# -*- coding: utf-8 -*- """The insulated-sandwich SECTION path in `section.py` / `capacity.py`. Every expected number here is arithmetic done by hand, written out beside it, and -- where a golden figure exists -- pinned to the value Eriksson Wall's own section table prints, NOT to the code's own expression. A pin that recomputes both sides agrees with any refactor, including a wrong one. THE GOLDEN PIECE is WPB3.0 Run 1 (docs/eriksson-wall-algorithms.md sec.12 and the extracted `WPB3.0.md`): a horizontal insulated wall panel, layup 3 in. concrete + 4 in. insulation + 4 in. concrete = 11 in. envelope at a clear width of 24 ft = 288 in (no opening at the checked station). Eriksson reports, for this section (p.11 of that packet): Area 2016.00 in2 Cy 5.79 in Izz 29955.43 in4 Sb 5177.48 St 5744.88 which is the FULLY-COMPOSITE transformed section of the two concrete wythes about their common centroid, with the insulation EXCLUDED. That is what `section.NetSection` must reproduce for a sandwich, and it is what closes the "treated as a solid 11 in prism" defect: the solid idealisation would give Izz = 288*11^3/12 = 31944 and S = 288*11^2/6 = 5808, both larger than the real section (unconservative), and its shear b*d would be 288*0.8*11, a third too large -- see `test_shear_bd_drops_by_the_36_percent_the_parity_run_found`. The task framed Eriksson's treatment as NON-composite; the golden figures do not bear that out (Area 2016 is BOTH wythes, and Izz 29955 is their composite inertia, not one wythe's), so this implements and pins the composite section Eriksson actually reports. The %composite knock-down Eriksson then applies (95% for stress/strength, 35% for in-place deflection) and the strand development layer are out of scope here; the more conservative non-composite / structural-wythe-alone bounds live in `sandwich.py`. """ import math import pytest from panellift import capacity, section from panellift.model import Panel, Stage ENVELOPE = 11.0 WIDTH_FT = 24.0 B_IN = WIDTH_FT * 12.0 # 288 in def wpb3_source(): """`source` as the exporter writes it for the WPB3.0 layup. 3 in wythe at the form face (z 0-3), 4 in insulation (z 3-7), 4 in wythe (z 7-11). The order does not change Area / Izz / the governing S; it only labels which face is Sb and which is St. """ return { 'solid_parts': [ {'counted': True, 'z0_in': 0.0, 'z1_in': 3.0, 'material': 'WYTHE 3IN', 'density_pcf': 150.0, 'volume_ft3': 1.0}, {'counted': False, 'z0_in': 3.0, 'z1_in': 7.0, 'material': 'INSULATION', 'density_pcf': 2.0, 'volume_ft3': 1.0}, {'counted': True, 'z0_in': 7.0, 'z1_in': 11.0, 'material': 'WYTHE 4IN', 'density_pcf': 150.0, 'volume_ft3': 1.0}, ], 'envelope_thickness_in': ENVELOPE, } def wpb3_panel(): return Panel(mark='WPB3', length_ft=24.0, width_ft=WIDTH_FT, thickness_in=ENVELOPE, unit_weight_pcf=150.0, fci_psi=3500.0, fc_psi=6000.0, source=wpb3_source()) def _mid_net(panel): return section.NetSection(panel, panel.length_ft / 2.0) # ------------------------------------------------ the section, vs the golden def test_area_is_both_wythes_with_the_insulation_excluded(): # A = b * (t1 + t2) = 288 * (3 + 4) = 2016.00 in2, NOT 288*11 = 3168. net = _mid_net(wpb3_panel()) assert net.is_sandwich is True assert net.area_in2 == pytest.approx(2016.0, abs=1e-6) def test_centroid_is_the_composite_neutral_axis(): # zbar = (A1*z1 + A2*z2)/A, wythes at z=1.5 and z=9: # = (3*1.5 + 4*9)/7 = (4.5 + 36)/7 = 5.785714 in -> Eriksson Cy 5.79 net = _mid_net(wpb3_panel()) assert net.zbar_in == pytest.approx(5.7857143, abs=1e-6) def test_through_thickness_inertia_matches_the_reported_izz(): # Iz = b*[ t1^3/12 + t1*(z1 - zbar)^2 + t2^3/12 + t2*(z2 - zbar)^2 ] # = 288*[ 3^3/12 + 3*(1.5-5.785714)^2 + 4^3/12 + 4*(9-5.785714)^2 ] # = 288*[ 2.25 + 55.1020 + 5.3333 + 41.3265 ] = 288*104.0119 = 29955.4 net = _mid_net(wpb3_panel()) assert net.iz_in4 == pytest.approx(29955.43, abs=0.5) # reported 29955.43 def test_the_two_faces_have_the_unequal_moduli_eriksson_prints(): # c_form = 5.785714 (to z=0), c_far = 11 - 5.785714 = 5.214286 (to z=11) # Sb = 29955.43 / 5.785714 = 5177.5 (Eriksson 5177.48) # St = 29955.43 / 5.214286 = 5744.9 (Eriksson 5744.88) net = _mid_net(wpb3_panel()) assert net.c_form_in == pytest.approx(5.7857143, abs=1e-6) assert net.c_far_in == pytest.approx(5.2142857, abs=1e-6) sb = net.iz_in4 / net.c_form_in st = net.iz_in4 / net.c_far_in assert sb == pytest.approx(5177.48, abs=1.0) assert st == pytest.approx(5744.88, abs=1.0) # sz_in3 is the GOVERNING (smaller) modulus, the one that decides tension. assert net.sz_in3 == pytest.approx(min(sb, st), abs=1e-6) def test_the_solid_idealisation_would_have_overstated_this_section(): """The number the refusal was built on: the solid 11 in prism is bigger than the real composite section, both in I and S -- unconservative.""" net = _mid_net(wpb3_panel()) solid_iz = B_IN * ENVELOPE ** 3 / 12.0 # 31944.0 solid_sz = B_IN * ENVELOPE ** 2 / 6.0 # 5808.0 assert solid_iz > net.iz_in4 # 31944 > 29955 assert solid_sz > net.sz_in3 # 5808 > 5177 # --------------------------------------------------------------- shear b*d def test_shear_bd_drops_by_the_36_percent_the_parity_run_found(): """docs sec.3.1: for a sandwich d_v = 0.8*th uses the CONCRETE depth (the summed wythes), not the envelope. Here that is 0.8*7 vs 0.8*11, so the shear area is 7/11 of the solid one -- a 36.4% drop, which is exactly the WPB3.0 shear residual the harness traced to b*d not excluding insulation. """ net = _mid_net(wpb3_panel()) assert net.shear_thickness_in == pytest.approx(7.0) # concrete, not 11 stage = Stage(name='strip', multiplier=1.4, fc_psi=3500.0) pt = capacity.detailed_shear_at(wpb3_panel(), stage, x=12.0, moment_lbft=5000.0, shear_lb=8000.0) solid_bd = net.width_in * capacity.pci.DV_OVER_H_SHEAR * ENVELOPE assert pt.bd_in2 == pytest.approx(solid_bd * 7.0 / 11.0, rel=1e-9) drop = 1.0 - pt.bd_in2 / solid_bd assert drop == pytest.approx(4.0 / 11.0, abs=1e-6) # 0.3636... # --------------------------------------- contiguous two-layer -> merged solid def test_a_contiguous_two_concrete_body_is_the_merged_solid(): """An architectural face cast monolithically on a structural wythe (no insulation gap) is not a hole in the middle -- its composite section is simply the solid over the whole concrete span, and that is what comes out. 4 in (z0-4) + 12 in (z4-16) contiguous -> a solid 16 in section.""" src = {'solid_parts': [ {'counted': True, 'z0_in': 4.0, 'z1_in': 16.0, 'material': 'STRUCT', 'density_pcf': 150.0, 'volume_ft3': 1.0}, {'counted': True, 'z0_in': 0.0, 'z1_in': 4.0, 'material': 'ARCH', 'density_pcf': 150.0, 'volume_ft3': 1.0}], 'envelope_thickness_in': 16.0} panel = Panel(mark='C', length_ft=20.0, width_ft=5.0, thickness_in=12.0, source=src) net = section.NetSection(panel, 10.0) b = 5.0 * 12.0 assert net.area_in2 == pytest.approx(b * 16.0) # all 16 in concrete assert net.zbar_in == pytest.approx(8.0) # mid-depth: solid assert net.iz_in4 == pytest.approx(b * 16.0 ** 3 / 12.0) assert net.shear_thickness_in == pytest.approx(16.0) # --------------------------------------------- the solid path does not move def test_a_solid_panel_is_untouched(): """No source -> not a sandwich -> the familiar b*t, b*t^2/6, b*t^3/12, d_v = 0.8*t, centroid t/2. Byte-for-byte what it always was.""" panel = Panel(mark='S', length_ft=20.0, width_ft=5.0, thickness_in=8.0) net = section.NetSection(panel, 10.0) b = 5.0 * 12.0 assert net.is_sandwich is False assert net.area_in2 == pytest.approx(b * 8.0) assert net.zbar_in == pytest.approx(4.0) assert net.c_form_in == pytest.approx(4.0) assert net.c_far_in == pytest.approx(4.0) assert net.iz_in4 == pytest.approx(b * 8.0 ** 3 / 12.0) assert net.sz_in3 == pytest.approx(b * 8.0 ** 2 / 6.0) assert net.shear_thickness_in == pytest.approx(8.0) def test_the_free_functions_follow_the_same_section(): """`section.area_in2` / `section_modulus_in3` / `inertia_in4` are what `statics` (EI) and `layout` read; they must be the sandwich section too.""" panel = wpb3_panel() x = 12.0 net = section.NetSection(panel, x) assert section.area_in2(panel, x) == pytest.approx(net.area_in2, abs=1e-6) assert section.inertia_in4(panel, x) == pytest.approx(net.iz_in4, abs=1e-6) assert section.section_modulus_in3(panel, x) == pytest.approx(net.sz_in3, abs=1e-6)