Files
willtools-demo/panellift/tests/test_pci_fig832.py
T
will 8e6ee31596
ci / test (push) Successful in 6m18s
WillTools 2026-09-09 -- clean snapshot of the development repo for review
History-free snapshot of the private development repository at cbe45c5
(2026-09-09). Code, docs and public catalogue only; no plant or job
knowledge layers, no client or job names (tools/scrub.py clean). The
intro deck is left out of this snapshot because its slide images could
not be checked for job drawings.
2026-09-09 08:32:15 -05:00

111 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
"""The beam solver must reproduce PCI Figure 8.3.2 from first principles.
This is the test the whole design rests on. Figure 8.3.2's coefficients are
not a separate method -- they are the closed-form solution of a uniformly
loaded beam with two overhangs, with the supports placed where the positive
and negative moments come out equal. If our solver cannot land on 0.0214 with
the supports there, it cannot be trusted with the supports somewhere else,
which is the only case anyone actually needs it for.
"""
import math
from panellift import pci, section, statics
from panellift.model import Stage
from conftest import ASPIRE, LENGTH_FT, WIDTH_FT
def test_optimal_support_fraction_is_the_root_of_the_quadratic():
a = pci.OPTIMAL_TWO_LINE_FRACTION
assert abs(4 * a ** 2 + 4 * a - 1) < 1e-12
assert abs(a - 0.20710678) < 1e-7
def test_closed_form_coefficient_is_pcis_0_0214():
"""M = w*(aL)^2/2 with a = (sqrt(2)-1)/2 gives 0.02145, printed as 0.0214."""
a = pci.OPTIMAL_TWO_LINE_FRACTION
coefficient = a ** 2 / 2.0
assert abs(coefficient - 0.0214) < 0.0001
def test_solver_reproduces_my_coefficient(solid_panel, rolling):
"""The numerical diagram must match 0.0214 w a b^2 to within 0.5%."""
stage = Stage(name='unfactored', multiplier=1.0, fc_psi=4000.0)
result = statics.analyse(solid_panel, stage, rolling,
reconcile_weight=False)
w_psf = section.unit_weight_psf(solid_panel)
_, expected = pci.fig_832_four_point(w_psf, WIDTH_FT, LENGTH_FT)
_, m_pos = result.diagram.max_positive
_, m_neg = result.diagram.max_negative
assert abs(m_pos - expected) / expected < 0.005
assert abs(abs(m_neg) - expected) / expected < 0.005
def test_positive_and_negative_moments_are_equal_at_the_optimum(solid_panel,
rolling):
"""That equality is the definition of the Figure 8.3.2 layout."""
stage = Stage(name='unfactored', multiplier=1.0, fc_psi=4000.0)
result = statics.analyse(solid_panel, stage, rolling,
reconcile_weight=False)
_, m_pos = result.diagram.max_positive
_, m_neg = result.diagram.max_negative
assert abs(m_pos - abs(m_neg)) / m_pos < 0.01
def test_unit_weight_matches_the_article(solid_panel):
assert abs(section.unit_weight_psf(solid_panel) -
ASPIRE['w_psf']) < 0.1
def _peak_abs_moment(result):
"""The governing moment, whichever sign it has.
Bunching the picks inboard turns the panel into two long cantilevers, and
then the whole interior hogs -- the largest positive moment is zero, at
the ends. Comparing only sagging would read that as an improvement.
"""
_, positive = result.diagram.max_positive
_, negative = result.diagram.max_negative
return max(abs(positive), abs(negative))
def test_moving_the_supports_inboard_raises_the_governing_moment(solid_panel,
rolling):
"""A sanity check that the solver is solving, not returning a constant."""
stage = Stage(name='unfactored', multiplier=1.0, fc_psi=4000.0)
optimum = _peak_abs_moment(
statics.analyse(solid_panel, stage, rolling, reconcile_weight=False))
for lifter in solid_panel.lifters:
lifter.x = LENGTH_FT / 2.0 + math.copysign(
4.0, lifter.x - LENGTH_FT / 2.0)
moved = _peak_abs_moment(
statics.analyse(solid_panel, stage, rolling, reconcile_weight=False))
assert moved > optimum * 1.5
def test_the_optimum_layout_really_is_the_optimum(solid_panel, rolling):
"""No other support position beats (sqrt(2)-1)/2. Worth proving once.
This is what makes the PCI layout a benchmark the report can measure a
detailer's actual layout against, rather than just another arrangement.
"""
stage = Stage(name='unfactored', multiplier=1.0, fc_psi=4000.0)
best = _peak_abs_moment(
statics.analyse(solid_panel, stage, rolling, reconcile_weight=False))
for fraction in (0.10, 0.15, 0.18, 0.23, 0.26, 0.30):
left = fraction * LENGTH_FT
for lifter in solid_panel.lifters:
lifter.x = left if lifter.x < LENGTH_FT / 2.0 else LENGTH_FT - left
trial = _peak_abs_moment(
statics.analyse(solid_panel, stage, rolling,
reconcile_weight=False))
assert trial >= best * 0.999, (
'fraction {0} beat the supposed optimum'.format(fraction))