Validation US Designation¶
In this page, we validate the section properties of some US designations. All W, S, M, WT, ST, MT, HP, HSS designations, which are compiled and well documented in the AISC Shapes Database v16.0, are included.
Some Utilities¶
The spreadsheet is loaded directly via the url.
import pandas
section_table = pandas.read_excel(
"https://www.aisc.org/globalassets/product-files-not-searched/manuals/aisc-shapes-database-v16.0.xlsx",
sheet_name=1,
storage_options={"User-Agent": "Mozilla/5.0"},
usecols="A:CF",
)
print(section_table.head())
--------------------------------------------------------------------------- HTTPError Traceback (most recent call last) Cell In[1], line 3 1 import pandas 2 ----> 3 section_table = pandas.read_excel( 4 "https://www.aisc.org/globalassets/product-files-not-searched/manuals/aisc-shapes-database-v16.0.xlsx", 5 sheet_name=1, 6 storage_options={"User-Agent": "Mozilla/5.0"}, File ~/checkouts/readthedocs.org/user_builds/suanpan-manual/envs/4.0/lib/python3.12/site-packages/pandas/io/excel/_base.py:481, in read_excel(io, sheet_name, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skiprows, nrows, na_values, keep_default_na, na_filter, verbose, parse_dates, date_format, thousands, decimal, comment, skipfooter, storage_options, dtype_backend, engine_kwargs) 479 if not isinstance(io, ExcelFile): 480 should_close = True --> 481 io = ExcelFile( 482 io, 483 storage_options=storage_options, 484 engine=engine, 485 engine_kwargs=engine_kwargs, 486 ) 487 elif engine and engine != io.engine: 488 raise ValueError( 489 "Engine should not be specified when passing " 490 "an ExcelFile - ExcelFile already has the engine set" 491 ) File ~/checkouts/readthedocs.org/user_builds/suanpan-manual/envs/4.0/lib/python3.12/site-packages/pandas/io/excel/_base.py:1604, in ExcelFile.__init__(self, path_or_buffer, engine, storage_options, engine_kwargs) 1601 ext = "xls" 1603 if ext is None: -> 1604 ext = inspect_excel_format( 1605 content_or_path=path_or_buffer, storage_options=storage_options 1606 ) 1607 if ext is None: 1608 raise ValueError( 1609 "Excel file format cannot be determined, you must specify " 1610 "an engine manually." 1611 ) File ~/checkouts/readthedocs.org/user_builds/suanpan-manual/envs/4.0/lib/python3.12/site-packages/pandas/io/excel/_base.py:1452, in inspect_excel_format(content_or_path, storage_options) 1417 def inspect_excel_format( 1418 content_or_path: FilePath | ReadBuffer[bytes], 1419 storage_options: StorageOptions | None = None, 1420 ) -> str | None: 1421 """ 1422 Inspect the path or content of an excel file and get its format. 1423 (...) 1450 If resulting stream does not have an XLS signature and is not a valid zipfile. 1451 """ -> 1452 with get_handle( 1453 content_or_path, "rb", storage_options=storage_options, is_text=False 1454 ) as handle: 1455 stream = handle.handle 1456 stream.seek(0) File ~/checkouts/readthedocs.org/user_builds/suanpan-manual/envs/4.0/lib/python3.12/site-packages/pandas/io/common.py:772, in get_handle(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options) 769 codecs.lookup_error(errors) 771 # open URLs --> 772 ioargs = _get_filepath_or_buffer( 773 path_or_buf, 774 encoding=encoding, 775 compression=compression, 776 mode=mode, 777 storage_options=storage_options, 778 ) 780 handle = ioargs.filepath_or_buffer 781 handles: list[BaseBuffer] File ~/checkouts/readthedocs.org/user_builds/suanpan-manual/envs/4.0/lib/python3.12/site-packages/pandas/io/common.py:404, in _get_filepath_or_buffer(filepath_or_buffer, encoding, compression, mode, storage_options) 402 # assuming storage_options is to be interpreted as headers 403 req_info = urllib.request.Request(filepath_or_buffer, headers=storage_options) --> 404 with urlopen(req_info) as req: 405 content_encoding = req.headers.get("Content-Encoding", None) 406 if content_encoding == "gzip": 407 # Override compression based on Content-Encoding header File ~/checkouts/readthedocs.org/user_builds/suanpan-manual/envs/4.0/lib/python3.12/site-packages/pandas/io/common.py:281, in urlopen(*args, **kwargs) 275 """ 276 Lazy-import wrapper for stdlib urlopen, as that imports a big chunk of 277 the stdlib. 278 """ 279 import urllib.request --> 281 return urllib.request.urlopen(*args, **kwargs) File ~/.asdf/installs/python/3.12.10/lib/python3.12/urllib/request.py:215, in urlopen(url, data, timeout, cafile, capath, cadefault, context) 213 else: 214 opener = _opener --> 215 return opener.open(url, data, timeout) File ~/.asdf/installs/python/3.12.10/lib/python3.12/urllib/request.py:521, in OpenerDirector.open(self, fullurl, data, timeout) 519 for processor in self.process_response.get(protocol, []): 520 meth = getattr(processor, meth_name) --> 521 response = meth(req, response) 523 return response File ~/.asdf/installs/python/3.12.10/lib/python3.12/urllib/request.py:630, in HTTPErrorProcessor.http_response(self, request, response) 627 # According to RFC 2616, "2xx" code indicates that the client's 628 # request was successfully received, understood, and accepted. 629 if not (200 <= code < 300): --> 630 response = self.parent.error( 631 'http', request, response, code, msg, hdrs) 633 return response File ~/.asdf/installs/python/3.12.10/lib/python3.12/urllib/request.py:559, in OpenerDirector.error(self, proto, *args) 557 if http_err: 558 args = (dict, 'default', 'http_error_default') + orig_args --> 559 return self._call_chain(*args) File ~/.asdf/installs/python/3.12.10/lib/python3.12/urllib/request.py:492, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args) 490 for handler in handlers: 491 func = getattr(handler, meth_name) --> 492 result = func(*args) 493 if result is not None: 494 return result File ~/.asdf/installs/python/3.12.10/lib/python3.12/urllib/request.py:639, in HTTPDefaultErrorHandler.http_error_default(self, req, fp, code, msg, hdrs) 638 def http_error_default(self, req, fp, code, msg, hdrs): --> 639 raise HTTPError(req.full_url, code, msg, hdrs, fp) HTTPError: HTTP Error 403: Forbidden
We define a function to get the area, moment of inertia about the strong and weak axes.
def get_properties(designation: str):
section = section_table[section_table["AISC_Manual_Label"] == designation]
return [section[x].values[0] for x in ("A", "Ix", "Iy")]
Analysis Template¶
Next, we define the function to run the analysis and extract the result.
The following is the template model file that will be used. A few things to note:
- The material is assumed to be elastic with a unit elastic modulus.
- The default integration scheme is used for each designation.
- A unit displacement load is applied to axial, strong axis bending and weak axis bending.
- The
US3DCcategory is used, it automatically recentre the section to its barycentre if it is not symmetric.
template = """
node 1 0 0 0
material Elastic1D 1 1
section US3DC $designation$ 1 1 1
element SingleSection3D 1 1 1
displacement 1 0 1 axial 1
displacement 2 0 1 rs 1
displacement 3 0 1 rw 1
step static 1 1
set ini_step_size 1
analyze
peek node 1
exit
"""
By using the above settings, the resistances on three DoFs are effectively the area and moments of inertia.
Extract Results¶
To extract the data, we process the output.
import subprocess
import tempfile
def run_analysis(designation: str):
with tempfile.NamedTemporaryFile() as fp:
with open(fp.name, "w") as f:
f.write(template.replace("$designation$", designation))
result = (
subprocess.check_output(["suanpan", "-nu", "-nc", "-f", fp.name])
.decode("utf-8")
.splitlines()
)
for i, line in enumerate(result):
if line.startswith("Resistance"):
return [float(x) for x in result[i + 1].split()]
The ratio between numerical result and the reference value given in the database is stored. Ideally, this ratio shall be close to unity, meaning a good match.
all_results = {}
def validate(designation: str):
if result := run_analysis(designation):
all_results[designation] = [
x / y for x, y in zip(result, get_properties(designation))
]
Collect All Sections¶
We can now iterate over all sections.
for _, row in section_table[
section_table["AISC_Manual_Label"].str.startswith(("W", "M", "S", "HP", "HSS"))
].iterrows():
designation = row["AISC_Manual_Label"]
# ignore C sections
if designation.startswith("MC"):
continue
validate(designation)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[6], line 1 ----> 1 for _, row in section_table[ 2 section_table["AISC_Manual_Label"].str.startswith(("W", "M", "S", "HP", "HSS")) 3 ].iterrows(): 4 designation = row["AISC_Manual_Label"] NameError: name 'section_table' is not defined
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(90, 50), tight_layout=True)
rhs_results = {
k: v for k, v in all_results.items() if k.startswith("HSS") and k.count("X") == 2
}
chs_results = {
k: v for k, v in all_results.items() if k.startswith("HSS") and k.count("X") == 1
}
i_results = {
k: v for k, v in all_results.items() if not k.startswith("HSS") and "T" not in k
}
t_results = {
k: v for k, v in all_results.items() if not k.startswith("HSS") and "T" in k
}
chs_t_results = {**chs_results, **t_results}
total = 3 * (int(bool(rhs_results)) + int(bool(i_results)) + int(bool(chs_t_results)))
counter = 0
def plot(title, index, results):
if not results:
return
values = [x[index] for x in results.values()]
min_value = min(values)
max_value = max(values)
colors = ["red" if abs(x - 1) > 0.05 else "green" for x in values]
global counter
counter += 1
ax = fig.add_subplot(total, 1, counter)
ax.bar(results.keys(), values, color=colors)
ax.set_ylabel("Numerical/Analytical")
ax.set_xlabel("Section")
ax.set_ybound(min_value - 0.02, max_value + 0.01)
ax.set_xlim(-1, len(results))
ax.grid()
for i, v in enumerate(values):
ax.text(
i, min_value - 0.01, f"{v:.3f}", horizontalalignment="center", rotation=90
)
ax.set_title(title)
ax.set_xticks(ax.get_xticks())
ax.set_xticklabels(ax.get_xticklabels(), rotation=90)
plot("HSS (Rectangle) Area", 0, rhs_results)
plot("HSS (Rectangle) Strong Axis Moment of Inertia", 1, rhs_results)
plot("HSS (Rectangle) Weak Axis Moment of Inertia", 2, rhs_results)
plot("I-Section Area", 0, i_results)
plot("I-Section Strong Axis Moment of Inertia", 1, i_results)
plot("I-Section Weak Axis Moment of Inertia", 2, i_results)
plot("HSS (Circle) and T-Section Area", 0, chs_t_results)
plot(
"HSS (Circle) and T-Section Strong Axis Moment of Inertia",
1,
chs_t_results,
)
plot("HSS (Circle) and T-Section Weak Axis Moment of Inertia", 2, chs_t_results)
<Figure size 9000x5000 with 0 Axes>
Area¶
In general, the area can be relatively accurately computed. However, as all those sections are internally modelled by three flat pieces, the root fillet cannot be accounted for. As a result, the numerical area is often smaller than the reference value.
Some very light M shapes cannot be well approximated.
Strong Axis Bending¶
Some very heavy T sections tend to have poor strong axis moment of inertia. In this shapes, the thickness of flange accounts for a significant portion of the overall depth. The normal stress in the flange presents gradient. In the meantime, there is only one layer of integration points along the thickness of flange, which is not accurate enough in this case.
Weak Axis Bending¶
The tapered shapes tend to have more material towards the center, as a result, the weak axis moment of inertia is smaller. It is modelled by a flat rectangle in numerical models, this overestimate the moment of inertia, mainly for S and ST shapes.
fig.savefig("US.pdf")
us_section = {}
for key, value in all_results.items():
num_ip = 10 if "HSS" in key and key.count("X") == 1 else 6
us_section[f"{key}-2D"] = {
"prefix": key,
"description": f"US 2D section {key}, accuracy: {value[0]:.2f}/{value[1]:.2f}",
"body": [
f"section US2D {key} " + "${1:(1)} ${2:(2)} ${3:[3]} ${4:[4]} ${5:[5]}",
"# (1) int, unique tag",
"# (2) int, material tag",
"# [3] double, scale, default: 1.0",
f"# [4] int, number of integration points, default: {num_ip}",
"# [5] double, eccentricity, default: 0.0",
"",
],
}
us_section[f"{key}-3D"] = {
"prefix": key,
"description": f"US 3D section {key}, accuracy: {value[0]:.2f}/{value[1]:.2f}/{value[2]:.2f}",
"body": [
f"section US3D {key} "
+ "${1:(1)} ${2:(2)} ${3:[3]} ${4:[4]} ${5:[5]} ${6:[6]}",
"# (1) int, unique tag",
"# (2) int, material tag",
"# [3] double, scale, default: 1.0",
f"# [4] int, number of integration points, default: {num_ip}",
"# [5] double, eccentricity of y axis, default: 0.0",
"# [6] double, eccentricity of z axis, default: 0.0",
"",
],
}
import json
with open("us_sections_completion.json", "w") as f:
json.dump({k: v for k, v in sorted(us_section.items())}, f, indent=4)
highlighting = []
for key in sorted(all_results.keys()):
highlighting.append(
{
"name": "support.constant.section",
"match": "\\b(?i)" + key.replace(".", "\\.") + "\\b",
},
)
with open("us_sections_highlight.json", "w") as f:
json.dump(highlighting, f, indent=4)
print(
r"\b(?i)("
+ "|".join([x.replace(".", r"\.") for x in sorted(all_results.keys())])
+ r")\b"
)
\b(?i)()\b
from itertools import zip_longest
all_w = [x for x in all_results.keys() if x.startswith("W") and not x.startswith("WT")]
all_m = [x for x in all_results.keys() if x.startswith("M")]
all_s = [x for x in all_results.keys() if x.startswith("S") and not x.startswith("ST")]
all_hp = [x for x in all_results.keys() if x.startswith("HP")]
all_wt = [x for x in all_results.keys() if x.startswith("WT")]
all_mt = [x for x in all_results.keys() if x.startswith("MT")]
all_st = [x for x in all_results.keys() if x.startswith("ST")]
all_rhs = [x for x in all_results.keys() if x.startswith("HSS") and x.count("X") == 2]
all_chs = [x for x in all_results.keys() if x.startswith("HSS") and x.count("X") == 1]
md_table = [
"| W | M | S | HP | WT | MT | ST | HSS (Rectangle) | HSS (Circle) |",
"|:--------|:-----------|:---------|:---------|:-------------|:-----------|:------------|:--------------------|:----------------|",
]
for w, m, s, hp, wt, mt, st, rhs, chs in zip_longest(
all_w, all_m, all_s, all_hp, all_wt, all_mt, all_st, all_rhs, all_chs, fillvalue=""
):
md_table.append(f"|{w}|{m}|{s}|{hp}|{wt}|{mt}|{st}|{rhs}|{chs}|")
print("\n".join(md_table))
| W | M | S | HP | WT | MT | ST | HSS (Rectangle) | HSS (Circle) | |:--------|:-----------|:---------|:---------|:-------------|:-----------|:------------|:--------------------|:----------------|