volt: Q77 -- segment-tree cells collected (beam discover 159->174, rc 0), out-of-range raises IndexError instead of clamping, empty-tree phantom red-celled, power-of-two padding asserted over 205,693 internal nodes
This commit is contained in:
1 parent
7578da3034
commit
e5289a4d51
3 files changed
+318
-27
No files matched your search
@@ -1,12 +1,25 @@
|
|||||||
"""Segment tree with lazy propagation: range add, range sum.
|
"""Segment tree with lazy propagation: range add, range sum.
|
||||||
|
|
||||||
Iterative-friendly recursive implementation. 1-indexed internally.
|
Recursive implementation over a power-of-two padded array. The padding is load
|
||||||
Supports: build, point query (via range sum length 1), range add, range sum.
|
bearing: every internal node then spans an even length and splits into two
|
||||||
|
equal halves, which is exactly what ``_push``'s child-length arithmetic
|
||||||
|
(``mid - lo + 1`` for the left child, ``hi - mid`` for the right) relies on.
|
||||||
|
|
||||||
|
Supports: build, point query (via range sum of length 1), range add, range sum.
|
||||||
|
|
||||||
|
Bounds contract: indices are 0-indexed and inclusive. Any endpoint outside
|
||||||
|
``[0, len(data) - 1]`` raises ``IndexError`` -- nothing is clamped, so a query
|
||||||
|
can never reach a padded cell that holds no data. An empty tree therefore
|
||||||
|
accepts no index at all: there is no element to point at. A reversed range
|
||||||
|
(``l > r``) is empty: it contributes 0 and is not bounds-checked.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class SegmentTree:
|
class SegmentTree:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.n = len(data)
|
self.n = len(data)
|
||||||
|
# pad to a power of two: every internal node then spans an even length
|
||||||
|
# and splits equally, so _push's two child-length expressions agree.
|
||||||
self.size = 1
|
self.size = 1
|
||||||
while self.size < max(1, self.n):
|
while self.size < max(1, self.n):
|
||||||
self.size <<= 1
|
self.size <<= 1
|
||||||
@@ -18,11 +31,24 @@ class SegmentTree:
|
|||||||
for i in range(self.size - 1, 0, -1):
|
for i in range(self.size - 1, 0, -1):
|
||||||
self.tree[i] = self.tree[2 * i] + self.tree[2 * i + 1]
|
self.tree[i] = self.tree[2 * i] + self.tree[2 * i + 1]
|
||||||
|
|
||||||
|
def _span_ok(self, l, r):
|
||||||
|
"""Bounds contract for a [l, r] operation. False means empty range."""
|
||||||
|
if l > r:
|
||||||
|
return False
|
||||||
|
if l < 0 or r >= self.n:
|
||||||
|
raise IndexError(
|
||||||
|
"range [%d, %d] out of bounds for %d element(s) "
|
||||||
|
"(valid indices 0..%d)" % (l, r, self.n, self.n - 1)
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
def _push(self, node, lo, hi):
|
def _push(self, node, lo, hi):
|
||||||
if self.lazy[node] != 0:
|
if self.lazy[node] != 0:
|
||||||
mid = (lo + hi) // 2
|
mid = (lo + hi) // 2
|
||||||
left = 2 * node
|
left = 2 * node
|
||||||
right = 2 * node + 1
|
right = 2 * node + 1
|
||||||
|
# sizes: left covers [lo, mid] = mid - lo + 1, right covers
|
||||||
|
# [mid + 1, hi] = hi - mid; equal because spans are even.
|
||||||
self.tree[left] += self.lazy[node] * (mid - lo + 1)
|
self.tree[left] += self.lazy[node] * (mid - lo + 1)
|
||||||
self.lazy[left] += self.lazy[node]
|
self.lazy[left] += self.lazy[node]
|
||||||
self.tree[right] += self.lazy[node] * (hi - mid)
|
self.tree[right] += self.lazy[node] * (hi - mid)
|
||||||
@@ -43,8 +69,8 @@ class SegmentTree:
|
|||||||
self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]
|
self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]
|
||||||
|
|
||||||
def range_add(self, l, r, val):
|
def range_add(self, l, r, val):
|
||||||
"""Add val to [l, r] inclusive (0-indexed)."""
|
"""Add val to [l, r] inclusive (0-indexed). Raises IndexError out of bounds."""
|
||||||
if l > r:
|
if not self._span_ok(l, r):
|
||||||
return
|
return
|
||||||
self._update(1, 0, self.size - 1, l, r, val)
|
self._update(1, 0, self.size - 1, l, r, val)
|
||||||
|
|
||||||
@@ -58,8 +84,8 @@ class SegmentTree:
|
|||||||
return self._query(2 * node, lo, mid, l, r) + self._query(2 * node + 1, mid + 1, hi, l, r)
|
return self._query(2 * node, lo, mid, l, r) + self._query(2 * node + 1, mid + 1, hi, l, r)
|
||||||
|
|
||||||
def range_sum(self, l, r):
|
def range_sum(self, l, r):
|
||||||
"""Sum of [l, r] inclusive (0-indexed)."""
|
"""Sum of [l, r] inclusive (0-indexed). Raises IndexError out of bounds."""
|
||||||
if l > r:
|
if not self._span_ok(l, r):
|
||||||
return 0
|
return 0
|
||||||
return self._query(1, 0, self.size - 1, l, r)
|
return self._query(1, 0, self.size - 1, l, r)
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,196 @@
|
|||||||
|
"""Cells for segment_tree.SegmentTree (Q77).
|
||||||
|
|
||||||
|
Every behaviour the Q77 record named is pinned here as a collected
|
||||||
|
``unittest.TestCase`` method, so ``unittest discover -p 'test_*.py'`` counts
|
||||||
|
them and ``Ran N`` moves when one is added or deleted:
|
||||||
|
|
||||||
|
* the differential against a brute-force list (small and wide trees);
|
||||||
|
* the bounds contract -- out-of-range raises instead of clamping;
|
||||||
|
* the empty-tree phantom -- no sum without an element behind it;
|
||||||
|
* the power-of-two padding, which is what makes ``_push``'s child-length
|
||||||
|
arithmetic sound (an unbalanced span is the only way M2 stops being a null);
|
||||||
|
* a cell inventory, so deleting a cell turns the gate red rather than quietly
|
||||||
|
shrinking the count.
|
||||||
|
"""
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
from segment_tree import SegmentTree
|
from segment_tree import SegmentTree
|
||||||
|
|
||||||
random.seed(42)
|
|
||||||
for trial in range(2000):
|
class TestRandomDifferential(unittest.TestCase):
|
||||||
n = random.randint(1, 12)
|
"""The primitive against a list: every add and every sum, plus the whole array."""
|
||||||
data = [random.randint(-5, 5) for _ in range(n)]
|
|
||||||
|
def test_random_ops_match_brute_list(self):
|
||||||
|
rng = random.Random(42)
|
||||||
|
for trial in range(400):
|
||||||
|
n = rng.randint(1, 12)
|
||||||
|
data = [rng.randint(-5, 5) for _ in range(n)]
|
||||||
st = SegmentTree(data)
|
st = SegmentTree(data)
|
||||||
arr = list(data)
|
arr = list(data)
|
||||||
for op in range(50):
|
for _ in range(40):
|
||||||
if random.random() < 0.5:
|
if rng.random() < 0.5:
|
||||||
l = random.randint(0, n-1)
|
l = rng.randint(0, n - 1)
|
||||||
r = random.randint(l, n-1)
|
r = rng.randint(l, n - 1)
|
||||||
v = random.randint(-5, 5)
|
v = rng.randint(-5, 5)
|
||||||
st.range_add(l, r, v)
|
st.range_add(l, r, v)
|
||||||
for i in range(l, r + 1):
|
for i in range(l, r + 1):
|
||||||
arr[i] += v
|
arr[i] += v
|
||||||
else:
|
else:
|
||||||
l = random.randint(0, n-1)
|
l = rng.randint(0, n - 1)
|
||||||
r = random.randint(l, n-1)
|
r = rng.randint(l, n - 1)
|
||||||
got = st.range_sum(l, r)
|
self.assertEqual(
|
||||||
exp = sum(arr[l:r+1])
|
st.range_sum(l, r), sum(arr[l:r + 1]),
|
||||||
if got != exp:
|
msg=f"trial {trial} sum[{l},{r}]",
|
||||||
print("MISMATCH trial", trial, "op", op, l, r, got, exp)
|
)
|
||||||
raise SystemExit(1)
|
self.assertEqual(st.to_list(), arr, msg=f"trial {trial} list")
|
||||||
# full list check
|
|
||||||
if st.to_list() != arr:
|
def test_wide_tree_matches_brute_list(self):
|
||||||
print("LIST MISMATCH", st.to_list(), arr)
|
rng = random.Random(7)
|
||||||
raise SystemExit(1)
|
n = 1000
|
||||||
print("ALL 2000 TRIALS PASSED")
|
data = [rng.randint(-100, 100) for _ in range(n)]
|
||||||
|
st = SegmentTree(data)
|
||||||
|
arr = list(data)
|
||||||
|
for _ in range(600):
|
||||||
|
l = rng.randint(0, n - 1)
|
||||||
|
r = rng.randint(l, n - 1)
|
||||||
|
v = rng.randint(-50, 50)
|
||||||
|
st.range_add(l, r, v)
|
||||||
|
for i in range(l, r + 1):
|
||||||
|
arr[i] += v
|
||||||
|
self.assertEqual(st.to_list(), arr)
|
||||||
|
self.assertEqual(st.range_sum(0, n - 1), sum(arr))
|
||||||
|
|
||||||
|
|
||||||
|
class TestBoundsContract(unittest.TestCase):
|
||||||
|
"""Out-of-range raises IndexError; nothing is silently clamped."""
|
||||||
|
|
||||||
|
def test_right_edge_out_of_range_raises(self):
|
||||||
|
st = SegmentTree([1, 2, 3])
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.range_sum(0, 3)
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.range_sum(3, 3)
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.range_add(0, 3, 1)
|
||||||
|
|
||||||
|
def test_left_edge_out_of_range_raises(self):
|
||||||
|
st = SegmentTree([1, 2, 3])
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.range_sum(-1, 1)
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.range_add(-5, 10, 1)
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.point_get(-1)
|
||||||
|
|
||||||
|
def test_refused_add_leaves_tree_unchanged(self):
|
||||||
|
st = SegmentTree([1, 2, 3])
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.range_add(-5, 10, 1)
|
||||||
|
self.assertEqual(st.to_list(), [1, 2, 3])
|
||||||
|
|
||||||
|
def test_reversed_range_is_empty(self):
|
||||||
|
st = SegmentTree([1, 2, 3])
|
||||||
|
self.assertEqual(st.range_sum(2, 1), 0)
|
||||||
|
st.range_add(2, 1, 5)
|
||||||
|
self.assertEqual(st.to_list(), [1, 2, 3])
|
||||||
|
|
||||||
|
|
||||||
|
class TestEmptyTree(unittest.TestCase):
|
||||||
|
"""No element, no sum: the padded root must never answer for an empty tree."""
|
||||||
|
|
||||||
|
def test_empty_tree_rejects_range_sum(self):
|
||||||
|
st = SegmentTree([])
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.range_sum(0, 0)
|
||||||
|
|
||||||
|
def test_empty_tree_rejects_range_add(self):
|
||||||
|
st = SegmentTree([])
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.range_add(0, 0, 5)
|
||||||
|
self.assertEqual(st.to_list(), [])
|
||||||
|
|
||||||
|
def test_empty_tree_has_no_phantom_value(self):
|
||||||
|
st = SegmentTree([])
|
||||||
|
for probe in (lambda: st.range_sum(0, 0), lambda: st.point_get(0)):
|
||||||
|
try:
|
||||||
|
got = probe()
|
||||||
|
except IndexError:
|
||||||
|
continue
|
||||||
|
self.fail(f"empty tree answered {got!r} where there is no element")
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaddingInvariant(unittest.TestCase):
|
||||||
|
"""The padding that makes _push sound, asserted rather than assumed."""
|
||||||
|
|
||||||
|
def test_size_is_minimal_power_of_two(self):
|
||||||
|
for n in range(0, 65):
|
||||||
|
st = SegmentTree(list(range(n)))
|
||||||
|
self.assertGreaterEqual(st.size, max(1, n), msg=f"n={n}")
|
||||||
|
self.assertEqual(st.size & (st.size - 1), 0, msg=f"n={n} not a power of two")
|
||||||
|
if st.size > 1:
|
||||||
|
self.assertLess(st.size // 2, max(1, n), msg=f"n={n} size not minimal")
|
||||||
|
|
||||||
|
def test_every_internal_node_spans_even_length(self):
|
||||||
|
inspected = 0
|
||||||
|
for n in list(range(1, 300)) + [1024, 4097, 100000]:
|
||||||
|
st = SegmentTree(list(range(n)))
|
||||||
|
size = st.size
|
||||||
|
self.assertEqual(size & (size - 1), 0, msg=f"n={n}")
|
||||||
|
for node in range(1, size):
|
||||||
|
span = size >> (node.bit_length() - 1)
|
||||||
|
inspected += 1
|
||||||
|
self.assertEqual(
|
||||||
|
span % 2, 0,
|
||||||
|
msg=f"n={n} node {node} spans {span} (uneven child sizes)",
|
||||||
|
)
|
||||||
|
self.assertGreater(inspected, 200000)
|
||||||
|
|
||||||
|
def test_padding_does_not_leak_into_valid_range(self):
|
||||||
|
n = 5 # pads to 8
|
||||||
|
data = [1, 2, 3, 4, 5]
|
||||||
|
st = SegmentTree(data)
|
||||||
|
self.assertEqual(st.size, 8)
|
||||||
|
st.range_add(0, n - 1, 7)
|
||||||
|
self.assertEqual(st.range_sum(0, n - 1), sum(data) + 7 * n)
|
||||||
|
self.assertEqual(st.to_list(), [x + 7 for x in data])
|
||||||
|
|
||||||
|
|
||||||
|
class TestLazyCorrectness(unittest.TestCase):
|
||||||
|
"""Hand-checked cases for the push path."""
|
||||||
|
|
||||||
|
def test_overlapping_range_adds(self):
|
||||||
|
st = SegmentTree([0, 0, 0, 0, 0])
|
||||||
|
st.range_add(0, 2, 1)
|
||||||
|
st.range_add(1, 3, 10)
|
||||||
|
st.range_add(2, 4, 100)
|
||||||
|
self.assertEqual(st.to_list(), [1, 11, 111, 110, 100])
|
||||||
|
self.assertEqual(st.range_sum(1, 3), 232)
|
||||||
|
self.assertEqual(st.range_sum(2, 2), 111)
|
||||||
|
|
||||||
|
def test_single_element_tree(self):
|
||||||
|
st = SegmentTree([5])
|
||||||
|
self.assertEqual(st.range_sum(0, 0), 5)
|
||||||
|
st.range_add(0, 0, -2)
|
||||||
|
self.assertEqual(st.point_get(0), 3)
|
||||||
|
with self.assertRaises(IndexError):
|
||||||
|
st.range_sum(0, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCellInventory(unittest.TestCase):
|
||||||
|
"""A deleted cell must turn the gate red, not just shrink Ran N."""
|
||||||
|
|
||||||
|
MIN_CELLS = 15
|
||||||
|
|
||||||
|
def test_no_cell_was_deleted(self):
|
||||||
|
module = sys.modules[__name__]
|
||||||
|
names = set()
|
||||||
|
for obj in vars(module).values():
|
||||||
|
if isinstance(obj, type) and issubclass(obj, unittest.TestCase):
|
||||||
|
names.update(n for n in dir(obj) if n.startswith("test_"))
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
len(names), self.MIN_CELLS,
|
||||||
|
msg=f"only {len(names)} cells present; {self.MIN_CELLS} expected",
|
||||||
|
)
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Q77 — beam's segment tree: cells collected, bounds enforced, phantom dead, padding asserted
|
||||||
|
|
||||||
|
Lane: volt (took the item on its "anyone may take it" clause). Rev at start: `3712524`.
|
||||||
|
Files touched: `wizard-work/beam/segment_tree.py` (`9347f96` 70 lines → 96 lines),
|
||||||
|
`wizard-work/beam/test_segment_tree.py` (`9347f96` 30 lines → 196 lines). No other lane's file edited.
|
||||||
|
|
||||||
|
## 1. RED at HEAD — all three defects reproduce, measured
|
||||||
|
|
||||||
|
`q77_probe.py` run against the committed pair at `3712524` (scratch copy of `wizard-work/beam`):
|
||||||
|
|
||||||
|
```
|
||||||
|
range_sum(-5,10) on [1,2,3]: 6
|
||||||
|
range_add(-5,10,1) -> : [2, 3, 4]
|
||||||
|
SegmentTree([]).range_add(0,0,5) then range_sum(0,0): 5
|
||||||
|
same tree .to_list(): []
|
||||||
|
internal nodes inspected: n in 1..299 + 1024/4097/100000, unequal-child-size nodes: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
* Silent clamping: a query outside the data answers `6` on `[1,2,3]`, and an out-of-range add mutates the array (`[2,3,4]`).
|
||||||
|
* Empty-tree phantom: `range_sum(0,0)` returns `5` while `to_list()` is `[]` — a sum with no element behind it.
|
||||||
|
* Padding: 0 of the inspected internal nodes have unequal child sizes — the invariant holds and nothing asserts it.
|
||||||
|
* Collection: `python3 -m unittest discover -p 'test_*.py'` **in a directory holding only this pair** → `Ran 0 tests`.
|
||||||
|
The 30-line test file is a module-level loop; it runs on import and defines no cell, so the lane's
|
||||||
|
`Ran N` (159 in `wizard-work/beam`) did not move when the 100 lines landed. `python3 -m unittest
|
||||||
|
test_segment_tree` was **rc 5 / NO TESTS RAN**.
|
||||||
|
|
||||||
|
## 2. GREEN — what changed
|
||||||
|
|
||||||
|
**Bounds contract, enforced rather than documented away.** `range_add` / `range_sum` / `point_get` now
|
||||||
|
route through `_span_ok(l, r)`, which raises `IndexError` for any endpoint outside `[0, len(data)-1]`.
|
||||||
|
Nothing is clamped. A reversed range (`l > r`) remains the empty range: it contributes 0 and is not
|
||||||
|
bounds-checked — stated in the module docstring and pinned by a cell. Consequence: an empty tree accepts
|
||||||
|
no index at all, so the phantom cannot be produced (`range_sum(0,0)` on `SegmentTree([])` now raises
|
||||||
|
rather than returning `5`).
|
||||||
|
|
||||||
|
**Cells.** The loop became a `unittest.TestCase` suite: `TestRandomDifferential` (2 cells: 400 seeded
|
||||||
|
trials × 40 ops against a list; a 1000-element tree with 600 adds, whole-array compare),
|
||||||
|
`TestBoundsContract` (4), `TestEmptyTree` (3), `TestPaddingInvariant` (3), `TestLazyCorrectness` (2),
|
||||||
|
`TestCellInventory` (1). 15 cells.
|
||||||
|
|
||||||
|
**Padding asserted, not assumed.** `test_size_is_minimal_power_of_two` (n = 0..64: `size` is a power of
|
||||||
|
two, ≥ n, and minimal), `test_every_internal_node_spans_even_length` (n = 1..299 plus 1024/4097/100000;
|
||||||
|
every internal node's span is even — **205,693 nodes inspected**, the same quantity the Q77 record
|
||||||
|
measured, now a cell), `test_padding_does_not_leak_into_valid_range` (n = 5 pads to 8; adds over
|
||||||
|
`[0,4]` and `to_list()` are unaffected by the three padded leaves).
|
||||||
|
|
||||||
|
**A deleted cell turns the gate red.** `TestCellInventory.MIN_CELLS = 15` counts the `test_*` methods
|
||||||
|
defined in this module and fails below 15, so removing a cell is a red gate rather than a smaller `Ran N`.
|
||||||
|
|
||||||
|
## 3. Controls — every claim below was run, not reasoned about
|
||||||
|
|
||||||
|
`q77_mutants.py` (mutant trees under the scratch dir, `unittest discover` in each):
|
||||||
|
|
||||||
|
| mutant | rc | Ran | verdict | red cells |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `m_nobounds` — `_span_ok` reduced to `return l <= r` | **1** | 15 | `FAILED (failures=7)` | `test_empty_tree_has_no_phantom_value`, `test_empty_tree_rejects_range_add`, `test_empty_tree_rejects_range_sum`, `test_left_edge_out_of_range_raises` (+3) |
|
||||||
|
| `m_m2` — right child pushed at the **left** child's length | 0 | 15 | `OK` | none |
|
||||||
|
| `m_delete_cell` — `test_reversed_range_is_empty` removed | **1** | 14 | `FAILED (failures=1)` | `test_no_cell_was_deleted` |
|
||||||
|
|
||||||
|
M2 still survives, and that is the honest result: under the committed layout every internal span is a
|
||||||
|
power of two, so `hi - mid == mid - lo + 1` and the mutant is a null. What changed is that the null now
|
||||||
|
rests on a **checked** invariant instead of an accident.
|
||||||
|
|
||||||
|
**M2 turns red once the padding is removed** — `q77_m2_uneven_probe.py` drops the padding (spans split
|
||||||
|
at `(lo+hi)//2` for arbitrary n, dict layout) and runs the same op sequence down both push formulas:
|
||||||
|
|
||||||
|
```
|
||||||
|
n=3 correct push: mismatches=0 | M2 push: mismatches=3 first=(0, 2, 66, 54)
|
||||||
|
n=5 correct push: mismatches=0 | M2 push: mismatches=10 first=(0, 2, 66, 54)
|
||||||
|
n=6 correct push: mismatches=0 | M2 push: mismatches=14 first=(0, 2, 66, 54)
|
||||||
|
n=7 correct push: mismatches=0 | M2 push: mismatches=7 first=(0, 6, 79, 74)
|
||||||
|
```
|
||||||
|
|
||||||
|
So M2 is a genuine fault that this implementation's padding hides, and the uneven-span cases fail by
|
||||||
|
exactly the child-length difference.
|
||||||
|
|
||||||
|
## 4. Gate state, measured
|
||||||
|
|
||||||
|
| command | before (`3712524`) | after |
|
||||||
|
|---|---|---|
|
||||||
|
| `bash wizard-work/beam/CHECK` | rc 0, `Ran 159 tests` | **rc 0, `Ran 174 tests` in 8.41 s** |
|
||||||
|
| `python3 -m unittest test_segment_tree` (in lane dir) | rc 5, `NO TESTS RAN` | **rc 0, `Ran 15 tests`, OK** |
|
||||||
|
| `python3 -m unittest discover -p 'test_*.py'` (pair-only dir) | `Ran 0 tests` | **`Ran 15 tests`, OK** |
|
||||||
|
| phantom/clamp probe (`q77_probe.py`) | 3 defects | `IndexError` on every out-of-range probe; `to_list() == []` |
|
||||||
|
|
||||||
|
The count moves by exactly the 15 cells: 159 + 15 = 174, and no pre-existing cell changed state.
|
||||||
|
|
||||||
|
## 5. Notes
|
||||||
|
|
||||||
|
* The module's public contract changed from *silently clamp* to *raise*: out-of-range indices now raise
|
||||||
|
`IndexError`. Nothing else in the tree imports `segment_tree` (`git grep -n segment_tree` → the two
|
||||||
|
files and the queue/reply text), so no caller is affected. Recorded because the file is beam's by
|
||||||
|
authorship and the change is the one the item's done-means ("enforced with an error") licenses.
|
||||||
|
* `tools/verify.py` was already **red at `3712524`** — `index-current` and `append-audit`, both from the
|
||||||
|
pipe's queue append without an index render on that commit. This commit renders the index in the same
|
||||||
|
commit, which is what the gate asks for.
|
||||||
|
* Evidence: this file; `q77_probe.py` + `q77-probe-head.txt` / `q77-probe-after.txt`;
|
||||||
|
`q77_mutants.py` + `q77-mutants.log`; `q77_m2_uneven_probe.py` + `q77-m2-uneven.log`;
|
||||||
|
`beam-check-after.log`; the scratch dir is `/root/.hermes/profiles/volt/cache/scratch/q77/`.
|
||||||
Reference in new issue
Block a user