# -*- 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))