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,174 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""The search remembers layouts it has already costed, and two dead branches.
|
||||
|
||||
`_peak_psi` is the inner loop of the whole package -- 884 calls on the median
|
||||
job of the reference sweep, 15 ms each, 93% of the run. 17% of those calls ask
|
||||
for a layout the search has already costed: the coarse sweep and the first
|
||||
refinement pass over the same stations, and each of the three refinement sweeps
|
||||
re-tries every line's CURRENT position before deciding it cannot improve on it.
|
||||
|
||||
Memoising is only safe because the function is pure, and it is pure for a
|
||||
reason that has to stay true: everything it reads apart from `xs` lives on the
|
||||
frame, and the frame holds the parts of the problem that DO NOT MOVE when a
|
||||
pick moves -- the load profile, the section, the station list. The first test
|
||||
below is the one that matters; it is what catches somebody later making the
|
||||
frame depend on the positions.
|
||||
"""
|
||||
import dataclasses
|
||||
|
||||
import pytest
|
||||
|
||||
from panellift import layout, section, statics
|
||||
from conftest import LENGTH_FT, aspire_pockets, make_panel
|
||||
|
||||
|
||||
def _frame(panel, stage, rigging, counts=(2, 2)):
|
||||
return layout._make_frame(panel, stage, rigging, list(counts))
|
||||
|
||||
|
||||
# ------------------------------------------------------------- it is pure
|
||||
|
||||
def test_the_frame_does_not_depend_on_where_the_picks_are(solid_panel,
|
||||
stripping, rolling):
|
||||
"""The memo's whole licence. If costing a layout could change the frame,
|
||||
a remembered answer would be an answer to a different question."""
|
||||
frame = _frame(solid_panel, stripping, rolling)
|
||||
before = (list(frame.inv_s), list(frame.jumps), list(frame.counts),
|
||||
list(frame.profile.w), frame.profile.dx)
|
||||
for xs in ([4.0, 28.0], [8.0, 24.0], [2.0, 30.0], [15.0, 16.0]):
|
||||
layout._peak_psi(frame, xs)
|
||||
after = (list(frame.inv_s), list(frame.jumps), list(frame.counts),
|
||||
list(frame.profile.w), frame.profile.dx)
|
||||
assert before == after
|
||||
|
||||
|
||||
def test_a_remembered_answer_is_the_answer(solid_panel, stripping, rolling):
|
||||
"""Bit-identical, not merely close. The search compares improvements at
|
||||
1e-9, so a memo that returned a differently-rounded number could pick a
|
||||
different layout."""
|
||||
frame = _frame(solid_panel, stripping, rolling)
|
||||
first = layout._peak_psi(frame, [6.0, 26.0])
|
||||
again = layout._peak_psi(frame, [6.0, 26.0])
|
||||
assert again == first
|
||||
|
||||
fresh = _frame(solid_panel, stripping, rolling)
|
||||
assert layout._peak_psi(fresh, [6.0, 26.0]) == first
|
||||
|
||||
|
||||
def test_the_memo_actually_hits(solid_panel, stripping, rolling):
|
||||
"""A cache nobody hits is a dict that costs memory and buys nothing."""
|
||||
frame = _frame(solid_panel, stripping, rolling)
|
||||
layout._peak_psi(frame, [6.0, 26.0])
|
||||
assert len(frame.memo) == 1
|
||||
layout._peak_psi(frame, [6.0, 26.0])
|
||||
assert len(frame.memo) == 1 # hit, not a second entry
|
||||
layout._peak_psi(frame, [7.0, 26.0])
|
||||
assert len(frame.memo) == 2
|
||||
|
||||
|
||||
def test_a_real_search_repeats_itself_enough_to_be_worth_it(solid_panel,
|
||||
stripping,
|
||||
rolling):
|
||||
"""Measured 17% repeats on the reference sweep; this pins that the
|
||||
repetition is structural and not a property of one job file."""
|
||||
calls = []
|
||||
real = layout._peak_psi
|
||||
|
||||
def counting(frame, xs):
|
||||
calls.append(tuple(xs))
|
||||
return real(frame, xs)
|
||||
|
||||
layout._peak_psi = counting
|
||||
try:
|
||||
layout.best_stations(solid_panel, stripping, rolling,
|
||||
start=[6.0, 26.0], counts=[2, 2],
|
||||
free=[True, True])
|
||||
finally:
|
||||
layout._peak_psi = real
|
||||
assert len(calls) > len(set(calls))
|
||||
|
||||
|
||||
def test_an_unbuildable_layout_is_remembered_too(solid_panel, stripping,
|
||||
rolling):
|
||||
"""Two lines on one station raise out of group_lifters and cost inf. That
|
||||
is an answer like any other and re-deriving it is the same wasted solve."""
|
||||
frame = _frame(solid_panel, stripping, rolling)
|
||||
value = layout._peak_psi(frame, [10.0, 10.0])
|
||||
assert value == float('inf')
|
||||
assert frame.memo[(10.0, 10.0)] == float('inf')
|
||||
|
||||
|
||||
def test_the_memo_is_bounded(solid_panel, stripping, rolling, monkeypatch):
|
||||
"""A pathological piece must not turn a cache into a memory problem.
|
||||
Overflowing costs a recomputation, never a different answer."""
|
||||
monkeypatch.setattr(layout, '_MEMO_MAX', 2)
|
||||
frame = _frame(solid_panel, stripping, rolling)
|
||||
for x in (4.0, 5.0, 6.0, 7.0, 8.0):
|
||||
layout._peak_psi(frame, [x, 26.0])
|
||||
assert len(frame.memo) == 2
|
||||
# and the answers are still right past the cap
|
||||
assert layout._peak_psi(frame, [8.0, 26.0]) == \
|
||||
layout._peak_psi(_frame(solid_panel, stripping, rolling), [8.0, 26.0])
|
||||
|
||||
|
||||
# --------------------------------------------- the search answer is unchanged
|
||||
|
||||
@pytest.mark.parametrize('with_pockets', [False, True])
|
||||
def test_the_recommendation_is_what_it_was_without_the_memo(
|
||||
with_pockets, stripping, rolling, monkeypatch):
|
||||
"""The memo must be invisible in the answer. Run the same search with the
|
||||
cache disabled (cap 0) and demand the same stations to the last bit."""
|
||||
panel = make_panel(blockouts=aspire_pockets() if with_pockets else None)
|
||||
|
||||
def search():
|
||||
return layout.best_stations(panel, stripping, rolling,
|
||||
start=[6.0, 26.0], counts=[2, 2],
|
||||
free=[True, True])
|
||||
|
||||
monkeypatch.setattr(layout, '_MEMO_MAX', 0)
|
||||
xs_off, best_off = search()
|
||||
monkeypatch.setattr(layout, '_MEMO_MAX', 200000)
|
||||
xs_on, best_on = search()
|
||||
|
||||
assert xs_on == xs_off
|
||||
assert best_on == best_off
|
||||
|
||||
|
||||
# ------------------------------------------------- the branch that was deleted
|
||||
|
||||
def test_the_section_is_solved_on_exactly_the_diagram_stations(solid_panel,
|
||||
stripping,
|
||||
rolling):
|
||||
"""`_peak_psi` used to carry a guard re-solving the section when the
|
||||
station list "changed under us". It could not fire: `_make_frame` builds
|
||||
the station list out of the same profile and the same discontinuities that
|
||||
`diagram()` does, from the same expression. The guard is gone; this is
|
||||
what it was guarding, asserted where it can actually fail.
|
||||
|
||||
A dead branch in the hottest loop in the package is a branch nobody can
|
||||
test, and the comment on it claimed a thing that could not happen.
|
||||
"""
|
||||
for panel in (solid_panel, make_panel(blockouts=aspire_pockets())):
|
||||
frame = _frame(panel, stripping, rolling)
|
||||
for xs in ([6.0, 26.0], [4.0, 28.0], [10.0, 22.0]):
|
||||
lifters = []
|
||||
for i, (x, n) in enumerate(zip(xs, frame.counts)):
|
||||
for j in range(n):
|
||||
lifters.append(layout.Lifter(
|
||||
mark='r{0}.{1}'.format(i, j), family='?',
|
||||
x=x, y=0.0, role='strip'))
|
||||
staged = dataclasses.replace(panel, lifters=lifters)
|
||||
rig = statics.group_lifters(staged, rolling, role='strip')
|
||||
statics.solve_rolling(rig, frame.profile)
|
||||
dia = statics.diagram(rig, frame.profile,
|
||||
extra_stations=frame.jumps)
|
||||
assert len(dia.xs) == len(frame.inv_s)
|
||||
|
||||
|
||||
def test_the_frame_carries_no_spare_lifters():
|
||||
"""`_Frame._spares` claimed a reuse optimisation that did not exist --
|
||||
nothing ever read it or wrote it. A field whose comment describes code
|
||||
that is not there is worse than no field."""
|
||||
names = [f.name for f in dataclasses.fields(layout._Frame)]
|
||||
assert '_spares' not in names
|
||||
assert 'memo' in names
|
||||
Reference in New Issue
Block a user