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.
209 lines
8.4 KiB
Python
209 lines
8.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Prestress, transfer length, and what a blockout does to both.
|
|
|
|
The numbers in `test_matches_a_commercial_check` are from an independent
|
|
run of the same piece through Eriksson Wall v5.2.0. That program was not
|
|
consulted while this was written -- its inputs were read off its own file and
|
|
its outputs off its own report -- so agreement to the last printed digit is
|
|
worth having, and disagreement is worth stopping for.
|
|
|
|
The piece: 38'-7.5" x 10'-7.5" x 8", one opening 111.25 x 40.25 set 16 in.
|
|
from one edge, twelve 1/2 in. 270k strands in two rows.
|
|
"""
|
|
import pytest
|
|
|
|
from panellift import checks, prestress, section
|
|
from panellift.model import Blockout, Panel, Prestress, Strand
|
|
|
|
|
|
PULL = 0.153 * 270_000 * 0.75 # 75% pull on a 1/2 in. 270k strand
|
|
POSITIONS_IN = [3.5, 16.5, 40.5, 59.5, 87.5, 109.5]
|
|
ROWS_IN = [1.75, 6.25]
|
|
|
|
|
|
def sample_panel(loss_pct=1.58, blockout=True):
|
|
strands = [Strand(y=y / 12.0, z_in=z, area_in2=0.153,
|
|
initial_pull_lb=PULL)
|
|
for z in ROWS_IN for y in POSITIONS_IN]
|
|
blockouts = []
|
|
if blockout:
|
|
blockouts.append(Blockout(name='OPENING 1', x0=330.0 / 12.0,
|
|
y0=16.0 / 12.0, length=111.25 / 12.0,
|
|
width=40.25 / 12.0))
|
|
return Panel(
|
|
mark='sample', length_ft=463.5 / 12.0, width_ft=127.5 / 12.0,
|
|
thickness_in=8.0, fc_psi=6000.0, fci_psi=3500.0,
|
|
blockouts=blockouts,
|
|
prestress=Prestress(strands=strands, loss_pct=loss_pct,
|
|
diameter_in=0.5))
|
|
|
|
|
|
# ------------------------------------------------------------------ transfer
|
|
|
|
def test_transfer_length_follows_the_effective_stress():
|
|
panel = sample_panel()
|
|
# f_se = 0.75 * 270 * (1 - 0.0158) = 199.3 ksi; lt = f_se * db / 3
|
|
assert prestress.transfer_length_in(panel.prestress) == \
|
|
pytest.approx(33.2, abs=0.1)
|
|
|
|
|
|
def test_a_strand_picks_its_force_up_over_the_transfer_length():
|
|
panel = sample_panel()
|
|
lt = prestress.transfer_length_in(panel.prestress) / 12.0
|
|
strand = panel.prestress.strands[0] # y = 3.5 in, never cut
|
|
assert prestress.developed(strand, panel, 0.0, lt) == 0.0
|
|
assert prestress.developed(strand, panel, lt / 2, lt) == pytest.approx(0.5)
|
|
assert prestress.developed(strand, panel, lt, lt) == pytest.approx(1.0)
|
|
assert prestress.developed(strand, panel, 10.0, lt) == pytest.approx(1.0)
|
|
|
|
|
|
def test_the_far_end_is_a_free_end_too():
|
|
panel = sample_panel()
|
|
lt = prestress.transfer_length_in(panel.prestress) / 12.0
|
|
strand = panel.prestress.strands[0]
|
|
assert prestress.developed(strand, panel, panel.length_ft, lt) == 0.0
|
|
assert prestress.developed(
|
|
strand, panel, panel.length_ft - lt / 2, lt) == pytest.approx(0.5)
|
|
|
|
|
|
# ------------------------------------------------------- blockouts cut strands
|
|
|
|
def test_a_blockout_cuts_only_the_strands_it_passes_over():
|
|
panel = sample_panel()
|
|
cut = [s for s in panel.prestress.strands if prestress.cuts_for(s, panel)]
|
|
# the opening spans y 16 -> 56.25 in, which is 16.5 and 40.5
|
|
assert len(cut) == 4
|
|
assert sorted(set(round(s.y * 12.0, 2) for s in cut)) == [16.5, 40.5]
|
|
|
|
|
|
def test_area_inside_the_opening_is_the_uncut_strands_only():
|
|
panel = sample_panel()
|
|
state = prestress.state_at(panel, 28.97) # mid-opening
|
|
assert state.area_in2 == pytest.approx(1.22, abs=0.01)
|
|
|
|
|
|
def test_area_ramps_down_approaching_the_cut():
|
|
"""Eriksson reports 1.84 / 1.75 / 1.33 / 1.22 at these four stations."""
|
|
panel = sample_panel()
|
|
for x_ft, expected in ((22.62, 1.84), (25.11, 1.75),
|
|
(27.04, 1.33), (27.50, 1.22)):
|
|
state = prestress.state_at(panel, x_ft)
|
|
assert state.area_in2 == pytest.approx(expected, abs=0.01), x_ft
|
|
|
|
|
|
def test_a_strand_the_opening_misses_keeps_its_force_through_it():
|
|
panel = sample_panel()
|
|
lt = prestress.transfer_length_in(panel.prestress) / 12.0
|
|
outer = [s for s in panel.prestress.strands
|
|
if round(s.y * 12.0, 2) == 109.5][0]
|
|
assert prestress.developed(outer, panel, 28.97, lt) == pytest.approx(1.0)
|
|
|
|
|
|
# ------------------------------------------------------------- net section
|
|
|
|
def test_an_off_centre_opening_moves_the_centroid():
|
|
panel = sample_panel()
|
|
net = section.NetSection(panel, 28.97)
|
|
assert net.area_in2 == pytest.approx(698.0, abs=1.0)
|
|
assert net.cy_in == pytest.approx(76.49, abs=0.05)
|
|
assert net.sz_in3 == pytest.approx(930.7, abs=1.0)
|
|
assert net.iy_in4 == pytest.approx(979_000, rel=0.005)
|
|
|
|
|
|
def test_a_solid_section_is_centred():
|
|
panel = sample_panel(blockout=False)
|
|
net = section.NetSection(panel, 19.0)
|
|
assert net.cy_in == pytest.approx(63.75, abs=0.01)
|
|
assert net.area_in2 == pytest.approx(1020.0, abs=0.5)
|
|
|
|
|
|
# ------------------------------------------------- the number that matters
|
|
|
|
def _corners(panel, x, moment_lbft):
|
|
net = section.NetSection(panel, x)
|
|
return [c[0] for c in checks._corner_stresses(panel, x, moment_lbft, net)]
|
|
|
|
|
|
def _corner_max(panel, x, moment_lbft):
|
|
return max(_corners(panel, x, moment_lbft))
|
|
|
|
|
|
def test_matches_a_commercial_check():
|
|
"""417.6 psi at 28.97 ft, from Eriksson Wall on the same piece.
|
|
|
|
Their factored service moment there is 48.17 k-ft. Feed the same moment
|
|
into the same section and the answer has to be the same, or one of us is
|
|
wrong about the mechanics rather than about the panel.
|
|
"""
|
|
panel = sample_panel(loss_pct=1.58)
|
|
got = _corner_max(panel, 28.97, 48_170.0)
|
|
assert got == pytest.approx(417.6, abs=2.0)
|
|
|
|
|
|
def test_without_prestress_the_same_piece_reads_far_higher():
|
|
"""The bug this whole feature exists to fix, kept as a number."""
|
|
panel = sample_panel()
|
|
bare = Panel(**{**panel.__dict__, 'prestress': None})
|
|
assert _corner_max(bare, 28.97, 48_170.0) == pytest.approx(621.0, abs=2.0)
|
|
|
|
|
|
def test_precompression_wipes_out_a_light_stage():
|
|
"""Our own stripping moment on this piece, which is larger than theirs.
|
|
|
|
23.83 k-ft of bending on the solid section against ~359 psi of
|
|
precompression: 210 psi of it goes, and the piece comes back well under
|
|
the limit instead of at 47% of it.
|
|
"""
|
|
panel = sample_panel()
|
|
assert _corner_max(panel, 10.94, 23_830.0) < 40.0
|
|
|
|
|
|
def test_precompression_and_compression_both_match_at_stripping():
|
|
"""Eriksson's stripping at 0.25L: tension 0.0 psi, compression 667.4.
|
|
|
|
Their moment there is 14.10 k-ft, not ours -- they idealise a static
|
|
lift and this idealises rolling blocks, so the two disagree about the
|
|
reactions before either says anything about stress. Feeding THEIR moment
|
|
in isolates the section from the rigging, and both faces then have to
|
|
agree, which is a stronger check than the tension alone: the tension is
|
|
zero either way once there is enough precompression, but the compression
|
|
is not, and it pins P/A and the eccentricity together.
|
|
"""
|
|
panel = sample_panel(loss_pct=1.58)
|
|
corners = _corners(panel, 9.66, 14_100.0)
|
|
assert max(corners) <= 0.0
|
|
assert -min(corners) == pytest.approx(667.4, abs=2.0)
|
|
|
|
|
|
def test_a_bigger_loss_gives_back_tension():
|
|
"""The loss assumption is a real lever, so it has to move the answer."""
|
|
lean = _corner_max(sample_panel(loss_pct=1.58), 28.97, 48_170.0)
|
|
fat = _corner_max(sample_panel(loss_pct=8.0), 28.97, 48_170.0)
|
|
assert fat > lean + 10.0
|
|
|
|
|
|
# --------------------------------------------------------- sign conventions
|
|
|
|
def _one_row_panel(z_in):
|
|
strands = [Strand(y=y / 12.0, z_in=z_in, initial_pull_lb=PULL)
|
|
for y in [15.0, 45.0, 75.0, 105.0]]
|
|
return Panel(mark='s', length_ft=30.0, width_ft=10.0, thickness_in=8.0,
|
|
fc_psi=6000.0,
|
|
prestress=Prestress(strands=strands, loss_pct=0.0))
|
|
|
|
|
|
def test_a_low_strand_helps_the_face_that_sagging_puts_in_tension():
|
|
"""Sagging puts tension on the form face, z = 0. A strand below mid-depth
|
|
hogs the piece and takes tension off that face; the same strand above
|
|
mid-depth adds to it.
|
|
|
|
Nothing in the commercial comparison can catch this: that piece has two
|
|
symmetric rows, so its eccentricity is exactly zero and the term this
|
|
tests does not exist. It was written wrong the first time and this is
|
|
what found it.
|
|
"""
|
|
low = _corners(_one_row_panel(1.0), 15.0, 20_000.0)[:2] # cz = -t/2
|
|
mid = _corners(_one_row_panel(4.0), 15.0, 20_000.0)[:2]
|
|
high = _corners(_one_row_panel(7.0), 15.0, 20_000.0)[:2]
|
|
assert max(low) < max(mid) < max(high)
|