slicecompose 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ name: Upload Python Package
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ pypi-publish:
12
+ name: Upload release to PyPI
13
+ runs-on: ubuntu-latest
14
+ environment: pypi
15
+ permissions:
16
+ id-token: write
17
+ steps:
18
+ - name: Check out repository
19
+ uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: "3.x"
27
+
28
+ - name: Install build dependencies
29
+ run: python -m pip install --upgrade build
30
+
31
+ - name: Build package distribution
32
+ run: python -m build
33
+
34
+ - name: Publish package distributions to PyPI
35
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ .pytest_cache/
3
+ .ruff_cache/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ src/slicecompose/_version.py
@@ -0,0 +1,69 @@
1
+ # slicecompose — project notes
2
+
3
+ ## What this is
4
+
5
+ Exact slice-composition library: `compose(s1, s2)` returns a slice equivalent
6
+ to `arr[s1][s2]` for **all** sequence lengths, or `None` exactly when none
7
+ exists. Sound AND complete — both directions are hard guarantees. Never
8
+ weaken either one to make a test pass.
9
+
10
+ ## Environment
11
+
12
+ - micromamba env: **py10** (pure-stdlib library; only pytest is needed).
13
+
14
+ ## Layout / packaging
15
+
16
+ - src layout per projtemplate: library in `src/slicecompose/__init__.py`,
17
+ tests in `tests/`. `verify_examples.py` stays at root (tied to PROBLEM.md).
18
+ - `pyproject.toml` uses setuptools + setuptools_scm (version from git tags;
19
+ `fallback_version = "0.0.0"` keeps builds working before the first tag).
20
+ `src/slicecompose/_version.py` is generated — never edit or commit it.
21
+ - pytest works from the repo root without installing (pytest `pythonpath =
22
+ ["src"]` in pyproject) — no editable install into py10 needed.
23
+ - ruff is configured in pyproject (line-length 99, double quotes).
24
+
25
+ ## Testing
26
+
27
+ - Quick tier: `micromamba run -n py10 python -m pytest -q` from the repo root (~30 s)
28
+ - Exhaustive tier: prefix `SLICECOMPOSE_FULL=1` (~35 min; measured 36 min)
29
+ - Run heavy tiers with `ulimit -v` and `nice`.
30
+ - Ground truth for any manual check is CPython `range` slicing:
31
+ `range(n)[s1][s2] == range(n)[s3]` is exact, O(1) even for astronomical
32
+ `n`, and ranges compare by value. Prefer it over the library's own
33
+ `_eval1`/`_eval2` when independence matters.
34
+
35
+ ## Architecture invariants
36
+
37
+ - **Soundness never depends on candidate generation**: every candidate
38
+ (including ±1 wiggles) must pass `_verify`, which compares only
39
+ ground-truth point evaluations. Keep it that way — new candidate families
40
+ are cheap to add and cannot break soundness.
41
+ - **Completeness** lives in the forcing lemmas (EXPLANATION.md §5.2) and the
42
+ candidate generators. This is the risky part: any change there requires
43
+ re-running the exhaustive grid audit (FULL tier) — it has caught a real
44
+ forcing-lemma bug before (the K<0 window stop pin).
45
+ - No code path may branch on field *magnitudes* (only signs, `None`-ness,
46
+ and exact comparisons); this uniformity is what lets the exhaustive
47
+ small-field audits stand in for all magnitudes.
48
+ - `SlicePlan` must keep delegating reduction to the `SliceChain` greedy
49
+ stack: the stack is left-to-right, so a collapsed prefix + new tail
50
+ reduces identically to the raw history and plan results are independent
51
+ of when queries happened. An "optimal" interval-DP reduction would break
52
+ that path-independence unless the raw history were kept alongside.
53
+
54
+ ## Documentation sync
55
+
56
+ - EXPLANATION.md §8 quotes the FULL-tier grid/table sizes; keep them in sync
57
+ with the parameters in `test_slicecompose.py` when grids change.
58
+ - The runtime estimates in the test module docstring are measured values;
59
+ update them if the grids change.
60
+
61
+ ## Known theory facts (verified, documented in EXPLANATION.md §7)
62
+
63
+ - Adjacent-pair chain reduction is **not confluent**: merge order can change
64
+ the final chain length, and the greedy stack does not always reach a
65
+ shortest fixpoint (spec only requires *a* fixpoint, so this is not a bug).
66
+ - Pairwise merging is inherently incomplete for chains ≥ 3: e.g.
67
+ `[2::-1], [:2], [:-1]` equals the single slice `[2:0:-2]` for every `n`,
68
+ yet neither adjacent pair merges. A k-ary `compose` would be needed to
69
+ detect such chains.
@@ -0,0 +1,405 @@
1
+ # Why `compose` is sound and complete for every length
2
+
3
+ `compose(s1, s2)` must return a slice equivalent to `arr[s1][s2]` for **all**
4
+ lengths `n >= 0`, or `None` exactly when no such slice exists. Testing any
5
+ finite set of lengths cannot establish this by itself; the argument below
6
+ shows how the implementation reduces the infinite quantification over `n` to
7
+ finitely many exact checks, without ever enumerating ranges (fields may be
8
+ astronomically large integers).
9
+
10
+ Throughout, `s1 = (a1, b1, k1)`, `s2 = (a2, b2, k2)` are the input slices and
11
+ `q = k1` after the mirror reduction of §2 (so `q >= 1`), `K = k1*k2`,
12
+ `D = q*|k2|`. `ceil(x/y)` denotes ceiling division.
13
+
14
+ ## 1. The action of a slice, and the equality criterion
15
+
16
+ For a slice `s` and length `n`, Python's semantics (`slice.indices`) yield
17
+ normalized `(alpha(n), beta(n), k)` and select the index progression
18
+
19
+ I_j(n) = A(n) + k*j, 0 <= j < L(n),
20
+
21
+ where `A = alpha` and `L(n) = max(0, ceil((beta - alpha)/k))`. The clamped
22
+ anchor forms are (with `[x]+ = max(0, x)`):
23
+
24
+ | step | field | normalized form |
25
+ |-------|--------------|------------------------------------------------------|
26
+ | k > 0 | start `None` | `alpha = 0` |
27
+ | k > 0 | start `s>=0` | `alpha = min(s, n)` |
28
+ | k > 0 | start `s<0` | `alpha = [n+s]+` |
29
+ | k > 0 | stop `None` | `beta = n` |
30
+ | k > 0 | stop `e>=0` | `beta = min(e, n)` |
31
+ | k > 0 | stop `e<0` | `beta = [n+e]+` |
32
+ | k < 0 | start `None` | `alpha = n-1` |
33
+ | k < 0 | start `s>=0` | `alpha = min(s, n-1)` |
34
+ | k < 0 | start `s<0` | `alpha = max(-1, n+s)` |
35
+ | k < 0 | stop `None` | `beta = -1` |
36
+ | k < 0 | stop `e>=0` | `beta = min(e, n-1)` |
37
+ | k < 0 | stop `e<0` | `beta = max(-1, n+e)` |
38
+
39
+ `_indices`/`_eval1` implement exactly these formulas (unit-tested against
40
+ CPython's own slicing for thousands of random slices and lengths).
41
+
42
+ Every anchor is either **front-anchored** (`alpha`/`beta` eventually a
43
+ constant) or **back-anchored** (eventually `n + const`). Two virtual
44
+ positions exist only via `None`: "the whole back" (`beta = n`, k > 0) and
45
+ "before the beginning" (`beta = -1`, k < 0).
46
+
47
+ **Equality criterion.** Two actions `(A, k, L)` and `(A', k', L')` at length
48
+ `n` select the same subsequence iff
49
+
50
+ L = L' and (L = 0 or A = A') and (L <= 1 or k = k').
51
+
52
+ Since `range(n)` has pairwise-distinct elements, per-`n` equality of index
53
+ selections is exactly the problem's equivalence. `_pt_eq` implements this.
54
+
55
+ The composition of `s1` then `s2` acts as
56
+
57
+ m(n) = L1(n),
58
+ A(n) = alpha1(n) + k1 * A2(m(n)), K = k1*k2, L(n) = L2(m(n)),
59
+
60
+ because selecting `j`-th of `s2` inside the `s1`-selection composes the two
61
+ affine index maps (`_eval2`). So `compose` must decide: *is there a slice
62
+ whose `(A, L)` functions agree with the composed ones in the sense above for
63
+ every `n`, with step `K` wherever some `L >= 2`?*
64
+
65
+ ## 2. Mirror reduction: assume `k1 >= 1`
66
+
67
+ Define `~x = -x-1` and `mirror(s) = slice(~start, ~stop, -step)` with `None`
68
+ kept. **Lemma (mirror).** For every slice `u` and every `n`:
69
+
70
+ arr[::-1][u] == arr[mirror(u)].
71
+
72
+ *Proof.* Reversal maps position `p` to `n-1-p`. The `j`-th selected index of
73
+ `u` on the reversed sequence is `A_u(n) + k_u j` in reversed coordinates,
74
+ i.e. `n-1-A_u(n) - k_u j` in original coordinates: an action with step
75
+ `-k_u`. Applying `p -> n-1-p` to the table above maps each anchor form to the
76
+ corresponding form with the field negated bitwise (`min(s,n)`-type maps to
77
+ `max(-1, n+~s)`-type and vice versa, front and back swap), and the two virtual
78
+ `None` positions map to each other. Hence the transformed action is exactly
79
+ the action of `mirror(u)`. ∎
80
+
81
+ Since `rev . rev = id`, also `u = rev . mirror(u)`. Consequently
82
+
83
+ s1 . s2 = rev . (mirror(s1) . s2),
84
+
85
+ and applying the lemma once more: `s1 . s2` is expressible as a single slice
86
+ iff `mirror(s1) . s2` is, and the results correspond via `mirror`. If
87
+ `k1 < 0`, `mirror(s1)` has positive step, so `compose` recurses once with
88
+ `q = |k1|` and mirrors the result back. From here on `q >= 1`.
89
+
90
+ ## 3. Structure of the composed action
91
+
92
+ ### 3.1 Breakpoints (regime boundaries)
93
+
94
+ Every `min`, `max`, `[.]+` branch switch and every sign change of
95
+ `beta - alpha` in §1 is a comparison between two terms of the form `c` or
96
+ `n + c` with `c ∈ {0, ±start, ±stop}`. Hence:
97
+
98
+ **Lemma B1.** All regime boundaries of a slice's action, as functions of the
99
+ length, lie in `{x + y + d : x, y ∈ {0, ±fields}, |d| <= 2}`.
100
+
101
+ `_combo_vals` takes exactly this generous superset. For the composed action,
102
+ `s2`'s regimes live in `m`-space; they are pulled back to `n`-space:
103
+
104
+ **Lemma B2.** `m(n) = L1(n)` changes by at most 1 when `n` grows by 1 (one
105
+ added element extends any slice result by at most one element; immediate from
106
+ the formulas), and is monotone between consecutive breakpoints of `s1` (there
107
+ `beta1 - alpha1` is affine). Hence `m` crosses each critical value `V` of
108
+ `s2` at most once per piece, cannot skip it, and the crossing is found
109
+ exactly by binary search (`_pullback_breaks`). On the unbounded final piece,
110
+ a crossing of `V`, if any, occurs within `q*(V+4) + 4*Mag + 64` of the piece
111
+ start (`m` moves with slope `1/q` and offsets bounded by the field
112
+ magnitudes `Mag`), so the bounded search window loses nothing.
113
+
114
+ The union of `s1`-breakpoints, pulled-back `s2`-breakpoints, the candidate's
115
+ own breakpoints (Lemma B1 applied to it), and `{0..3}`, each with ±2 guards,
116
+ cuts `n >= 0` into **stretches** (`_stretches`); by construction every
117
+ involved function is regime-constant on every stretch. The count is O(1)
118
+ (a few hundred at most), independent of field magnitudes.
119
+
120
+ ### 3.2 Per-stretch normal forms
121
+
122
+ **Lemma S1 (lengths).** On a stretch, `m` is constant or
123
+ `ceil((±n + c)/q)`; `L2` as a function of `m` is constant or
124
+ `ceil((±m + c')/k2')` with `k2' = |k2|` (possibly clamped at 0, which only
125
+ truncates the staircase). Composing and using the exact identities
126
+
127
+ ceil((ceil(x/p) + c)/r) = ceil((x + p*c)/(p*r)),
128
+ ceil((c - ceil(x/p))/r) = ceil((p*(c-1) + 1 - x)/(p*r)),
129
+
130
+ the composed `L(n)` on a stretch is **constant, or
131
+ `max(0, ceil((±n + c'')/D))` with `D = q*|k2|`**: a monotone staircase with
132
+ unit jumps spaced exactly `D`. The same holds for a candidate slice with
133
+ divisor `|k3|`.
134
+
135
+ **Lemma S2 (first index).** On a stretch, `A(n) = alpha1(n) + q*A2(m(n))`
136
+ where `alpha1` is affine (slope 0 or 1) and `A2` is affine in `m` with slope
137
+ `tau ∈ {0, 1}` (slope 1 iff `s2`'s start is back-anchored in the active
138
+ regime). Since `q*ceil((±n+c)/q) = ±n + c + ((∓n - c) mod q)`,
139
+
140
+ A(n) = affine(n) + tau * sawtooth_q(n),
141
+
142
+ where the sawtooth has period `q` and amplitude `q - 1`. It is present
143
+ (non-degenerate) iff `tau = 1`, `m` non-constant on the stretch, and `q > 1`.
144
+ A candidate slice's `A` is affine on every stretch (its regime switches are
145
+ breakpoints).
146
+
147
+ **Lemma S3 (monotonicity).** On a stretch, `m` is monotone and `L2` is
148
+ monotone in `m` over the traversed range (no regime change inside), so the
149
+ composed `L` is monotone on every stretch; likewise the candidate's `L`.
150
+ Consequently `{n in stretch : L(n) >= t}` is a contiguous prefix or suffix.
151
+
152
+ **Lemma S4 (final stretch).** Beyond the last breakpoint no clamp changes
153
+ state anymore, so `L` is constant or a growing staircase. A strictly
154
+ decreasing regime always terminates at its zero-crossing, which is a
155
+ pulled-back death threshold (a 2-field combination, Lemma B1) and hence a
156
+ breakpoint; the code still guards the impossible case (`tail_mode ==
157
+ 'shrink'`) conservatively.
158
+
159
+ ## 4. The verifier is exact (soundness)
160
+
161
+ `_verify` compares the composed action `f` with a candidate's action `g` on
162
+ every stretch. All probes evaluate **ground truth** (`_eval2`, `_eval1`
163
+ implement §1 directly), so no derived formula can silently disagree with
164
+ Python's semantics; the lemmas are only needed to show the probes *suffice*.
165
+
166
+ On a **finite stretch** `[u, v]` (`_check_stretch_finite`):
167
+
168
+ 1. *Support and step-relevance regions.* For `t ∈ {1, 2}` the regions
169
+ `{L_f >= t}` and `{L_g >= t}` are contiguous (S3) and are computed exactly
170
+ by monotone binary search; they must coincide. This pins where results
171
+ are empty, where they have one element, and where steps matter.
172
+ 2. *Lengths on the `L>=2` region `[w1, w2]`.* Point comparisons at `w1`
173
+ (which has `L >= 2`) force `k3 = K`, hence both staircases have the same
174
+ divisor `D` (S1). Two monotone unit-jump staircases that are exactly
175
+ `D`-periodic, agree at `w1`, and have the same first jump position (found
176
+ by binary search, compared, and value-checked at `jump-1, jump, jump+1`)
177
+ are identical on the region: level and phase determine an exact staircase.
178
+ The `L ∈ {0, 1}` parts are already fixed by step 1.
179
+ 3. *First indices on the `L>=1` region.* `g`'s `A` is affine (S2). If the
180
+ composed sawtooth is active (measured via `tau` and `m` at the region's
181
+ endpoints — exact because regimes are constant) and the region spans at
182
+ least `q` points, `f`'s `A` contains a full sawtooth period, is not
183
+ affine, and cannot equal any slice's `A`: reject. Otherwise the region
184
+ contains at most one sawtooth wrap. The checked points include `w1`,
185
+ `w1+1`, midpoint, `w2-1`, `w2`: if there is no wrap both sides are affine
186
+ and agreement at two adjacent points implies agreement everywhere; if
187
+ there is one wrap, the composed values after the wrap deviate from the
188
+ affine function through `(w1, w1+1)` by exactly `±q ≠ 0`, which the
189
+ endpoint checks expose.
190
+ 4. All curated points are additionally compared with the full criterion of
191
+ §1 (length, first index, step where relevant).
192
+
193
+ On the **final stretch** `[u, ∞)` (`_check_stretch_inf`): by S4 both sides
194
+ are constant or growing exact staircases with divisor at most
195
+ `dmax = max(D, |k3|)`, so every threshold and the first jump lie within
196
+ `u + 2*dmax + 5`; the same finite reasoning applies there, far probes at
197
+ `u + 3*dmax + 7` (and `+ dmax` beyond) pin constant levels and growth, and
198
+ exact `D`-periodicity (S1) extends the jump alignment to all `n`. An active
199
+ sawtooth necessarily wraps on an infinite support region and is rejected
200
+ outright — no slice can match it (this is what kills, e.g., `[::2][::-1]`).
201
+
202
+ **Soundness theorem.** If `_verify(t1, t2, cand)` returns `True`, then the
203
+ candidate is equivalent to the composition for every `n >= 0`: the stretches
204
+ cover all `n`, and on each stretch steps 1–4 establish pointwise equality of
205
+ the selections in the sense of §1. Since `compose` only ever returns a
206
+ verified candidate, every returned slice is correct — independently of any
207
+ property of the candidate generator.
208
+
209
+ ## 5. Candidate generation is exhaustive (completeness)
210
+
211
+ Suppose *some* equivalent slice `w = (s, e, k3)` exists. We show the
212
+ generator (`_candidates`) emits a candidate that is pointwise-equal to `w`
213
+ for all `n`; the verifier then accepts it, so `compose` cannot return `None`.
214
+
215
+ ### 5.1 Every slice belongs to one of finitely many families
216
+
217
+ From the table in §1, classify `w` by its support `{n : L_w(n) >= 1}` and
218
+ tail behavior:
219
+
220
+ - **always empty**;
221
+ - **bounded support**: only back-start/front-stop with `k3 > 0`, or
222
+ front-start/back-stop with `k3 < 0` (every other anchor combination has
223
+ `beta - alpha` eventually constant or growing, giving tail support or
224
+ emptiness);
225
+ - **tail support, `L` eventually constant** ("windows"): front/front or
226
+ back/back anchors, either step sign;
227
+ - **tail support, `L` growing**: front-start/back-stop with `k3 > 0`, or
228
+ back-start/front-stop with `k3 < 0` (the growth forces the stop to recede
229
+ with `n` while the start stays anchored).
230
+
231
+ The profile (§3 machinery without a candidate) measures the composition's
232
+ support bounds `N0`, `N1`, its tail mode (empty / constant `P` / growing),
233
+ `max_len`, and tail probes `A(n̂)`, `sigma = A(n̂+1) - A(n̂)`. These
234
+ measurements are exact: on the final stretch the forms of S1/S2 hold, so
235
+ constancy vs. growth is decided by probes `2D+5` apart, and monotone binary
236
+ searches locate the support boundaries. Equivalence forces `w` to reproduce
237
+ exactly these measured quantities, which places `w` in exactly one family
238
+ and, as shown next, forces its parameters.
239
+
240
+ ### 5.2 Parameter forcing per family
241
+
242
+ *Step.* If `max_len >= 2`, some `n` has two selected elements whose distance
243
+ is `q*k2`, so `k3 = K` (generated as such). If `max_len <= 1`, the step
244
+ never influences the selection beyond enforcing `L <= 1`: for the emitted
245
+ shapes, `L >= 1` is step-independent (`beta - alpha >= 1`) and the single
246
+ selected element is `alpha(n)`; any `kappa >= max(beta - alpha)` works and
247
+ all such slices are pointwise identical, so emitting the safe bound
248
+ `max(1, ...)` loses nothing.
249
+
250
+ *Start.* On the tail, `sigma ∈ {0, 1}` for any slice (S2 without sawtooth).
251
+ `sigma = 0` forces a front anchor with `s = A(n̂)` (on its support a
252
+ front-anchored start equals its field); `sigma = 1` forces a back anchor with
253
+ `s = A(n̂) - n̂`. If the measured `sigma` is anything else, the composed `A`
254
+ is not affine on the tail (it carries a sawtooth) and **no** slice matches —
255
+ correctly yielding `None`. For bounded support the same reading is done at
256
+ the support ends `N0`, `N1`.
257
+
258
+ *Stop.*
259
+ - *Growing `L`, `K > 0`* (front/back): the tail lengths are
260
+ `ceil((n + e - s)/K)`; the position `n_j` of the first length jump past
261
+ `n̂` (binary search) and the value `L(n_j)` pin `e` exactly:
262
+ `e = s + K*(L(n_j)-1) + 1 - n_j`, with `e = 0` meaning `stop=None`.
263
+ Distinct `e` give distinct tail length functions, so `e` is unique.
264
+ - *Growing `L`, `K < 0`* (back/front): symmetrically
265
+ `e = n_j + s + K*(L(n_j)-1) - 1`, with `e = -1` meaning `stop=None`.
266
+ - *Window, `K > 0`, front/front*: `e` may vary within one step bucket
267
+ `(s + K(P-1), s + K*P]`. **Bucket invariance:** for any `n`, replacing `e`
268
+ by another bucket member changes `beta = min(e, n)` only when `beta = n`
269
+ for both or when `n` lies between them, in which case `ceil((n - s)/K)`
270
+ equals `P` as well — the selection is unchanged *for every* `n`. Both
271
+ bucket ends are emitted.
272
+ - *Window, `K > 0`, back/back*: the support start of
273
+ `[s:e:K]` (`s < e <= 0`) is `max(1, 1 - e)` (while the start clamps at 0,
274
+ `L >= 1` iff `n + e >= 1`), so `e = 1 - N0`, unique.
275
+ - *Window, `K < 0`, front/front*: the support start of `[s:e:K]` is `e + 2`
276
+ (`beta = min(e, n-1) < alpha` first happens there), or `1` for
277
+ `stop=None`; so `e = N0 - 2`, unique. (The tail bucket does *not* pin `e`
278
+ here: bucket members differ at small `n` while the start is clamped —
279
+ found and fixed by the exhaustive grid audit.)
280
+ - *Window, `K < 0`, back/back*: bucket freedom again; the clamp
281
+ `beta = max(-1, n+e)` hands over seamlessly (`L = min(ceil((n+s+1)/|K|), P)`
282
+ independent of the bucket member), so both bucket ends are emitted.
283
+ - *Bounded support:* emptiness first fails at `N1 + 1`, giving
284
+ `e = N1 + s + 1` (`K > 0`) resp. `e = s - N1 - 1` (`K < 0`); the start is
285
+ read at the support ends as above.
286
+
287
+ *`max_len <= 1` shapes.* The selected-element function `E(n) = A(n)` of any
288
+ `L <= 1` slice is, on its support: constant `c` (front start; support is then
289
+ forced to be exactly `{n > c}` for every such slice, so one representative
290
+ `slice(c, c+1)` suffices); `[n+s]+` (back start, tail support; support start
291
+ `N0` pins the stop via `e = 1 - N0` as above, `kappa = max(1, e - s)`);
292
+ `min(s, n-1)` (negative-step front start; `e = N0 - 2`,
293
+ `kappa = max(1, s - e)`); or the bounded-support versions handled with the
294
+ bounded pins. Anything else (e.g. the parity-dependent `E` of `[::2][-1:]`)
295
+ matches no slice and correctly yields `None`.
296
+
297
+ ±1 wiggles around every derived stop (and the alternative start readings for
298
+ bounded support) are also emitted purely as armor against off-by-one
299
+ conventions; the exact verifier filters them, so they cannot affect
300
+ soundness.
301
+
302
+ **Completeness theorem.** If an equivalent slice `w` exists, the profile
303
+ places it in one family; the forcing lemmas show the generator emits a
304
+ candidate pointwise-equal to `w` (either `w`'s exact parameters or an
305
+ equivalent bucket/`kappa` representative). The verifier, being exact (§4),
306
+ accepts it. Hence `compose` returns `None` only when no equivalent slice
307
+ exists.
308
+
309
+ ## 6. Always-empty compositions and edge cases
310
+
311
+ - If `max_len = 0` (e.g. `[5:3][::-1]`), `slice(0, 0, 1)` is emitted and
312
+ trivially verified — any always-empty slice is acceptable.
313
+ - `n = 0` imposes no constraint (every slice selects nothing), and all
314
+ formulas above are exact from `n = 0` on.
315
+ - Emitted slices are cosmetically normalized (`start 0 -> None` for positive
316
+ step, `start -1 -> None` for negative step, `step 1 -> None`); each rewrite
317
+ is a strict semantic identity from the table in §1, and it is the
318
+ normalized triple that gets verified.
319
+
320
+ ## 7. `SliceChain`
321
+
322
+ `SliceChain(*slices)` reduces with a stack: each incoming slice is repeatedly
323
+ merged with the top of the stack while `compose` succeeds. A successful
324
+ merge replaces the top, so the new top is retried against its own left
325
+ neighbor — this finds cascading reductions such as
326
+
327
+ [::2], [::-1], [::-1] -> [::2], [:] -> [::2]
328
+
329
+ (the problem's chain example: the *last two* merge first, then again).
330
+ Invariant: adjacent stack entries never merge; therefore one pass reaches a
331
+ state where **no adjacent pair merges**, which is the required fixpoint.
332
+ The invariant is maintained because mergeability of a pair depends only on
333
+ the pair's composed function, and a merge leaves the composed function of
334
+ every disjoint pair unchanged — so the already-checked pairs to the left
335
+ stay unmergeable.
336
+
337
+ The fixpoint is not canonical, though: adjacent-pair reduction is **not
338
+ confluent**, and merge order can even affect the length of the result. A
339
+ merge consumes its two blocks, which can destroy a pairing the optimum
340
+ needed. Example (all claims machine-verified):
341
+
342
+ [-2:2], [0:2], [:0:-1], [1:0:-1]
343
+
344
+ The stack eagerly merges the first pair and then gets stuck at three slices,
345
+ while merging the middle pair first (`[0:2][:0:-1] == [1:2]`) collapses the
346
+ whole chain to a single always-empty slice. So the stack reaches *a*
347
+ fixpoint (as the problem statement requires) but not always a shortest one;
348
+ finding a shortest reachable fixpoint would take an interval DP over
349
+ mergeable blocks, matrix-chain style. Pairwise merging is moreover
350
+ inherently incomplete for chains of three or more:
351
+
352
+ [2::-1], [:2], [:-1] == [2:0:-2] for every n,
353
+
354
+ yet neither adjacent pair is expressible on its own, so no merge order can
355
+ reduce this chain at all; deciding such cases would require a k-ary
356
+ `compose`.
357
+
358
+ `apply` simply applies the reduced slices in order, which is correct whether
359
+ or not any merging happened (each merge preserved the function). The
360
+ reduced tuple is exposed as `.slices`.
361
+
362
+ `SlicePlan` is a lazy wrapper over the same reduction: it gathers slices
363
+ unreduced (each bracket application returns a new plan) and on first query
364
+ replaces its stored chain by the `SliceChain` fixpoint, flagging itself
365
+ reduced. Because the stack processes slices left to right, its state after
366
+ the prefix *is* the collapsed prefix, so reducing a collapsed prefix plus
367
+ new tail equals reducing the raw history — query timing never affects a
368
+ plan's result, and everything above applies to `SlicePlan` unchanged.
369
+
370
+ ## 8. Validation performed
371
+
372
+ The proofs above are the argument for all `n`; the test suite additionally
373
+ hammers every code path empirically:
374
+
375
+ - the problem statement's 17 merge examples, 6 non-merge examples, and the
376
+ chain examples;
377
+ - an **exhaustive** grid of 147,456 pairs (fields in `{None, 0, ±1, ±2, ±3}`,
378
+ steps up to `±3`) and 1,000,000 pairs with steps up to `±6` and fields up
379
+ to `±7`: every merge is checked against brute force for `n <= 500` plus
380
+ large spot lengths, and every `None` is audited for completeness against
381
+ brute-force candidate tables of 18,000 resp. ~221,000 slices (both
382
+ audits found zero defects; an earlier run of the audit is what caught
383
+ the `K < 0` window stop-pin bug described in §5.2);
384
+ - randomized soundness checks with fields up to `10^30` and steps up to
385
+ `10^12`, compared at random lengths up to `10^60`;
386
+ - a structural completeness probe at huge magnitudes: for random triples,
387
+ `(s1∘s2)∘s3` and `s1∘(s2∘s3)` decide expressibility of the same function,
388
+ so their `None`-ness must agree whenever both pair-merges exist;
389
+ - the mirror lemma tested pointwise for thousands of random slices,
390
+ including huge fields;
391
+ - `_indices`/`_eval1` tested against CPython's own slicing.
392
+
393
+ Because no branch of the implementation depends on the *magnitude* of any
394
+ field (only on signs, `None`-ness, and exact comparisons), the exhaustive
395
+ small-field audits exercise every regime combination that large-field inputs
396
+ can produce; large-field correctness then follows from the magnitude-uniform
397
+ lemmas, and is additionally spot-checked directly.
398
+
399
+ ## 9. Complexity
400
+
401
+ `compose` performs O(1) profile/verification stretches, each with O(1)
402
+ probes plus binary searches over value ranges bounded by the field
403
+ magnitudes — O(polylog(magnitude)) big-integer operations total
404
+ (~0.1 ms for small fields, ~1 ms for 100-digit fields). No operation ever
405
+ materializes a range, so memory is O(1).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 István Sárándi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.