Flexural buckling analysis, using R-Project

Over the past year, I’ve been astounded to find that R-project has slowly become one of my favorite programming environments. Not only do you get to use massive table data sets with many user maintained packages comparable to Python, but the advantages come all the way down to the syntax level, with everything being interpreted as columns and rows. Below is a demonstration of multiplying a list of numbers by a scalar:

1
2
3
> foo = c(1, 2, 3, 4, 5, 6); # column of numbers
> foo * 6;
[1] 6 12 18 24 30 36

Or, you can multiply two columns of equal size:

1
2
3
> bar = c(2, 3, 4, 5, 6, 7);
> foo * bar;
[1] 2 6 12 20 30 42

…all without any loops!

And you can plot graphs and other visualizations flexible on a level you would not be able to cleanly do with spreadsheets. Now, if you were to be using data science for the sake of doing online analytics of your site, usually Python would suffice. But if you’re working with CSV files, then you’d be better off using R-Project. You might not necessarily be working with CSV’s, but if you are given data on a spreadsheet, you can export it to a CSV file. Most engineering majors will get some coding experience one way or another, and R can be a good venue for some of it.

Example - AISC Catalog, Designing for compression

Now, say you wanted to design a steel column. The AISC website provides an XLSX file containing all the shapes in its construction manual in this page. Download the v15.0 shapes database file and open it in Excel. Open the sheet named Database v15.0.

Figure 1 - Screencap of the AISC shapes database XLSX file, downloaded from the AISC website.

This table contains pretty much all of the material available in the first part of AISC Steel Construction Manual 15th edition. Save the file as a CSV.

Figure 2 - Save the file as a CSV.

Make sure you’re on the AISC shapes sheet and not the Readme when you get the prompt below:

Figure 3 - Prompt telling you that you cannot save more than one sheet.

You want to click “Save Active Sheet”. Now we have a CSV file.

I’m going to be using a popular IDE called RStudio to make my steel column script, but you can go ahead and use whatever you want. In the console pane, you’re going to install a package for cleanly handling data tables called dplyr.

1
install.packages('dplyr');

My first few lines will include our above package and import the database we just made. We will need to use the fileEncoding parameter to avoid a multibyte string error and the na.strings to default the cells marked with a dash ‘-‘ to NA.

1
2
3
4
5
require('dplyr');

db = read.csv("~/Web/Excel/aisc-shapes-database-v15.0.csv",
fileEncoding = 'latin1',
na.strings = c('-','Ð'));

Once you run the code, you should see an entry called db in the Environment tab. Double click it and you should see the data in a separate tab.

Figure 4 - AISC catalog on RStudio.

You can also execute dplyr functions from the console, like for example, if you want to filter all HSS shapes, you can run db %>% filter(Type == 'HSS').

Let’s move on to our example. Let’s say the column is fixed on both ends, so that its K value is 0.65 and its length is 50 feet long. I will include references to the blue book for those who have it and want to follow along.

1
2
3
4
5
6
7
# Example parameters (use LRFD)
K = 0.65; # AISC 15th, 16.1-570, Table C-A-7.1
L = 50; # ft
P_req = 300 # k

L_c = K*L; # ft
E = 29000 # ksi, AISC 15th, 16.1-xxvii

For the sake of simplicity, we are going to assume that you’re going to use a doubly symmetrical shape for your column and not an angle. We want to use dplyr to filter out all the doubly symmetric shapes. So we’re going to use some regular expression here. I recommend this website if you’re not familiar with regex. Conveniently enough, there’s a column named ‘Type’ in the database that gives only the prefix to the name designation.

1
2
doubly_symmetric = db %>% rowwise() %>%
filter(grepl("^(W|M|S|HP|HSS|PIPE)$", Type))

We can use dplyr’s mutate() function to create new columns like you would on a spreadsheet, and we can pluck out columns we want using select().

1
2
3
4
5
6
7
8
doubly_symmetric = db %>% rowwise() %>%
filter(grepl("^(W|M|S|HP|HSS|PIPE)$", Type)) %>%
mutate(r = min(rx, ry)) %>% # Use smaller r value
mutate(L_c_over_r = L_c*12/r) %>% # slenderness ratio
filter(L_c_over_r <= 200) %>% # AISC 15th, 16.1-35 E2
mutate(F_e = pi^2*E/(L_c_over_r^2)); # ksi; AISC 15th, 16.1-36 E3-4

View(doubly_symmetric)

And now if we run this code, we will get a table like this:

Figure 5 - Doubly symmetric

