Skip to content

Commit 76b95db

Browse files
Merge pull request #476 from robbievanleeuwen/feature/yield-moment
Add yield moment calculation
2 parents 92090b6 + 4e949ef commit 76b95db

File tree

6 files changed

+423
-4
lines changed

6 files changed

+423
-4
lines changed

docs/user_guide.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Cross-Section Analysis
5252

5353
* ☑ Second moments of area
5454
* ☑ Elastic section moduli
55-
* Yield moment
55+
* Yield moment
5656
* ☑ Radii of gyration
5757
* ☑ Plastic centroid
5858
* ☑ Plastic section moduli
@@ -62,7 +62,7 @@ Cross-Section Analysis
6262

6363
* ☑ Second moments of area
6464
* ☑ Elastic section moduli
65-
* Yield moment
65+
* Yield moment
6666
* ☑ Radii of gyration
6767
* ☑ Plastic centroid
6868
* ☑ Plastic section moduli

docs/user_guide/results.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,12 @@ Geometric Analysis
9999
~sectionproperties.analysis.section.Section.get_c
100100
~sectionproperties.analysis.section.Section.get_eic
101101
~sectionproperties.analysis.section.Section.get_ez
102+
~sectionproperties.analysis.section.Section.get_my
102103
~sectionproperties.analysis.section.Section.get_rc
103104
~sectionproperties.analysis.section.Section.get_eip
104105
~sectionproperties.analysis.section.Section.get_phi
105106
~sectionproperties.analysis.section.Section.get_ezp
107+
~sectionproperties.analysis.section.Section.get_my_p
106108
~sectionproperties.analysis.section.Section.get_rp
107109
~sectionproperties.analysis.section.Section.get_nu_eff
108110
~sectionproperties.analysis.section.Section.get_e_eff

docs/user_guide/theory.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,20 @@ extreme (min. and max.) coordinates of the cross-section in the x and y-directio
420420
Z_{yy}^+ = \frac{I_{\overline{yy}}}{x_{max} - x_c} \\
421421
Z_{yy}^- = \frac{I_{\overline{yy}}}{x_c - x_{min}} \\
422422
423+
Yield Moments
424+
~~~~~~~~~~~~~
425+
426+
The yield moment is defined as the lowest bending moment that causes any point within
427+
cross-section to reach the yield strength. Note that this implementation is purely
428+
linear-elastic i.e. uses the linear-elastic modulus and bi-directional yield strength
429+
only.
430+
431+
``sectionproperties`` applies a unit bending moment, about each axis separately, and
432+
determines the yield index for each point within the mesh. The yield index is defined as
433+
the stress divided by the material yield strength. Through this method, a critical yield
434+
index is determined (i.e. point which will yield first under bending) and the yield
435+
moment calculated as the inverse of the critical yield index.
436+
423437
.. _label-theory-plastic-section-moduli:
424438

425439
Plastic Section Moduli

