WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s
ci / test (push) Successful in 6m18s
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.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# -*- 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))
|
||||
Reference in New Issue
Block a user