Now, I’m going to create a function that chooses an available steel material for each shape. It’s important to note that while R has if-else blocks, they unfortunately cannot pass columns of data via dplyr mutate() functions. This section is going to have some more advanced regex pattern-matching in order to tell the difference between a round and rectangular HSS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
getPreferredFy = function(Type, AISC_Manual_Label) {
# Using AISC 15th 2-48 Table 2-4
ifelse(Type == 'HSS',
# Regex \ needs to be doubly escaped.
ifelse(grepl("^HSS\\d+(?:-\\d+\\/\\d+)?X\\d+(?:-\\d+\\/\\d+)?X\\d+\\/\\d+$",
AISC_Manual_Label), # rectangular
return(50),
ifelse(grepl("^HSS\\d+(?:.\\d+)?X\\d+(?:.\\d+)?$",
AISC_Manual_Label), # round
return(46),
stop(paste('Unidentified steel shape!', AISC_Manual_Label))
)
),
return(switch(as.character(Type),
'W' = 50,
'M' = 36,
'S' = 36,
'HP' = 50,
'C' = 36,
'MC' = 36,
'L' = 36,
'2L' = 36,
'PIPE' = 35, # Note: Table 2-4 categorizes this shape as HSS,
# but the database identifies it as PIPE.
stop(paste('Unidentified steel shape!', Type))))
);
}

Then you can use the design requirement formulas for the two limit states – inelastic and elastic buckling – in section E3, “Flexural Buckling of Members without Slender Elements” and use the reduction factor 0.9. Aside: There’s still some work to do on this blog setup, and one of my priorities will be to include a math environment for typing out the formulas.

At the end of our calculations, we still have to filter out our unqualified shapes that didn’t meet the required service load. We will sort our table by beam weight (assuming weight as a substitute for cost) and only use the figures relevant to what the client wanted.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
doubly_symmetric = db %>% rowwise() %>%
filter(grepl("^(W|M|S|HP|HSS|PIPE)$", Type)) %>%
mutate(r = min(rx, ry)) %>% # Use smaller r value
mutate(L_c_over_r = L_c*12/r) %>% # slenderness ratio
filter(L_c_over_r <= 200) %>% # AISC 15th, 16.1-35 E2
mutate(F_e = pi^2*E/(L_c_over_r^2)) %>% # ksi; AISC 15th, 16.1-36 E3-4
mutate(F_y = getPreferredFy(Type, AISC_Manual_Label)) %>%
mutate(F_cr = ifelse(L_c_over_r <= (4.71*sqrt(E/F_y)),
0.658^(F_y/F_e)*F_y, # AISC 15th, 16.1-35 E3-2
0.877*F_e # AISC 15th, 16.1-35 E3-3
)) %>% mutate(P_n = F_cr * A) %>% # AISC 15th, 16.1-35 E3-1
mutate(P_u_sup = 0.9*P_n) %>% # AISC 15th, 16.1-33 E1
filter(P_u_sup >= P_req) %>% # Filter out unqualified shapes
arrange(W) %>% # Use weight as proxy for cost
select(Type, AISC_Manual_Label, W, A, r, L_c_over_r, F_e, F_y, F_cr, P_n, P_u_sup);

If we run this code and open up our doubly_symmetric table, you’ll find that the HSS16.000x0.250 qualifies as the most lightweight shape for designing against flexural buckling. But what if the client hasn’t made up their mind on what shape they want to use for a column? Then you have to present the options. We need to find the lightest shape for each beam category, which will use the functions group_by() and slice().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
doubly_symmetric = db %>% rowwise() %>%
filter(grepl("^(W|M|S|HP|HSS|PIPE)$", Type)) %>%
mutate(r = min(rx, ry)) %>% # Use smaller r value
mutate(L_c_over_r = L_c*12/r) %>% # slenderness ratio
filter(L_c_over_r <= 200) %>% # AISC 15th, 16.1-35 E2
mutate(F_e = pi^2*E/(L_c_over_r^2)) %>% # ksi; AISC 15th, 16.1-36 E3-4
mutate(F_y = getPreferredFy(Type, AISC_Manual_Label)) %>%
mutate(F_cr = ifelse(L_c_over_r <= (4.71*sqrt(E/F_y)),
0.658^(F_y/F_e)*F_y, # AISC 15th, 16.1-35 E3-2
0.877*F_e # AISC 15th, 16.1-35 E3-3
)) %>% mutate(P_n = F_cr * A) %>% # AISC 15th, 16.1-35 E3-1
mutate(P_u_sup = 0.9*P_n) %>% # AISC 15th, 16.1-33 E1
filter(P_u_sup >= P_req) %>% # Filter out unqualified shapes
group_by(Type) %>% # Make 'Type' the key of the table
slice(which.min(W)) %>%
arrange(W) %>% # Use weight as proxy for cost
select(Type, AISC_Manual_Label, W, A, r, L_c_over_r, F_e, F_y, F_cr, P_n, P_u_sup);