src/sectionproperties/analysis/section.py

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ def calculate_geometric_properties(self) -> None:
202202
- Centroidal section moduli
203203
- Radii of gyration
204204
- Principal axis properties
205+
- Yield moments (composite only)
205206
"""
206207

207208
def calculate_geom(progress: Progress | None = None) -> None:
@@ -259,11 +260,87 @@ def calculate_geom(progress: Progress | None = None) -> None:
259260
)
260261
self.section_props.e_eff = self.section_props.ea / self.section_props.area
261262
self.section_props.g_eff = self.section_props.ga / self.section_props.area
263+
264+
# calculate derived properties
262265
self.section_props.calculate_elastic_centroid()
263266
self.section_props.calculate_centroidal_properties(
264267
node_list=self.mesh["vertices"]
265268
)
266269

270+
# calculate yield moments
271+
self.section_props.my_xx = 0.0
272+
self.section_props.my_yy = 0.0
273+
self.section_props.my_11 = 0.0
274+
self.section_props.my_22 = 0.0
275+
276+
# calculate yield moments:
277+
# 1) loop through each material group and through each element in the group
278+
# 2) for each point, calculate the bending stress from a unit bending moment
279+
# in each direction (mxx, myy, m11, m22)
280+
# 3) from this, calculate the yield index for each point
281+
# 4) get the largest yield index and scale the bending moment such that the
282+
# yield index is 1
283+
284+
# initialise the yield indexes
285+
yield_index = {
286+
"mxx": 0.0,
287+
"myy": 0.0,
288+
"m11": 0.0,
289+
"m22": 0.0,
290+
}
291+
292+
# get useful section properties
293+
cx = self.section_props.cx
294+
cy = self.section_props.cy
295+
phi = self.section_props.phi
296+
ixx = self.section_props.ixx_c
297+
iyy = self.section_props.iyy_c
298+
ixy = self.section_props.ixy_c
299+
i11 = self.section_props.i11_c
300+
i22 = self.section_props.i22_c
301+
302+
if ixx is None or iyy is None or ixy is None or i11 is None or i22 is None:
303+
msg = "Section properties failed to save."
304+
raise RuntimeError(msg)
305+
306+
# loop through each material group
307+
for group in self.material_groups:
308+
em = group.material.elastic_modulus
309+
fy = group.material.yield_strength
310+
311+
# loop through each element in the material group
312+
for el in group.elements:
313+
# loop through each node in the element
314+
for coord in el.coords.transpose():
315+
# calculate coordinates wrt centroidal & principal axes
316+
x = coord[0] - cx
317+
y = coord[1] - cy
318+
x11, y22 = fea.principal_coordinate(phi=phi, x=x, y=y)
319+
320+
# calculate bending stresses due to unit moments
321+
sig_mxx = em * (
322+
-ixy / (ixx * iyy - ixy**2) * x
323+
+ iyy / (ixx * iyy - ixy**2) * y
324+
)
325+
sig_myy = em * (
326+
-ixx / (ixx * iyy - ixy**2) * x
327+
+ ixy / (ixx * iyy - ixy**2) * y
328+
)
329+
sig_m11 = em / i11 * y22
330+
sig_m22 = -em / i22 * x11
331+
332+
# update yield indexes
333+
yield_index["mxx"] = max(yield_index["mxx"], abs(sig_mxx / fy))
334+
yield_index["myy"] = max(yield_index["myy"], abs(sig_myy / fy))
335+
yield_index["m11"] = max(yield_index["m11"], abs(sig_m11 / fy))
336+
yield_index["m22"] = max(yield_index["m22"], abs(sig_m22 / fy))
337+
338+
# calculate yield moments
339+
self.section_props.my_xx = 1.0 / yield_index["mxx"]
340+
self.section_props.my_yy = 1.0 / yield_index["myy"]
341+
self.section_props.my_11 = 1.0 / yield_index["m11"]
342+
self.section_props.my_22 = 1.0 / yield_index["m22"]
343+
267344
if progress and task is not None:
268345
msg = "[bold green]:white_check_mark: Geometric analysis complete"
269346
progress.update(task_id=task, description=msg)
@@ -1120,7 +1197,7 @@ def calculate_plastic_properties(
11201197
11211198
- Plastic centroids (centroidal and principal axes)
11221199
- Plastic section moduli (centroidal and principal axes)
1123-
- Shape factors, non-composite only (centroidal and principal axe)
1200+
- Shape factors, non-composite only (centroidal and principal axes)
11241201
"""
11251202
# check that a geometric analysis has been performed
11261203
if self.section_props.cx is None:
@@ -2240,6 +2317,32 @@ def get_ez(
22402317
self.section_props.zyy_minus / e_ref,
22412318
)
22422319

