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