You may get a warning in your console telling you “Grouping rowwise data frame strips rowwise nature”. This warning did not affect the outcome of our data, but if you don’t want to see this warning, include the line, ungroup() >%>, before the line group_by(Type) %>%. Your output table will give you four shapes.

Figure 6 - The most qualified shapes of each beam category.

To Summarize

So basically, we were able to run a flexural buckling analysis for all of the doubly symmetric shapes in the AISC catalog, simultaneously, then outputting the parameters of each shape we might want. Can you do all of that in Excel? Sure. But if you’re experienced or interested in coding your way through, you got other options! Really, that’s my point here – thinking outside the box.

And this is part of a more generalized trend I’ve found common in many of the engineering fields, especially Civil, where people problem-solve differently depending on programming experience. And oftentimes, many of times, the same solution works out for most people. I for example use spreadsheets for a lot of engineering notes and other self serving material.

Below, you can find the source code of the entire script, with the exception of the exported csv file for the shape properties, which you can make yourself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
require('dplyr');

db = read.csv("~/Web/Excel/aisc-shapes-database-v15.0.csv",
fileEncoding = 'latin1',
na.strings = c('-','Ð'));

# Example parameters (use LRFD)
K = 0.65; # AISC 15th, 16.1-570, Table C-A-7.1
L = 50; # ft
P_req = 300 # k

L_c = K*L; # ft
E = 29000 # ksi, AISC 15th, 16.1-xxvii

getPreferredFy = function(Type, AISC_Manual_Label) {
# Using AISC 15th 2-48 Table 2-4
ifelse(Type == 'HSS',
# Regex \ needs to be doubly escaped.
ifelse(grepl("^HSS\\d+(?:-\\d+\\/\\d+)?X\\d+(?:-\\d+\\/\\d+)?X\\d+\\/\\d+$",
AISC_Manual_Label), # rectangular
return(50),
ifelse(grepl("^HSS\\d+(?:.\\d+)?X\\d+(?:.\\d+)?$",
AISC_Manual_Label), # round
return(46),
stop(paste('Unidentified steel shape!', AISC_Manual_Label))
)
),
return(switch(as.character(Type),
'W' = 50,
'M' = 36,
'S' = 36,
'HP' = 50,
'C' = 36,
'MC' = 36,
'L' = 36,
'2L' = 36,
'PIPE' = 35, # Note: Table 2-4 categorizes this shape as HSS,
# but the database identifies it as PIPE.
stop(paste('Unidentified steel shape!', Type))))
);
}


doubly_symmetric = db %>% rowwise() %>%
filter(grepl("^(W|M|S|HP|HSS|PIPE)$", Type)) %>%
mutate(r = min(rx, ry)) %>% # Use smaller r value
mutate(L_c_over_r = L_c*12/r) %>% # slenderness ratio
filter(L_c_over_r <= 200) %>% # AISC 15th, 16.1-35 E2
mutate(F_e = pi^2*E/(L_c_over_r^2)) %>% # ksi; AISC 15th, 16.1-36 E3-4
mutate(F_y = getPreferredFy(Type, AISC_Manual_Label)) %>%
mutate(F_cr = ifelse(L_c_over_r <= (4.71*sqrt(E/F_y)),
0.658^(F_y/F_e)*F_y, # AISC 15th, 16.1-35 E3-2
0.877*F_e # AISC 15th, 16.1-35 E3-3
)) %>% mutate(P_n = F_cr * A) %>% # AISC 15th, 16.1-35 E3-1
mutate(P_u_sup = 0.9*P_n) %>% # AISC 15th, 16.1-33 E1
filter(P_u_sup >= P_req) %>% # Filter out unqualified shapes
ungroup() %>% # strip rowwise nature so we can do grouping.
group_by(Type) %>% # Make 'Type' the key of the table
slice(which.min(W)) %>%
arrange(W) %>% # Use weight as proxy for cost
select(Type, AISC_Manual_Label, W, A, r, L_c_over_r, F_e, F_y, F_cr, P_n, P_u_sup);