2320+
def get_my(self) -> tuple[float, float]:
2321+
"""Returns the yield moment for bending about the centroidal axis.
2322+
2323+
This is a composite only property, as such this can only be returned if material
2324+
properties have been applied to the cross-section.
2325+
2326+
Returns:
2327+
Yield moment for bending about the centroidal ``x`` and ``y`` axes
2328+
(``my_xx``, ``my_yy``)
2329+
2330+
Raises:
2331+
RuntimeError: If material properties have *not* been applied
2332+
RuntimeError: If a geometric analysis has not been performed
2333+
"""
2334+
if not self.is_composite():
2335+
msg = "Attempting to get a composite only property for a geometric analysis"
2336+
msg += " (material properties have not been applied). Consider using"
2337+
msg += " get_z()."
2338+
raise RuntimeError(msg)
2339+
2340+
if self.section_props.my_xx is None or self.section_props.my_yy is None:
2341+
msg = "Conduct a geometric analysis."
2342+
raise RuntimeError(msg)
2343+
2344+
return (self.section_props.my_xx, self.section_props.my_yy)
2345+
22432346
def get_rc(self) -> tuple[float, float]:
22442347
"""Returns the cross-section centroidal radii of gyration.
22452348
@@ -2418,6 +2521,32 @@ def get_ezp(
24182521
self.section_props.z22_minus / e_ref,
24192522
)
24202523

2524+
def get_my_p(self) -> tuple[float, float]:
2525+
"""Returns the yield moment for bending about the principal axis.
2526+
2527+
This is a composite only property, as such this can only be returned if material
2528+
properties have been applied to the cross-section.
2529+
2530+
Returns:
2531+
Yield moment for bending about the principal ``11`` and ``22`` axes
2532+
(``my_11``, ``my_22``)
2533+
2534+
Raises:
2535+
RuntimeError: If material properties have *not* been applied
2536+
RuntimeError: If a geometric analysis has not been performed
2537+
"""
2538+
if not self.is_composite():
2539+
msg = "Attempting to get a composite only property for a geometric analysis"
2540+
msg += " (material properties have not been applied). Consider using"
2541+
msg += " get_zp()."
2542+
raise RuntimeError(msg)
2543+
2544+
if self.section_props.my_11 is None or self.section_props.my_22 is None:
2545+
msg = "Conduct a geometric analysis."
2546+
raise RuntimeError(msg)
2547+
2548+
return (self.section_props.my_11, self.section_props.my_22)
2549+
24212550
def get_rp(self) -> tuple[float, float]:
24222551
"""Returns the cross-section principal radii of gyration.
24232552

src/sectionproperties/post/post.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ class SectionProperties:
7272
negative extreme value of the 11-axis
7373
r11_c: Radius of gyration about the principal 11-axis.
7474
r22_c: Radius of gyration about the principal 22-axis.
75+
my_xx: Yield moment about the x-axis
76+
my_yy: Yield moment about the y-axis
77+
my_11: Yield moment about the 11-axis
78+
my_22: Yield moment about the 22-axis
7579
j: Torsion constant
7680
omega: Warping function
7781
psi_shear: Psi shear function
@@ -165,6 +169,10 @@ class SectionProperties:
165169
r11_c: float | None = None
166170
r22_c: float | None = None
167171
j: float | None = None
172+
my_xx: float | None = None
173+
my_yy: float | None = None
174+
my_11: float | None = None
175+
my_22: float | None = None
168176
omega: npt.NDArray[np.float64] | None = None
169177
psi_shear: npt.NDArray[np.float64] | None = None
170178
phi_shear: npt.NDArray[np.float64] | None = None
@@ -278,7 +286,7 @@ def calculate_centroidal_properties(
278286
else:
279287
self.phi = np.arctan2(self.ixx_c - self.i11_c, self.ixy_c) * 180 / np.pi
280288

281-
# initialise min, max variables TODO: check for `if xxx:` where xxx is float
289+
# initialise min, max variables
282290
if self.phi is not None:
283291
x1, y2 = fea.principal_coordinate(
284292
phi=self.phi,
@@ -662,6 +670,15 @@ def print_results(
662670
except RuntimeError:
663671
pass
664672

673+
# print cross-section my
674+
if is_composite:
675+
try:
676+
my_xx, my_yy = section.get_my()
677+
table.add_row("my_xx", f"{my_xx:>{fmt}}")
678+
table.add_row("my_yy-", f"{my_yy:>{fmt}}")
679+
except RuntimeError:
680+
pass
681+
665682
# print cross-section rc
666683
try:
667684
rx, ry = section.get_rc()
@@ -713,6 +730,15 @@ def print_results(
713730
except RuntimeError:
714731
pass
715732

733+
# print cross-section my_p
734+
if is_composite:
735+
try:
736+
my_11, my_22 = section.get_my_p()
737+
table.add_row("my_11", f"{my_11:>{fmt}}")
738+
table.add_row("my_22-", f"{my_22:>{fmt}}")
739+
except RuntimeError:
740+
pass
741+
716742
# print cross-section rp
717743
try:
718744
r11, r22 = section.get_rp()

0 commit comments

Comments
 (0)