Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shape factors for composite analyses #480

Merged
merged 2 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/user_guide/results.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ Plastic Analysis
~sectionproperties.analysis.section.Section.get_pc_p
~sectionproperties.analysis.section.Section.get_mp
~sectionproperties.analysis.section.Section.get_mp_p
~sectionproperties.analysis.section.Section.get_sf
~sectionproperties.analysis.section.Section.get_sf_p

.. _label-material-affects-results:

Expand Down
75 changes: 40 additions & 35 deletions src/sectionproperties/analysis/plastic_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,46 +204,51 @@ def calculate_plastic_properties(
if progress and task is not None:
progress.update(task_id=task, advance=1)

# if there are no materials specified, calculate shape factors
# calculate shape factors
props = section.section_props

if (
props.sxx is None
or props.syy is None
or props.s11 is None
or props.s22 is None
):
msg = "Property save failed."
raise RuntimeError(msg)

# for a geometric only analysis sf = s / z
if list(set(section.materials)) == [pre.DEFAULT_MATERIAL]:
if (
section.section_props.zxx_plus
and section.section_props.zxx_minus
and section.section_props.zyy_plus
and section.section_props.zyy_minus
props.zxx_plus
and props.zxx_minus
and props.zyy_plus
and props.zyy_minus
):
section.section_props.sf_xx_plus = (
section.section_props.sxx / section.section_props.zxx_plus
)
section.section_props.sf_xx_minus = (
section.section_props.sxx / section.section_props.zxx_minus
)
section.section_props.sf_yy_plus = (
section.section_props.syy / section.section_props.zyy_plus
)
section.section_props.sf_yy_minus = (
section.section_props.syy / section.section_props.zyy_minus
)
props.sf_xx_plus = props.sxx / props.zxx_plus
props.sf_xx_minus = props.sxx / props.zxx_minus
props.sf_yy_plus = props.syy / props.zyy_plus
props.sf_yy_minus = props.syy / props.zyy_minus
if (
section.section_props.s11
and section.section_props.s22
and section.section_props.z11_plus
and section.section_props.z11_minus
and section.section_props.z22_plus
and section.section_props.z22_minus
props.z11_plus
and props.z11_minus
and props.z22_plus
and props.z22_minus
):
section.section_props.sf_11_plus = (
section.section_props.s11 / section.section_props.z11_plus
)
section.section_props.sf_11_minus = (
section.section_props.s11 / section.section_props.z11_minus
)
section.section_props.sf_22_plus = (
section.section_props.s22 / section.section_props.z22_plus
)
section.section_props.sf_22_minus = (
section.section_props.s22 / section.section_props.z22_minus
)
props.sf_11_plus = props.s11 / props.z11_plus
props.sf_11_minus = props.s11 / props.z11_minus
props.sf_22_plus = props.s22 / props.z22_plus
props.sf_22_minus = props.s22 / props.z22_minus
else:
if props.my_xx and props.my_yy:
props.sf_xx_plus = props.sxx / props.my_xx
props.sf_xx_minus = props.sf_xx_plus
props.sf_yy_plus = props.syy / props.my_yy
props.sf_yy_minus = props.sf_yy_plus
if props.my_11 and props.my_22:
props.sf_11_plus = props.s11 / props.my_11
props.sf_11_minus = props.sf_11_plus
props.sf_22_plus = props.s22 / props.my_22
props.sf_22_minus = props.sf_22_plus

if progress and task is not None:
progress.update(
Expand Down
43 changes: 25 additions & 18 deletions src/sectionproperties/analysis/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -1184,7 +1184,8 @@ def calculate_plastic_properties(
Note:
If materials are specified, the values calculated for the plastic section
moduli are displayed as plastic moments (i.e :math:`M_p = f_y S`) and the
shape factors are not calculated.
shape factors are calculated as the ratio between the plastic moment and the
yield moment.

Warning:
The geometric properties must be calculated prior to the calculation of the
Expand All @@ -1197,7 +1198,7 @@ def calculate_plastic_properties(

- Plastic centroids (centroidal and principal axes)
- Plastic section moduli (centroidal and principal axes)
- Shape factors, non-composite only (centroidal and principal axes)
- Shape factors (centroidal and principal axes)
"""
# check that a geometric analysis has been performed
if self.section_props.cx is None:
Expand Down Expand Up @@ -3167,22 +3168,25 @@ def get_mp_p(self) -> tuple[float, float]:
def get_sf(self) -> tuple[float, float, float, float]:
"""Returns the cross-section centroidal axis shape factors.

This is a geometric only property, as such this can only be returned if material
properties have *not* been applied to the cross-section.
For a geometric-only analysis, the shape factor is defined as the ratio between
the plastic section modulus and the elastic section modulus. For a composite
analysis the shape factors is defined as the ratio between the plastic moment
and the yield moment.

.. note::
For composite analyses, the ``plus`` values will always equal the ``minus``
values as the yield moment occurs when the first fibre reaches its yield
strength. For geometric-only analyses, the elastic section moduli is
calculated with respect to the top and bottom fibres (i.e. ``plus`` and
``minus``).

Returns:
Centroidal axis shape factors with respect to the top and bottom fibres
(``sf_xx_plus``, ``sf_xx_minus``, ``sf_yy_plus``, ``sf_yy_minus``)

Raises:
RuntimeError: If material properties have been applied
RuntimeError: If a plastic analysis has not been performed
"""
if self.is_composite():
msg = "Attempting to get a geometric only property for a composite analysis"
msg += " (material properties have been applied)."
raise RuntimeError(msg)

if (
self.section_props.sf_xx_plus is None
or self.section_props.sf_xx_minus is None
Expand All @@ -3202,22 +3206,25 @@ def get_sf(self) -> tuple[float, float, float, float]:
def get_sf_p(self) -> tuple[float, float, float, float]:
"""Returns the cross-section principal axis shape factors.

This is a geometric only property, as such this can only be returned if material
properties have *not* been applied to the cross-section.
For a geometric-only analysis, the shape factor is defined as the ratio between
the plastic section modulus and the elastic section modulus. For a composite
analysis the shape factors is defined as the ratio between the plastic moment
and the yield moment.

.. note::
For composite analyses, the ``plus`` values will always equal the ``minus``
values as the yield moment occurs when the first fibre reaches its yield
strength. For geometric-only analyses, the elastic section moduli is
calculated with respect to the top and bottom fibres (i.e. ``plus`` and
``minus``).

Returns:
Principal bending axis shape factors with respect to the top and bottom
fibres (``sf_11_plus``, ``sf_11_minus``, ``sf_22_plus``, ``sf_22_minus``)

Raises:
RuntimeError: If material properties have been applied
RuntimeError: If a plastic analysis has not been performed
"""
if self.is_composite():
msg = "Attempting to get a geometric only property for a composite analysis"
msg += " (material properties have been applied)."
raise RuntimeError(msg)

if (
self.section_props.sf_11_plus is None
or self.section_props.sf_11_minus is None
Expand Down
38 changes: 18 additions & 20 deletions src/sectionproperties/post/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ def print_results(
try:
my_xx, my_yy = section.get_my()
table.add_row("my_xx", f"{my_xx:>{fmt}}")
table.add_row("my_yy-", f"{my_yy:>{fmt}}")
table.add_row("my_yy", f"{my_yy:>{fmt}}")
except RuntimeError:
pass

Expand Down Expand Up @@ -735,7 +735,7 @@ def print_results(
try:
my_11, my_22 = section.get_my_p()
table.add_row("my_11", f"{my_11:>{fmt}}")
table.add_row("my_22-", f"{my_22:>{fmt}}")
table.add_row("my_22", f"{my_22:>{fmt}}")
except RuntimeError:
pass

Expand Down Expand Up @@ -913,26 +913,24 @@ def print_results(
pass

# print cross-section sf
if not is_composite:
try:
sf_xx_plus, sf_xx_minus, sf_yy_plus, sf_yy_minus = section.get_sf()
table.add_row("sf_xx+", f"{sf_xx_plus:>{fmt}}")
table.add_row("sf_xx-", f"{sf_xx_minus:>{fmt}}")
table.add_row("sf_yy+", f"{sf_yy_plus:>{fmt}}")
table.add_row("sf_yy-", f"{sf_yy_minus:>{fmt}}")
except RuntimeError:
pass
try:
sf_xx_plus, sf_xx_minus, sf_yy_plus, sf_yy_minus = section.get_sf()
table.add_row("sf_xx+", f"{sf_xx_plus:>{fmt}}")
table.add_row("sf_xx-", f"{sf_xx_minus:>{fmt}}")
table.add_row("sf_yy+", f"{sf_yy_plus:>{fmt}}")
table.add_row("sf_yy-", f"{sf_yy_minus:>{fmt}}")
except RuntimeError:
pass

# print cross-section sf_p
if not is_composite:
try:
sf_11_plus, sf_11_minus, sf_22_plus, sf_22_minus = section.get_sf_p()
table.add_row("sf_11+", f"{sf_11_plus:>{fmt}}")
table.add_row("sf_11-", f"{sf_11_minus:>{fmt}}")
table.add_row("sf_22+", f"{sf_22_plus:>{fmt}}")
table.add_row("sf_22-", f"{sf_22_minus:>{fmt}}")
except RuntimeError:
pass
try:
sf_11_plus, sf_11_minus, sf_22_plus, sf_22_minus = section.get_sf_p()
table.add_row("sf_11+", f"{sf_11_plus:>{fmt}}")
table.add_row("sf_11-", f"{sf_11_minus:>{fmt}}")
table.add_row("sf_22+", f"{sf_22_plus:>{fmt}}")
table.add_row("sf_22-", f"{sf_22_minus:>{fmt}}")
except RuntimeError:
pass

console = Console()
console.print(table)
Expand Down
31 changes: 31 additions & 0 deletions tests/analysis/test_yield_moment.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ def test_rectangle():
assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0)
assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0)

# check shape factors
sec.calculate_plastic_properties()
sf_xx, _, sf_yy, _ = sec.get_sf()
sf_11, _, sf_22, _ = sec.get_sf_p()
assert sf_xx == pytest.approx(1.5)
assert sf_yy == pytest.approx(1.5)
assert sf_11 == pytest.approx(1.5)
assert sf_22 == pytest.approx(1.5)


def test_rectangle_rotated():
"""Test the yield moment of a simple rotated rectangle."""
Expand All @@ -92,6 +101,12 @@ def test_rectangle_rotated():
assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0)
assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0)

# check shape factors
sec.calculate_plastic_properties()
sf_11, _, sf_22, _ = sec.get_sf_p()
assert sf_11 == pytest.approx(1.5)
assert sf_22 == pytest.approx(1.5)


def test_isection():
"""Test the yield moment of an isection."""
Expand Down Expand Up @@ -119,6 +134,22 @@ def test_isection():
assert check_yield_index(stress, "sig_zz_m11") == pytest.approx(1.0)
assert check_yield_index(stress, "sig_zz_m22") == pytest.approx(1.0)

# check that shape factors with a geometric-only analysis match the composite
sec.calculate_plastic_properties()
sf_xx_c, _, sf_yy_c, _ = sec.get_sf()

geom = i_section(d=200, b=100, t_f=10, t_w=5, r=12, n_r=8)
geom.create_mesh(mesh_sizes=0, coarse=True)
sec = Section(geometry=geom)
sec.calculate_geometric_properties()
sec.calculate_plastic_properties()

sf_xx_plus, sf_xx_minus, sf_yy_plus, sf_yy_minus = sec.get_sf()
assert sf_xx_c == pytest.approx(sf_xx_plus)
assert sf_xx_c == pytest.approx(sf_xx_minus)
assert sf_yy_c == pytest.approx(sf_yy_plus)
assert sf_yy_c == pytest.approx(sf_yy_minus)


def test_rectangle_composite():
"""Test the yield moment of a composite rectangular section."""
Expand Down
6 changes: 0 additions & 6 deletions tests/post/test_get_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,19 +798,13 @@ def test_get_plastic(sec_no_mat_and_mat):
assert sf_yy_plus == pytest.approx(1.5)
assert sf_yy_minus == pytest.approx(1.5)

with pytest.raises(RuntimeError):
rect_mat.get_sf()

# check sf_p
sf_11_plus, sf_11_minus, sf_22_plus, sf_22_minus = rect_no_mat.get_sf_p()
assert sf_11_plus == pytest.approx(1.5)
assert sf_11_minus == pytest.approx(1.5)
assert sf_22_plus == pytest.approx(1.5)
assert sf_22_minus == pytest.approx(1.5)

with pytest.raises(RuntimeError):
rect_mat.get_sf_p()


def test_get_effective_material():
"""Tests effective material properties."""
Expand Down