25592fa6c2
ci / test (push) Successful in 6m14s
History-free snapshot of the private development repository at 77a82eb. Code, docs and the public catalogue only: no plant or job knowledge layers, no client, job or producer names (tools/scrub.py clean against the widened term list; a second word-level grep clean). The intro deck is left out because its slide images cannot be checked for job drawings.
154 lines
6.7 KiB
Python
154 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""The PCI Design Aid 15.2.3 strand power curve and the strain-compatibility
|
|
flexure it feeds -- docs/eriksson-wall-algorithms.md sec.4.2.
|
|
|
|
ADDITIVE. `pci.fps_approximate_ksi` (the ACI approximate f_ps that the shared
|
|
`capacity_at`/`phi_mn_lbft` and Panel Lift Check keep) is untouched; these pin
|
|
the more exact law the reference tool (Eriksson Wall) uses and that the additive
|
|
`handling_flexure_at` (Panel Analysis) now prefers.
|
|
"""
|
|
import pytest
|
|
|
|
from panellift import capacity, pci
|
|
from panellift.model import Panel, Prestress, Stage, Strand
|
|
|
|
|
|
# ------------------------------------------------------- the pure power curve
|
|
|
|
def test_fps_power_curve_elastic_branch():
|
|
"""eps_ps <= 0.0085 is elastic: f_ps = Ep*eps_ps.
|
|
|
|
0.005 * 28500 = 142.5 ksi. At the limit itself, 0.0085 * 28500 = 242.25.
|
|
"""
|
|
assert pci.fps_power_curve_ksi(0.005, 270.0) == pytest.approx(142.5)
|
|
assert pci.fps_power_curve_ksi(0.0085, 270.0) == pytest.approx(242.25)
|
|
|
|
|
|
def test_fps_power_curve_power_branch():
|
|
"""eps_ps > 0.0085: f_ps = f_pu - 0.04/(eps_ps - 0.007).
|
|
|
|
At 0.010: 270 - 0.04/(0.010 - 0.007) = 270 - 13.3333 = 256.6667 ksi.
|
|
At 0.020: 270 - 0.04/0.013 = 270 - 3.0769 = 266.9231 ksi.
|
|
"""
|
|
assert pci.fps_power_curve_ksi(0.010, 270.0) == pytest.approx(256.6667,
|
|
abs=1e-3)
|
|
assert pci.fps_power_curve_ksi(0.020, 270.0) == pytest.approx(266.9231,
|
|
abs=1e-3)
|
|
|
|
|
|
def test_fps_power_curve_is_nearly_continuous_at_the_knee():
|
|
"""The two branches meet within ~1 ksi at eps_ps = 0.0085 -- one smooth
|
|
law, not a step. Elastic 242.25; power 270 - 0.04/0.0015 = 243.33."""
|
|
elastic = pci.fps_power_curve_ksi(0.0085, 270.0)
|
|
power = 270.0 - 0.04 / (0.0085 - 0.007)
|
|
assert abs(power - elastic) < 1.1
|
|
|
|
|
|
def test_fps_power_curve_caps_at_fpu_and_floors_at_zero():
|
|
# The power branch approaches f_pu from below but never reaches it: at
|
|
# eps = 1.0, 270 - 0.04/0.993 = 269.96 ksi, under f_pu.
|
|
assert pci.fps_power_curve_ksi(1.0, 270.0) == pytest.approx(269.96, abs=0.01)
|
|
assert pci.fps_power_curve_ksi(1.0, 270.0) < 270.0
|
|
# The cap DOES bind if the elastic branch is pushed past f_pu (an out-of-
|
|
# range Ep): 0.005 * 100000 = 500 ksi -> capped to f_pu = 270.
|
|
assert pci.fps_power_curve_ksi(0.005, 270.0, ep_ksi=100000.0) \
|
|
== pytest.approx(270.0)
|
|
# Non-positive strain is no stress, not a negative one.
|
|
assert pci.fps_power_curve_ksi(0.0, 270.0) == 0.0
|
|
assert pci.fps_power_curve_ksi(-0.001, 270.0) == 0.0
|
|
|
|
|
|
# --------------------------------------- strain-compat Mn on a solid panel
|
|
|
|
def _solid_prestressed_panel():
|
|
"""A plain solid panel, one bottom strand row, no openings -- so the
|
|
power-curve solve has a single fully-developed tension layer and can be
|
|
reproduced by an independent bisection in the test."""
|
|
strands = [Strand(y=2.0, z_in=1.5, area_in2=0.153, initial_pull_lb=28900.0),
|
|
Strand(y=6.0, z_in=1.5, area_in2=0.153, initial_pull_lb=28900.0)]
|
|
pre = Prestress(strands=strands, loss_pct=0.0, diameter_in=0.5,
|
|
fpu_ksi=270.0)
|
|
return Panel(mark='S', length_ft=24.0, width_ft=8.0, thickness_in=8.0,
|
|
unit_weight_pcf=150.0, fci_psi=3500.0, fc_psi=6000.0,
|
|
prestress=pre)
|
|
|
|
|
|
def test_power_curve_mn_matches_an_independent_strain_compat_solve():
|
|
"""`capacity_at` stores a power-curve M_n; reproduce it from the same law.
|
|
|
|
Midspan, fully developed: every strand sees the full power-curve f_ps at
|
|
its own strain, and equilibrium 0.85 f'ci beta1 c b = sum(A f_ps) fixes c.
|
|
The test re-solves that balance independently and checks the stored M_n.
|
|
"""
|
|
panel = _solid_prestressed_panel()
|
|
stage = Stage(name='strip', multiplier=1.4, fc_psi=3500.0)
|
|
x = 12.0
|
|
cap = capacity.capacity_at(panel, stage, x, moment_lbft=1.0, shear_lb=0.0)
|
|
assert cap.mn_powercurve_lbft is not None
|
|
|
|
# Independent solve, same inputs: two identical strands at d = 8 - 1.5 =
|
|
# 6.5 in, f_se = 28900/0.153/1000 = 188.9 ksi, eps_se = f_se/28500.
|
|
import math
|
|
fc = 3500.0
|
|
b = panel.thickness_in and (panel.width_ft * 12.0) # solid, full width
|
|
beta1 = pci.beta1(fc)
|
|
ep = pci.STRAND_ELASTIC_MODULUS_KSI
|
|
d = 8.0 - 1.5
|
|
area = 0.153 * 2
|
|
fse = 28900.0 / 0.153 / 1000.0
|
|
eps_se = fse / ep
|
|
|
|
def tension(c):
|
|
eps = eps_se + 0.003 * (d - c) / c
|
|
return area * pci.fps_power_curve_ksi(eps, 270.0, ep) * 1000.0
|
|
|
|
lo, hi = 1e-4, d
|
|
for _ in range(80):
|
|
mid = 0.5 * (lo + hi)
|
|
if 0.85 * fc * beta1 * mid * b - tension(mid) > 0.0:
|
|
hi = mid
|
|
else:
|
|
lo = mid
|
|
c = 0.5 * (lo + hi)
|
|
a = beta1 * c
|
|
mn = tension(c) * (d - a / 2.0) / 12.0
|
|
assert cap.mn_powercurve_lbft == pytest.approx(mn, rel=1e-3)
|
|
|
|
|
|
def test_handling_flexure_prefers_the_power_curve_mn():
|
|
"""handling_flexure_at reads the power-curve M_n, not the ACI-approximate
|
|
one, when the point carries it -- and applies the strain-based phi to it."""
|
|
panel = _solid_prestressed_panel()
|
|
stage = Stage(name='strip', multiplier=1.4, fc_psi=3500.0)
|
|
x = 12.0
|
|
cap = capacity.capacity_at(panel, stage, x, moment_lbft=4000.0, shear_lb=0.0)
|
|
phi, phi_mn, ratio = capacity.handling_flexure_at(cap, 4000.0)
|
|
# Fully developed, low rho -> tension-controlled -> phi 0.90, and phi*M_n
|
|
# is 0.90 times the stored power-curve M_n, NOT the approximate phi_mn_lbft.
|
|
assert phi == pytest.approx(0.90)
|
|
assert phi_mn == pytest.approx(0.90 * cap.mn_powercurve_lbft)
|
|
assert phi_mn != pytest.approx(cap.phi_mn_lbft) # differs from approximate
|
|
assert ratio == pytest.approx(4000.0 / phi_mn)
|
|
|
|
|
|
def test_power_curve_mn_is_none_without_developed_strand():
|
|
"""A free end has no developed area, so no power-curve M_n is stored."""
|
|
panel = _solid_prestressed_panel()
|
|
stage = Stage(name='strip', multiplier=1.4, fc_psi=3500.0)
|
|
cap = capacity.capacity_at(panel, stage, x=0.0, moment_lbft=1.0,
|
|
shear_lb=0.0)
|
|
assert cap.mn_powercurve_lbft is None
|
|
|
|
|
|
def test_power_curve_free_end_flexure_is_not_a_full_phi_mn():
|
|
"""The development cap keeps a station near a free end from claiming the
|
|
full midspan phi*M_n: at x just inside the transfer length the strand has
|
|
only begun to develop, so phi*M_n there is well below the midspan value."""
|
|
panel = _solid_prestressed_panel()
|
|
stage = Stage(name='strip', multiplier=1.4, fc_psi=3500.0)
|
|
mid = capacity.capacity_at(panel, stage, 12.0, 1.0, 0.0)
|
|
near = capacity.capacity_at(panel, stage, 0.5, 1.0, 0.0)
|
|
assert mid.mn_powercurve_lbft is not None
|
|
if near.mn_powercurve_lbft is not None:
|
|
assert near.mn_powercurve_lbft < mid.mn_powercurve_lbft
|