Spaces:
Sleeping
Sleeping
| """ | |
| Tests for Eurocode 3 code checks (eurocode3.py). | |
| Covers: | |
| - Bending utilisation ratio | |
| - Shear utilisation ratio | |
| - Buckling reduction factor | |
| - Interaction formula | |
| - Deflection check | |
| - Wall elements pass trivially | |
| """ | |
| import math | |
| import pytest | |
| from structural_design_env.solver.eurocode3 import check_member, MemberChecks | |
| from structural_design_env.solver.sections import ( | |
| COLUMN_SECTIONS, | |
| BEAM_SECTIONS, | |
| F_Y_STEEL, | |
| E_STEEL, | |
| ) | |
| class TestBendingCheck: | |
| def test_zero_moment_gives_zero_ur(self): | |
| props = COLUMN_SECTIONS["HEB200"] | |
| checks = check_member("column", props, {"N": 0, "V": 0, "M_max": 0, "delta_max_mm": 0}, L_m=3.5) | |
| assert checks.UR_bending == 0.0 | |
| def test_at_yield_gives_ur_one(self): | |
| props = BEAM_SECTIONS["IPE300"] | |
| # M at yield = W_el_y * f_y | |
| M_yield = props["W_el_y"] * F_Y_STEEL | |
| checks = check_member("beam", props, {"N": 0, "V": 0, "M_max": M_yield, "delta_max_mm": 0}, L_m=5.0) | |
| assert checks.UR_bending == pytest.approx(1.0, rel=0.01) | |
| def test_overstressed_ur_above_one(self): | |
| props = BEAM_SECTIONS["IPE200"] | |
| M_yield = props["W_el_y"] * F_Y_STEEL | |
| checks = check_member("beam", props, {"N": 0, "V": 0, "M_max": 2.0 * M_yield, "delta_max_mm": 0}, L_m=5.0) | |
| assert checks.UR_bending > 1.0 | |
| assert not checks.passes_all | |
| class TestShearCheck: | |
| def test_zero_shear_gives_zero_ur(self): | |
| props = BEAM_SECTIONS["IPE360"] | |
| checks = check_member("beam", props, {"N": 0, "V": 0, "M_max": 0, "delta_max_mm": 0}, L_m=4.0) | |
| assert checks.UR_shear == 0.0 | |
| def test_shear_at_capacity(self): | |
| props = BEAM_SECTIONS["IPE300"] | |
| V_Rd = F_Y_STEEL * props["A_v"] / math.sqrt(3) | |
| checks = check_member("beam", props, {"N": 0, "V": V_Rd, "M_max": 0, "delta_max_mm": 0}, L_m=5.0) | |
| assert checks.UR_shear == pytest.approx(1.0, rel=0.01) | |
| class TestBucklingCheck: | |
| def test_columns_have_buckling_check(self): | |
| props = COLUMN_SECTIONS["HEB200"] | |
| # Apply significant compressive load | |
| N_Ed = 500e3 # 500 kN | |
| checks = check_member( | |
| "column", props, | |
| {"N": -N_Ed, "V": 0, "M_max": 0, "delta_max_mm": 0}, | |
| L_m=3.5, | |
| ) | |
| assert checks.UR_buckling > 0.0 | |
| def test_beams_have_zero_buckling(self): | |
| props = BEAM_SECTIONS["IPE400"] | |
| checks = check_member( | |
| "beam", props, | |
| {"N": -100e3, "V": 50e3, "M_max": 50e3, "delta_max_mm": 5.0}, | |
| L_m=6.0, | |
| ) | |
| assert checks.UR_buckling == 0.0 | |
| def test_short_column_chi_near_one(self): | |
| """Very short column should have chi close to 1.0.""" | |
| props = COLUMN_SECTIONS["HEB300"] | |
| A = props["A"] | |
| # Squash load | |
| N_b_Rd_max = A * F_Y_STEEL | |
| # Apply small load → UR_buckling should be small | |
| checks = check_member( | |
| "column", props, | |
| {"N": -N_b_Rd_max * 0.1, "V": 0, "M_max": 0, "delta_max_mm": 0}, | |
| L_m=1.0, # very short | |
| ) | |
| assert checks.UR_buckling < 0.15 | |
| def test_slender_column_higher_buckling_ur(self): | |
| props = COLUMN_SECTIONS["HEB140"] | |
| A = props["A"] | |
| N_Ed = A * F_Y_STEEL * 0.5 # 50% of squash load | |
| checks_short = check_member( | |
| "column", props, | |
| {"N": -N_Ed, "V": 0, "M_max": 0, "delta_max_mm": 0}, | |
| L_m=2.0, | |
| ) | |
| checks_tall = check_member( | |
| "column", props, | |
| {"N": -N_Ed, "V": 0, "M_max": 0, "delta_max_mm": 0}, | |
| L_m=8.0, | |
| ) | |
| assert checks_tall.UR_buckling > checks_short.UR_buckling | |
| class TestDeflectionCheck: | |
| def test_zero_deflection_gives_zero_ur(self): | |
| props = BEAM_SECTIONS["IPE300"] | |
| checks = check_member("beam", props, {"N": 0, "V": 0, "M_max": 0, "delta_max_mm": 0}, L_m=5.0) | |
| assert checks.UR_deflection == 0.0 | |
| def test_at_limit_gives_ur_one(self): | |
| props = BEAM_SECTIONS["IPE400"] | |
| L = 6.0 # m | |
| defl_limit_mm = L * 1000 / 300.0 # = 20.0 mm | |
| checks = check_member( | |
| "beam", props, | |
| {"N": 0, "V": 0, "M_max": 0, "delta_max_mm": defl_limit_mm}, | |
| L_m=L, | |
| ) | |
| assert checks.UR_deflection == pytest.approx(1.0, rel=0.01) | |
| def test_columns_have_zero_deflection_ur(self): | |
| props = COLUMN_SECTIONS["HEB240"] | |
| checks = check_member( | |
| "column", props, | |
| {"N": -300e3, "V": 0, "M_max": 0, "delta_max_mm": 50.0}, | |
| L_m=3.5, | |
| ) | |
| assert checks.UR_deflection == 0.0 | |
| class TestWallCheck: | |
| def test_wall_always_passes(self): | |
| checks = check_member( | |
| "wall", {}, | |
| {"N": 0, "V": 0, "M_max": 0, "delta_max_mm": 0}, | |
| L_m=5.0, | |
| ) | |
| assert checks.passes_all | |
| assert checks.max_UR == 0.0 | |
| class TestInteraction: | |
| def test_combined_axial_bending_higher_ur(self): | |
| props = COLUMN_SECTIONS["HEB200"] | |
| A = props["A"] | |
| W_pl = props["W_pl_y"] | |
| # Pure bending at 50% capacity | |
| M_50 = 0.5 * W_pl * F_Y_STEEL | |
| checks_M_only = check_member( | |
| "column", props, | |
| {"N": 0, "V": 0, "M_max": M_50, "delta_max_mm": 0}, | |
| L_m=3.5, | |
| ) | |
| # Add axial load on top | |
| N_Ed = 0.3 * A * F_Y_STEEL | |
| checks_combined = check_member( | |
| "column", props, | |
| {"N": -N_Ed, "V": 0, "M_max": M_50, "delta_max_mm": 0}, | |
| L_m=3.5, | |
| ) | |
| assert checks_combined.UR_interaction >= checks_M_only.UR_interaction | |
| class TestMaxUR: | |
| def test_max_ur_is_maximum_of_all_checks(self): | |
| props = BEAM_SECTIONS["IPE200"] | |
| L = 3.0 | |
| M_80pct = 0.80 * props["W_el_y"] * F_Y_STEEL | |
| defl_50pct = L * 1000 / 300.0 * 0.5 | |
| checks = check_member( | |
| "beam", props, | |
| {"N": 0, "V": 0, "M_max": M_80pct, "delta_max_mm": defl_50pct}, | |
| L_m=L, | |
| ) | |
| expected_max = max( | |
| checks.UR_bending, | |
| checks.UR_shear, | |
| checks.UR_buckling, | |
| checks.UR_interaction, | |
| checks.UR_deflection, | |
| ) | |
| assert checks.max_UR == pytest.approx(expected_max, rel=0.001) | |