patchcraft 0.5.4__tar.gz → 0.5.5__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.
Files changed (90) hide show
  1. {patchcraft-0.5.4 → patchcraft-0.5.5}/CHANGELOG.md +123 -1
  2. {patchcraft-0.5.4 → patchcraft-0.5.5}/CITATION.cff +2 -2
  3. {patchcraft-0.5.4 → patchcraft-0.5.5}/CONTRIBUTING.md +17 -16
  4. {patchcraft-0.5.4 → patchcraft-0.5.5}/MAP.md +7 -5
  5. {patchcraft-0.5.4 → patchcraft-0.5.5}/PKG-INFO +6 -6
  6. {patchcraft-0.5.4 → patchcraft-0.5.5}/README.md +8 -8
  7. {patchcraft-0.5.4 → patchcraft-0.5.5}/README.pt-BR.md +6 -6
  8. {patchcraft-0.5.4 → patchcraft-0.5.5}/README.pypi.md +5 -5
  9. {patchcraft-0.5.4 → patchcraft-0.5.5}/STATUS.md +13 -11
  10. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/GUIDE.md +29 -37
  11. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/PERFORMANCE.md +16 -11
  12. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/USAGE.md +6 -8
  13. {patchcraft-0.5.4 → patchcraft-0.5.5}/pyproject.toml +5 -0
  14. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/_version.py +3 -3
  15. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/geometry.py +35 -18
  16. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/metrics.py +5 -3
  17. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/reconstruct.py +23 -10
  18. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/stitch.py +34 -11
  19. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft.egg-info/PKG-INFO +6 -6
  20. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft.egg-info/SOURCES.txt +3 -1
  21. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft.egg-info/scm_file_list.json +28 -0
  22. patchcraft-0.5.5/src/patchcraft.egg-info/scm_version.json +8 -0
  23. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_stitch.py +14 -0
  24. {patchcraft-0.5.4 → patchcraft-0.5.5}/tools/benchmark.py +5 -3
  25. patchcraft-0.5.5/tools/compare_exactness_rules.py +77 -0
  26. patchcraft-0.5.5/tools/make_outreach_figures.py +970 -0
  27. patchcraft-0.5.4/src/patchcraft.egg-info/scm_version.json +0 -8
  28. {patchcraft-0.5.4 → patchcraft-0.5.5}/.gitignore +0 -0
  29. {patchcraft-0.5.4 → patchcraft-0.5.5}/.pre-commit-config.yaml +0 -0
  30. {patchcraft-0.5.4 → patchcraft-0.5.5}/.python-version +0 -0
  31. {patchcraft-0.5.4 → patchcraft-0.5.5}/AGENTS.md +0 -0
  32. {patchcraft-0.5.4 → patchcraft-0.5.5}/CODE_OF_CONDUCT.md +0 -0
  33. {patchcraft-0.5.4 → patchcraft-0.5.5}/LICENSE +0 -0
  34. {patchcraft-0.5.4 → patchcraft-0.5.5}/MANIFEST.in +0 -0
  35. {patchcraft-0.5.4 → patchcraft-0.5.5}/SECURITY.md +0 -0
  36. {patchcraft-0.5.4 → patchcraft-0.5.5}/accel/Cargo.lock +0 -0
  37. {patchcraft-0.5.4 → patchcraft-0.5.5}/accel/Cargo.toml +0 -0
  38. {patchcraft-0.5.4 → patchcraft-0.5.5}/accel/README.md +0 -0
  39. {patchcraft-0.5.4 → patchcraft-0.5.5}/accel/src/kernel.rs +0 -0
  40. {patchcraft-0.5.4 → patchcraft-0.5.5}/accel/src/lib.rs +0 -0
  41. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/ADR/0001-patch-extraction-api.md +0 -0
  42. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/ADR/0002-patchify-transform.md +0 -0
  43. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/ADR/0003-reversibility-classes.md +0 -0
  44. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/ADR/0004-precision-and-effort-parameters.md +0 -0
  45. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/AUXILIARY.md +0 -0
  46. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/FOCO-1.0.md +0 -0
  47. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/ROADMAP.md +0 -0
  48. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/SCOPE.md +0 -0
  49. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/STUDIES/2026-08-04-patch-techniques.md +0 -0
  50. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/STUDIES/2026-09-03-precision-measurements.md +0 -0
  51. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/STUDIES/2026-09-03-strata-conformity.md +0 -0
  52. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/THEORY.md +0 -0
  53. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/design/2026-09-01-fase1-bugs-performance.md +0 -0
  54. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/design/2026-09-01-fase2-acelerador.md +0 -0
  55. {patchcraft-0.5.4 → patchcraft-0.5.5}/docs/design/2026-09-01-fase3-predicado.md +0 -0
  56. {patchcraft-0.5.4 → patchcraft-0.5.5}/setup.cfg +0 -0
  57. {patchcraft-0.5.4 → patchcraft-0.5.5}/setup.py +0 -0
  58. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/__init__.py +0 -0
  59. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/_accel.py +0 -0
  60. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/_foldgeom.py +0 -0
  61. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/cache.py +0 -0
  62. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/extract.py +0 -0
  63. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/pair.py +0 -0
  64. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/py.typed +0 -0
  65. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft/resize.py +0 -0
  66. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft.egg-info/dependency_links.txt +0 -0
  67. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft.egg-info/requires.txt +0 -0
  68. {patchcraft-0.5.4 → patchcraft-0.5.5}/src/patchcraft.egg-info/top_level.txt +0 -0
  69. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/__init__.py +0 -0
  70. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/_datasets.py +0 -0
  71. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/_rng.py +0 -0
  72. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/conftest.py +0 -0
  73. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_accel.py +0 -0
  74. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_cache.py +0 -0
  75. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_citation.py +0 -0
  76. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_datasets_helper.py +0 -0
  77. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_docs_usage.py +0 -0
  78. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_exactness.py +0 -0
  79. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_extract.py +0 -0
  80. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_geometry.py +0 -0
  81. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_import.py +0 -0
  82. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_metrics.py +0 -0
  83. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_pair.py +0 -0
  84. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_public_api.py +0 -0
  85. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_reconstruct.py +0 -0
  86. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_reference.py +0 -0
  87. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_resize.py +0 -0
  88. {patchcraft-0.5.4 → patchcraft-0.5.5}/tests/test_rng.py +0 -0
  89. {patchcraft-0.5.4 → patchcraft-0.5.5}/tools/check_dist.py +0 -0
  90. {patchcraft-0.5.4 → patchcraft-0.5.5}/uv.lock +0 -0
@@ -6,6 +6,127 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.5] - 2026-09-26
10
+
11
+ A performance release that changes no bit. Every change to `src/` below was
12
+ compared against the 0.5.4 code through integer views, so that `-0.0` and
13
+ `+0.0` or two NaN payloads count as different: 53,716 cases over 1,382
14
+ geometries, four float dtypes, both code paths and gradients, with 0
15
+ differences. The full 126,736-geometry exactness sweep passes on both paths.
16
+
17
+ ### Changed
18
+
19
+ - **`stitch` builds its window denominator without a Python loop.** The
20
+ 1-D window fold ran one iteration per stride residue, six tensor operations
21
+ each, and that loop dominated `stitch` at every size once the numerator was
22
+ accelerated. All residues are now one cumsum over a `(m, step)` view of the
23
+ zero-padded kernel, which scans each residue in the same order, so the
24
+ output is bit-identical: 0 differences against the previous code over
25
+ 38,696 cases, covering three windows, four float dtypes, both code paths and
26
+ gradients. Accelerated `stitch` is 2.7x to 5.1x faster at 512x512 and 1.2x
27
+ to 1.4x at 2048x2048; on the pure path `F.fold` still dominates at 2048.
28
+
29
+ - **`reconstruct` builds its count map with fewer operations, and both
30
+ `reconstruct` and `stitch` divide in place.** The suffix ramp of the
31
+ closed-form count map is the prefix ramp reversed, a square grid reuses the
32
+ H axis for W, and the outer product is taken in the accumulation dtype,
33
+ where multiplying two small exact integers rounds their exact product once,
34
+ as casting the int64 product did. The numerator and the denominator are
35
+ fresh tensors each function owns, so the final clamp and division no longer
36
+ allocate. Bit-identical: 0 differences over 13,820 `reconstruct` and 38,696
37
+ `stitch` cases, and the caller's patches are never mutated or aliased.
38
+ Accelerated `reconstruct` is 1.15x to 1.26x faster, and one call at
39
+ 3x2048x2048 with patch 64 and stride 32 allocates 64 MiB instead of 177
40
+ (`stitch` hann: 65 instead of 128).
41
+
42
+ - **`tilings(allow_overlap=True)` visits only the strides that can work.** A
43
+ stride covers both axes exactly when it divides `h - p` and `w - p`, that
44
+ is when it divides their gcd, so the loop runs over the divisors of the gcd
45
+ instead of over every stride below the patch size. The output is the same
46
+ list in the same order: identical in 9,530 cases, every shape from 1x1 to
47
+ 69x69 in both modes plus larger ones, and `paired_tilings`, which builds on
48
+ it. 22.8 to 7.3 ms at 512x512, 91 to 9 ms at 1080x1920, 699 to 17 ms at
49
+ 4000x3000.
50
+
51
+ - **`stitch` with the uniform window skips the fold on non-overlapping
52
+ grids.** There the fold adds each value, times 1, into a zero-filled buffer
53
+ and divides by a count of 1, which is `0.0 + x`: the value itself, except
54
+ that `-0.0` comes out as `+0.0` and a NaN comes out quiet. The new path
55
+ rearranges the patches and adds `0.0`, which reproduces exactly that; a bare
56
+ rearrangement would have kept `-0.0`, a difference `torch.equal` cannot see
57
+ and an integer-view comparison can. Bit-identical in the full comparison
58
+ and in a dedicated check of zeros of both signs, NaN payloads, infinities,
59
+ extremes and subnormals, and pinned by a new test against `F.fold` itself.
60
+ 3.4x to 58x faster depending on size: 146 to 9.4 ms on the pure path at
61
+ 3x2048x2048 with patch 32.
62
+
63
+ - docs/PERFORMANCE.md is re-measured on the code that ships, twice, agreeing
64
+ within about 10%. The accelerator's range over the published table is now
65
+ 5.8x to 16.5x, against 2.6x to 14x at 0.5.1; the low end moved most, since
66
+ accelerated `stitch` at 512 went from 6.3 to 3.0 ms. The thread sweep behind
67
+ "the gain is algorithmic" was re-run as well: the pure path at 2048 takes
68
+ 448 ms at 4 threads and 390 at 36, no better than at 8.
69
+
70
+ - **The manual, the usage page and the three READMEs no longer carry
71
+ development history.** Sentences about what an earlier version did, what a
72
+ first audit found or what used to be printed moved out; where an old
73
+ measurement is the argument for the current rule it stays, in the present
74
+ tense. GUIDE §8 is now "Where this applies today", and each of its items says
75
+ what a reader should do (on GPU it works and does not accelerate; on macOS and
76
+ aarch64, compare once against `PATCHCRAFT_ACCEL=0`), instead of listing what
77
+ the project has not done. The old anchor is updated everywhere it was linked.
78
+
79
+ - The README tagline read "Encode one image into patches, decode it back",
80
+ which invited the compression reading the page then had to head off. It now
81
+ says "Cut one image into patches, and put it back together", followed by one
82
+ sentence on what a patch is for.
83
+
84
+ - GUIDE's hann figure against the model's own patches read 27.14 dB and prints
85
+ 27.15 dB on both code paths; the page now quotes what the code prints.
86
+
87
+ - `tools/make_outreach_figures.py` lost 301 unreachable lines left from
88
+ earlier layouts, with every generated file byte-identical before the later
89
+ edits. Every caption that states a result is now checked against it before
90
+ it is drawn, including the refusal at stride 20 and the exact round trip at
91
+ 32 and 16. The MNIST grid is drawn at display scale instead of into the
92
+ digit's pixels, where it hid more than a third of a 28x28 image, and the
93
+ patch shown alone is outlined in it.
94
+
95
+ ### Added
96
+
97
+ - `tools/compare_exactness_rules.py` measures the exactness rule against the
98
+ obvious looser one, keeping the largest coverage count at 4 or below, over
99
+ the 126,736 geometries `tests/test_exactness.py` enumerates, with the round
100
+ trip on five seeds as ground truth. The power-of-two rule makes no false
101
+ promise and withholds none; `k_max <= 4` promises exactness on 13,870
102
+ geometries that do not come back exact. The outreach article quoted an
103
+ earlier sweep of 14,969 rectangular geometries from a lab script that is not
104
+ in the tree, so no command reproduced it; it now cites this one.
105
+
106
+ ### Fixed
107
+
108
+ - docs/PERFORMANCE.md and `tools/benchmark.py` said non-overlapping geometries
109
+ never reach the fold. At 0.5.4 that held for `reconstruct`, which has a
110
+ closed-form path at `stride == patch_size`, and not for `stitch`, which
111
+ folded at every geometry: about 144 ms on the pure path at 3x2048x2048 with
112
+ patch 32, where `reconstruct` returned the same bits in about 6 ms. This
113
+ release gives `stitch` that path under the uniform window, and both texts
114
+ now say which windows still fold and why.
115
+
116
+ - `metrics.py` claimed no allocation beyond the difference and, in a comment,
117
+ that the in-place subtract avoids a float64 copy of `b`. Measured with the
118
+ torch profiler, `a.to(float64).sub_(b)` with `b` in float32 allocates a
119
+ float64 temporary of `b` first, doubling the largest allocation from 188 to
120
+ 375 MiB on 94 MiB of input. The comment and the module docstring now say
121
+ what happens; the code is unchanged.
122
+
123
+ - CONTRIBUTING placed the `WeightKind` paragraph between "what each digit is
124
+ for here:" and the table the colon announced, and its errata repeated two
125
+ worked examples word for word. MAP and CONTRIBUTING counted five issue forms
126
+ where there are four, called all-closed 1.0 blockers "still in the way", and
127
+ listed neither generator in `tools/`. STATUS still called the `Cache` fix
128
+ unreleased after 0.5.4 shipped it.
129
+
9
130
  ## [0.5.4] - 2026-09-04
10
131
 
11
132
  ### Fixed
@@ -895,7 +1016,8 @@ First public release. Public API stable; signatures will only change in 1.x.
895
1016
  - [`README.md`](README.md) covers installation, the car-vs-track metaphor,
896
1017
  validation lab.
897
1018
 
898
- [Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.4...HEAD
1019
+ [Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.5...HEAD
1020
+ [0.5.5]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.5
899
1021
  [0.5.4]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.4
900
1022
  [0.5.3]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.3
901
1023
  [0.5.2]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.2
@@ -6,8 +6,8 @@ authors:
6
6
  - given-names: Leonardo
7
7
  family-names: Marques de Souza
8
8
  email: leonardo.marques.souza@gmail.com
9
- version: "0.5.4"
10
- date-released: "2026-09-04"
9
+ version: "0.5.5"
10
+ date-released: "2026-09-26"
11
11
  repository-code: "https://github.com/LeoPR/PatchCraft"
12
12
  license: MIT
13
13
  abstract: >
@@ -34,7 +34,7 @@ is silently inert here. Either make that global hook delegate, or set
34
34
  whatever else the global hooks were doing.
35
35
 
36
36
  `ruff-format` is deliberately not among the hooks. This project has never used
37
- it, and adopting it would reformat 36 of 65 files in a commit that changed no
37
+ it, and adopting it would reformat 37 of 82 files in a commit that changed no
38
38
  behaviour.
39
39
 
40
40
  ```
@@ -130,6 +130,8 @@ PatchCraft/
130
130
  ├── setup.py the one build decision: with or without the Rust extension
131
131
  ├── tools/check_dist.py gate: extension present, versions agree, tag respected
132
132
  ├── tools/benchmark.py accelerated vs pure, and proves they agree first
133
+ ├── tools/compare_exactness_rules.py the power-of-two rule against k_max <= 4, measured
134
+ ├── tools/make_outreach_figures.py every outreach figure and generated page, computed
133
135
  ├── MANIFEST.in what the sdist carries
134
136
  ├── README.md the call page, canonical, English
135
137
  ├── README.pt-BR.md the same call page in Portuguese
@@ -148,7 +150,7 @@ PatchCraft/
148
150
  ├── SECURITY.md how to report, and what the real attack surface is
149
151
  ├── CODE_OF_CONDUCT.md Contributor Covenant 2.1, verbatim
150
152
  ├── .github/
151
- │ ├── ISSUE_TEMPLATE/ five forms; the required fields are what makes a report reproducible
153
+ │ ├── ISSUE_TEMPLATE/ four forms and a chooser; the required fields are what makes a report reproducible
152
154
  │ ├── PULL_REQUEST_TEMPLATE.md asks whether a returned value moved, which decides the version bump
153
155
  │ └── workflows/
154
156
  │ ├── test.yml matrix CI on PRs/main, plus the accelerated job
@@ -185,6 +187,9 @@ PatchCraft/
185
187
  │ ├── test_accel.py native path, skipped unless the extension was built
186
188
  │ ├── test_datasets_helper.py label_subset
187
189
  │ ├── test_import.py
190
+ │ ├── test_docs_usage.py runs docs/USAGE.md as a doctest
191
+ │ ├── test_citation.py CITATION.cff agrees with the package
192
+ │ ├── conftest.py shared fixtures
188
193
  │ ├── _rng.py audited round-trip helpers (data generation, bit equality)
189
194
  │ ├── test_rng.py tests for those helpers
190
195
  │ └── _datasets.py dev-only fixtures (MNIST, etc), NOT public API
@@ -199,14 +204,15 @@ PatchCraft/
199
204
  │ ├── AUXILIARY.md tests/_datasets, lab/, Z:\ conventions (NOT part of the wheel)
200
205
  │ ├── THEORY.md distilled design + §9 condition contract; §0 binding scope
201
206
  │ ├── ROADMAP.md milestone plan
202
- │ ├── FOCO-1.0.md what 1.0 freezes and the blockers in the way
207
+ │ ├── FOCO-1.0.md what 1.0 freezes, and the six blockers, all closed
203
208
  │ ├── ADR/ one file per decision; the choice only, evidence in STUDIES/
204
209
  │ ├── STUDIES/ background reading behind those decisions
205
210
  │ └── design/ one spec per work phase: the alternatives measured and the decision
206
211
  ├── outreach/ material for presenting the project publicly, not shipped
207
212
  │ ├── README.md how it is organized and what to check before publishing
208
- │ ├── 2026-09-03-lancamento.md the dated news source every channel text derives from
209
- │ └── linkedin/ post (short) and artigo (long), each in PT and EN
213
+ │ ├── 2026-09-04-lancamento.md the current dated news source the channel texts derive from
214
+ │ ├── 2026-09-03-lancamento.md the previous one, kept as the record of 0.5.1
215
+ │ └── linkedin/ post, artigo, the illustrated page and the figures, PT and EN
210
216
  └── archive/ reference-only; gitignored (pruned 2026-05-17, only HISTORY.md kept)
211
217
  ```
212
218
 
@@ -253,12 +259,7 @@ PEP 440's `~=0.5.0` means `>=0.5.0, <0.6.0`. In every one of them a `0.y` bump
253
259
  is the announcement that something may break, and a `0.y.z` bump is the
254
260
  promise that nothing does.
255
261
 
256
- That fixes what each digit is for here:
257
-
258
- One consequence worth stating on its own, because a consumer cannot infer it
259
- from a `Literal`: **`WeightKind` is an open set.** Adding a member is additive
260
- and lands in a `0.y` before 1.0 and a minor after it; removing or renaming one
261
- is a break. Code that matches on it exhaustively should keep a fallback arm.
262
+ That fixes what each digit is for here.
262
263
 
263
264
  The question that decides it is not "did a returned value change". A bug fix
264
265
  changes returned values by definition, and if that forced a `0.Y.0` then a
@@ -289,6 +290,11 @@ under any rule keyed on whether values changed:
289
290
  - `0.5.0` made `tilings` return 73 specs where it returned 100. The
290
291
  enumeration's output set is the contract. A **y**.
291
292
 
293
+ One consequence worth stating on its own, because a consumer cannot infer it
294
+ from a `Literal`: **`WeightKind` is an open set.** Adding a member is additive
295
+ and lands in a `0.y` before 1.0 and a minor after it; removing or renaming one
296
+ is a break. Code that matches on it exhaustively should keep a fallback arm.
297
+
292
298
  After 1.0 the ordinary reading applies: breaking is major, additive is minor,
293
299
  fixed is patch. What 1.0 freezes is written in
294
300
  [`docs/FOCO-1.0.md`](docs/FOCO-1.0.md).
@@ -305,11 +311,6 @@ minting a y announced a break that never happened.
305
311
  `0.5.1` was briefly numbered `0.6.0` before release. It changes only how the
306
312
  package is built and shipped, so it is a z-bump.
307
313
 
308
- Two others were right and are worth recording as the shape to copy. `0.3.0`
309
- moved `stitch` output by ULPs through a different summation order, which is an
310
- output change and belongs at a y boundary. `0.5.0` made `tilings` return 73
311
- specs where it returned 100, which is squarely a break.
312
-
313
314
  The accelerator in [`accel/`](accel/) has no version of its own to manage. It
314
315
  is compiled into this wheel, so it ships when `patchcraft` ships. The version
315
316
  in `accel/Cargo.toml` is internal and nothing reads it; what the Python side
@@ -22,7 +22,7 @@ PatchCraft/
22
22
  ├── CONTRIBUTING.md ........... layout, the two code paths, the versioning rule, releasing
23
23
  ├── CITATION.cff .............. citation metadata; GitHub reads it for the cite button
24
24
  ├── LICENSE ................... MIT
25
- ├── SECURITY.md ............... how to report, and the three trust boundaries that matter
25
+ ├── SECURITY.md ............... how to report, and the five edges of the attack surface
26
26
  ├── CODE_OF_CONDUCT.md ........ Contributor Covenant 2.1
27
27
  │
28
28
  ├── src/patchcraft/ ........... THE LIBRARY. 20 public names, frozen by test
@@ -55,19 +55,21 @@ PatchCraft/
55
55
  │ ├── USAGE.md .............. the 20 symbols one at a time; every >>> runs in the suite
56
56
  │ ├── AUXILIARY.md .......... test fixtures, lab/, the off-tree conventions
57
57
  │ ├── ROADMAP.md ............ milestones, historical
58
- │ ├── FOCO-1.0.md ........... what 1.0 freezes, and the blockers still in the way
58
+ │ ├── FOCO-1.0.md ........... what 1.0 freezes, and the six blockers, all closed
59
59
  │ ├── ADR/ .................. one file per decision; the choice, not the research
60
60
  │ ├── design/ ............... one spec per work phase: alternatives measured, then chosen
61
61
  │ └── STUDIES/ .............. background reading, and the Strata conformity review
62
62
  │
63
63
  ├── tools/
64
64
  │ ├── benchmark.py .......... accelerated vs pure, and proves they agree before timing
65
- │ └── check_dist.py ......... release gate: extension present, versions agree, tag respected
65
+ │ ├── check_dist.py ......... release gate: extension present, versions agree, tag respected
66
+ │ ├── compare_exactness_rules.py the power-of-two rule against k_max <= 4, measured
67
+ │ └── make_outreach_figures.py every outreach figure and generated page, computed
66
68
  │
67
69
  ├── outreach/ ................. material for presenting the project; not shipped
68
70
  ├── lab/ ...................... scratch experiments; only the README is tracked
69
71
  └── .github/
70
- ├── ISSUE_TEMPLATE/ ....... five forms, each asking for the geometry and PATCHCRAFT_ACCEL=0
72
+ ├── ISSUE_TEMPLATE/ ....... four forms, each asking for the geometry and PATCHCRAFT_ACCEL=0
71
73
  ├── PULL_REQUEST_TEMPLATE.md
72
74
  └── workflows/ ............ test.yml (both code paths) and release.yml (one project, six wheels)
73
75
  ```
@@ -83,7 +85,7 @@ PatchCraft/
83
85
  | Know **what each function accepts and rejects**, precisely | [docs/THEORY.md §9](docs/THEORY.md), which is the arbiter where documents disagree |
84
86
  | Know **whether this library is the right tool** | [docs/SCOPE.md](docs/SCOPE.md) |
85
87
  | Know **what the native accelerator is worth**, and re-measure it | [docs/PERFORMANCE.md](docs/PERFORMANCE.md), then `python tools/benchmark.py` |
86
- | Know **what the project refuses to claim** | [docs/GUIDE.md §8](docs/GUIDE.md#8-what-this-project-does-not-claim) |
88
+ | Know **where the library applies today** | [docs/GUIDE.md §8](docs/GUIDE.md#8-where-this-applies-today) |
87
89
  | Understand **why the API looks like this** | [docs/ADR/](docs/ADR/), and [docs/STUDIES/](docs/STUDIES/) for the measurements behind them |
88
90
  | Understand **why a phase was built the way it was** | [docs/design/](docs/design/) |
89
91
  | **Contribute**: run the gates, understand the two code paths | [CONTRIBUTING.md](CONTRIBUTING.md) |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchcraft
3
- Version: 0.5.4
3
+ Version: 0.5.5
4
4
  Summary: Image patch extraction, reconstruction, pairing and seam-aware stitching for super-resolution and dataset pipelines.
5
5
  Author: Leonardo Marques de Souza
6
6
  License-Expression: MIT
@@ -42,7 +42,7 @@ Dynamic: license-file
42
42
 
43
43
  # PatchCraft
44
44
 
45
- Encode one image into patches, decode it back, and decide what happens at the seams.
45
+ Cut one image into patches, put it back together, and decide what happens at the seams.
46
46
 
47
47
  [![Latest version on PyPI](https://img.shields.io/pypi/v/patchcraft?color=3775A9&label=PyPI)](https://pypi.org/project/patchcraft/)
48
48
  [![Supported Python versions](https://img.shields.io/pypi/pyversions/patchcraft?color=3776AB)](https://pypi.org/project/patchcraft/)
@@ -88,7 +88,7 @@ paths, which return the same values.
88
88
 
89
89
  `patchcraft.accel_available()` reports at runtime which one you got, and
90
90
  `PATCHCRAFT_ACCEL=0` in the environment forces the pure path. On the overlapping
91
- fold it is worth between 2.6x and 14x on the machine it was measured on, which
91
+ fold the accelerator is worth between 5.8x and 16.5x on the machine it was measured on, which
92
92
  the [performance page](https://github.com/LeoPR/PatchCraft/blob/main/docs/PERFORMANCE.md)
93
93
  reports in full.
94
94
 
@@ -144,7 +144,7 @@ assert scrambled.shape == patches.shape # the right shape
144
144
  assert not torch.equal(scrambled, patches) # and the wrong pixels
145
145
  ```
146
146
 
147
- The saving is real on the other side too. Tiling an image, running a per-patch model and blending the result back with a Hann window took 17 non-blank lines by hand against 3 with `extract` and `stitch`, and the two outputs agreed to about 1.2e-05 on a value in [0, 1].
147
+ The saving is real on the other side too. Tiling an image, running a per-patch model and blending the result back with a Hann window took 17 non-blank lines by hand against 3 with `extract` and `stitch`, and the two outputs agreed to about 1.2e-05 on a value in [0, 1], summation-order rounding more than 300 times smaller than one 8-bit step.
148
148
 
149
149
  ## The geometry has to cover the image
150
150
 
@@ -170,9 +170,9 @@ The everyday shorthand is that `stride == patch_size` and `stride == patch_size
170
170
 
171
171
  ## Status
172
172
 
173
- This is pre-1.0, so both the output values and the API shape can still move. While the leading digit is zero the middle one is the compatibility boundary, which makes a new `0.y.z` safe to take and a new `0.y` the place where a change is allowed to land, and the [changelog](https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md) records each one with the measurement behind it. The suite collects 1656 tests and passes on Python 3.12, 3.13 and 3.14, on Ubuntu and on Windows alike.
173
+ This is pre-1.0, so both the output values and the API shape can still move. While the leading digit is zero the middle one is the compatibility boundary, which makes a new `0.y.z` safe to take and a new `0.y` the place where a change is allowed to land, and the [changelog](https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md) records each one with the measurement behind it. The suite collects 1657 tests and passes on Python 3.12, 3.13 and 3.14, on Ubuntu and on Windows alike.
174
174
 
175
- Two limits are worth knowing before you depend on it. Every figure on this page was measured on CPU, and no CUDA path has ever executed in the test matrix, so the pipeline does preserve the device you hand it while the exactness numbers stay unverified on GPU. The other limit is that no external project has consumed the published API in real use yet, and that consumption is this project's own stated gate for calling the shape settled.
175
+ Where it applies today: on GPU the functions accept CUDA tensors and keep the device, and the Rust kernel is CPU-only, so it does not accelerate there; every figure on this page is a CPU figure, so check exactness on your device before relying on it. No external project consumes the published API yet, which is the gate this project set for calling the shape settled.
176
176
 
177
177
  ## Documentation
178
178
 
@@ -8,7 +8,7 @@
8
8
  [![Supported Python versions](https://img.shields.io/pypi/pyversions/patchcraft.svg)](https://pypi.org/project/patchcraft/)
9
9
  [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/LeoPR/PatchCraft/blob/main/LICENSE)
10
10
 
11
- **Encode one image into patches, decode it back.** PatchCraft owns the `unfold` and `fold` arithmetic, the geometry validation and the seam blending, so that your pipeline can own everything else.
11
+ **Cut one image into patches, and put it back together.** A large image rarely goes into a network whole: it is cut into small tiles, the patches, each one is processed on its own, and the image is put back. PatchCraft owns the `unfold` and `fold` arithmetic, the geometry validation and the seam blending, so that your pipeline can own everything else.
12
12
 
13
13
  **One image at a time, on purpose.** Every call takes one `(C, H, W)` float tensor and returns one tensor, because the patch count depends on the image and a batched API would have to pad or return a list. Your `for` loop, `torch.vmap` or `DataLoader` supplies the batching.
14
14
 
@@ -30,7 +30,7 @@ The image goes out as a stack of patches, you do your work on the stack, and it
30
30
  image. That last arrow has two doors: `reconstruct` when the patches are untouched, and `stitch`
31
31
  when a model rewrote them and the seams need to fade.
32
32
 
33
- This page is the call page. The manual is [docs/GUIDE.md](docs/GUIDE.md), and it carries the measurements, the tables and the long examples that used to live here.
33
+ This page is the call page. The manual is [docs/GUIDE.md](docs/GUIDE.md), and it carries the measurements, the tables and the long examples.
34
34
 
35
35
  ## Install
36
36
 
@@ -47,8 +47,8 @@ paths, which return the same values.
47
47
 
48
48
  `patchcraft.accel_available()` reports at runtime which one you got, and
49
49
  `PATCHCRAFT_ACCEL=0` in the environment forces the pure path. On the overlapping
50
- fold it is worth between 2.6x and 14x here, measured in
51
- [docs/PERFORMANCE.md](docs/PERFORMANCE.md).
50
+ fold the accelerator is worth between 5.8x and 16.5x on the machine it was
51
+ measured on, which [docs/PERFORMANCE.md](docs/PERFORMANCE.md) reports in full.
52
52
 
53
53
  The distribution name and the import name are both `patchcraft`. The runtime dependencies are `torch>=2.6`, `numpy>=1.26` and `pillow>=10`, and the supported Python versions are in [the guide](docs/GUIDE.md#9-install-details-and-citation) together with the note you need before you install a GPU wheel.
54
54
 
@@ -108,7 +108,7 @@ assert not torch.equal(scrambled, patches) # and the wrong pix
108
108
 
109
109
  The second one is a stride that does not cover the image. On a 128 by 128 image with `patch=32, stride=20` the grid stops at pixel 112, which leaves 3840 of the 16384 pixels at zero, and a hand-rolled `fold` returns that partly black image without complaining.
110
110
 
111
- Writing the tile-and-blend loop by hand costs 17 non-blank lines against 3 here, and the two results agree to about 1.2e-05 on a value in [0, 1], the two routes differing only in the order they sum a separable window. [The guide](docs/GUIDE.md#1-why-not-unfold-and-fold-directly) runs both versions side by side.
111
+ Writing the tile-and-blend loop by hand costs 17 non-blank lines against 3 here, and the two results agree to about 1.2e-05 on a value in [0, 1], more than 300 times smaller than one 8-bit step, the two routes differing only in the order they sum a separable window. [The guide](docs/GUIDE.md#1-why-not-unfold-and-fold-directly) runs both versions side by side.
112
112
 
113
113
  ## The geometry has to cover the image
114
114
 
@@ -130,7 +130,7 @@ print([s.patch_size for s in tilings(image.shape)]) # 7 exact tilings, from the
130
130
 
131
131
  `tilings` is arithmetic on the shape, so it reads nothing and allocates nothing. Pass `allow_overlap=True` when you want the overlapping geometries too.
132
132
 
133
- ## Where you are getting into
133
+ ## What you are getting into
134
134
 
135
135
  The surface is one tensor in and one tensor out. There is no batch axis, no dataset, no dataloader and no training, and that boundary is binding rather than provisional, recorded in [docs/THEORY.md](docs/THEORY.md) §0.
136
136
 
@@ -146,9 +146,9 @@ The round trip is exact when every value in the count map is a power of two, whi
146
146
 
147
147
  **Pre-1.0**, and the badge above is the current release. While the leading digit is zero the middle one is the compatibility boundary, so `0.y.z` is safe to take and a new `0.y` is where an output value or a signature is allowed to move. [CHANGELOG.md](CHANGELOG.md) records every such change with the measurement behind it.
148
148
 
149
- 1656 tests collected and CI is green on {Ubuntu, Windows} x {Python 3.12, 3.13, 3.14}, with `ruff check` and `mypy --strict` in the same run. The package is typed and ships `py.typed`.
149
+ 1657 tests collected and CI is green on {Ubuntu, Windows} x {Python 3.12, 3.13, 3.14}, with `ruff check` and `mypy --strict` in the same run. The package is typed and ships `py.typed`.
150
150
 
151
- No external project has consumed it yet, and no CUDA path in this library has ever executed. [The guide](docs/GUIDE.md#8-what-this-project-does-not-claim) lists what else this project declines to claim.
151
+ On GPU the functions keep the device you hand them, and the Rust kernel is CPU-only, so it does not accelerate there; every figure here is a CPU figure. No external project consumes the library yet, which is the gate for calling the API settled. [The guide](docs/GUIDE.md#8-where-this-applies-today) says where the rest applies.
152
152
 
153
153
  ## Where to read next
154
154
 
@@ -8,7 +8,7 @@
8
8
  [![Supported Python versions](https://img.shields.io/pypi/pyversions/patchcraft.svg)](https://pypi.org/project/patchcraft/)
9
9
  [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/LeoPR/PatchCraft/blob/main/LICENSE)
10
10
 
11
- **Codifica uma imagem em patches e decodifica de volta.** O PatchCraft cuida da aritmética do `unfold` e do `fold`, da validação da geometria e da mistura nas emendas, para que o seu pipeline possa cuidar de todo o resto.
11
+ **Recorta uma imagem em patches e monta de volta.** Uma imagem grande raramente entra inteira numa rede: ela é cortada em pedaços pequenos, os patches, cada um é processado sozinho, e a imagem é montada de volta. O PatchCraft cuida da aritmética do `unfold` e do `fold`, da validação da geometria e da mistura nas emendas, para que o seu pipeline possa cuidar de todo o resto.
12
12
 
13
13
  **Uma imagem por vez, de propósito.** Cada chamada recebe um tensor float `(C, H, W)` e devolve um tensor, porque a quantidade de patches depende da imagem, e uma API em lote teria que preencher com padding ou devolver uma lista. Quem fornece o lote é o seu `for`, o `torch.vmap` ou o `DataLoader`.
14
14
 
@@ -47,8 +47,8 @@ rodam os caminhos em torch puro, que devolvem os mesmos valores.
47
47
 
48
48
  O `patchcraft.accel_available()` informa em runtime qual dos dois você recebeu,
49
49
  e `PATCHCRAFT_ACCEL=0` no ambiente força o caminho puro. No fold com
50
- sobreposição ele vale entre 2.6x e 14x aqui, medido em
51
- [docs/PERFORMANCE.md](docs/PERFORMANCE.md).
50
+ sobreposição o acelerador vale entre 5,8x e 16,5x na máquina em que foi
51
+ medido, que o [docs/PERFORMANCE.md](docs/PERFORMANCE.md) relata por inteiro.
52
52
 
53
53
  O nome da distribuição e o nome de importação são os dois `patchcraft`. As dependências de execução são `torch>=2.6`, `numpy>=1.26` e `pillow>=10`. As versões de Python suportadas estão [no manual](docs/GUIDE.md#9-install-details-and-citation), junto com a observação que você precisa ler antes de instalar uma wheel de GPU.
54
54
 
@@ -108,7 +108,7 @@ assert not torch.equal(scrambled, patches) # e os pixels errad
108
108
 
109
109
  O segundo é um stride que não cobre a imagem. Numa imagem de 128 por 128 com `patch=32, stride=20`, a grade para no pixel 112, o que deixa 3840 dos 16384 pixels em zero, e um `fold` escrito à mão devolve essa imagem parcialmente preta sem reclamar.
110
110
 
111
- Escrever o laço de recortar e remontar à mão custa 17 linhas não vazias contra 3 aqui, e os dois resultados concordam em cerca de 1.2e-05 num valor em [0, 1], com as duas rotas diferindo só na ordem em que somam uma janela separável. [O manual](docs/GUIDE.md#1-why-not-unfold-and-fold-directly) roda as duas versões lado a lado.
111
+ Escrever o laço de recortar e remontar à mão custa 17 linhas não vazias contra 3 aqui, e os dois resultados concordam em cerca de 1,2e-05 num valor em [0, 1], mais de 300 vezes menos que um degrau de 8 bits, com as duas rotas diferindo só na ordem em que somam uma janela separável. [O manual](docs/GUIDE.md#1-why-not-unfold-and-fold-directly) roda as duas versões lado a lado.
112
112
 
113
113
  ## A geometria precisa cobrir a imagem
114
114
 
@@ -146,9 +146,9 @@ O round-trip é exato quando todo valor do mapa de cobertura é potência de doi
146
146
 
147
147
  **Pré-1.0**, e o badge acima mostra a versão atual. Enquanto o primeiro dígito for zero, quem marca a fronteira de compatibilidade é o do meio: um `0.y.z` novo é seguro de pegar, e é num `0.y` novo que um valor de saída ou uma assinatura pode mudar. O [CHANGELOG.md](CHANGELOG.md) registra cada uma dessas mudanças com a medição por trás dela.
148
148
 
149
- São 1656 testes coletados, com CI verde em {Ubuntu, Windows} x {Python 3.12, 3.13, 3.14}, e com `ruff check` e `mypy --strict` na mesma execução. O pacote é tipado e distribui o `py.typed`.
149
+ São 1657 testes coletados, com CI verde em {Ubuntu, Windows} x {Python 3.12, 3.13, 3.14}, e com `ruff check` e `mypy --strict` na mesma execução. O pacote é tipado e distribui o `py.typed`.
150
150
 
151
- Nenhum projeto externo consumiu a biblioteca ainda, e nenhum caminho CUDA dela jamais executou. [O manual](docs/GUIDE.md#8-what-this-project-does-not-claim) lista o que mais este projeto se recusa a afirmar.
151
+ Em GPU as funções preservam o dispositivo que você passa, e o kernel Rust é só de CPU, então não acelera ali; todo número desta página é de CPU. Nenhum projeto externo consome a biblioteca ainda, que é o critério para dizer a API assentada. [O manual](docs/GUIDE.md#8-where-this-applies-today) diz onde o resto se aplica.
152
152
 
153
153
  ## Onde ler em seguida
154
154
 
@@ -1,6 +1,6 @@
1
1
  # PatchCraft
2
2
 
3
- Encode one image into patches, decode it back, and decide what happens at the seams.
3
+ Cut one image into patches, put it back together, and decide what happens at the seams.
4
4
 
5
5
  [![Latest version on PyPI](https://img.shields.io/pypi/v/patchcraft?color=3775A9&label=PyPI)](https://pypi.org/project/patchcraft/)
6
6
  [![Supported Python versions](https://img.shields.io/pypi/pyversions/patchcraft?color=3776AB)](https://pypi.org/project/patchcraft/)
@@ -46,7 +46,7 @@ paths, which return the same values.
46
46
 
47
47
  `patchcraft.accel_available()` reports at runtime which one you got, and
48
48
  `PATCHCRAFT_ACCEL=0` in the environment forces the pure path. On the overlapping
49
- fold it is worth between 2.6x and 14x on the machine it was measured on, which
49
+ fold the accelerator is worth between 5.8x and 16.5x on the machine it was measured on, which
50
50
  the [performance page](https://github.com/LeoPR/PatchCraft/blob/main/docs/PERFORMANCE.md)
51
51
  reports in full.
52
52
 
@@ -102,7 +102,7 @@ assert scrambled.shape == patches.shape # the right shape
102
102
  assert not torch.equal(scrambled, patches) # and the wrong pixels
103
103
  ```
104
104
 
105
- The saving is real on the other side too. Tiling an image, running a per-patch model and blending the result back with a Hann window took 17 non-blank lines by hand against 3 with `extract` and `stitch`, and the two outputs agreed to about 1.2e-05 on a value in [0, 1].
105
+ The saving is real on the other side too. Tiling an image, running a per-patch model and blending the result back with a Hann window took 17 non-blank lines by hand against 3 with `extract` and `stitch`, and the two outputs agreed to about 1.2e-05 on a value in [0, 1], summation-order rounding more than 300 times smaller than one 8-bit step.
106
106
 
107
107
  ## The geometry has to cover the image
108
108
 
@@ -128,9 +128,9 @@ The everyday shorthand is that `stride == patch_size` and `stride == patch_size
128
128
 
129
129
  ## Status
130
130
 
131
- This is pre-1.0, so both the output values and the API shape can still move. While the leading digit is zero the middle one is the compatibility boundary, which makes a new `0.y.z` safe to take and a new `0.y` the place where a change is allowed to land, and the [changelog](https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md) records each one with the measurement behind it. The suite collects 1656 tests and passes on Python 3.12, 3.13 and 3.14, on Ubuntu and on Windows alike.
131
+ This is pre-1.0, so both the output values and the API shape can still move. While the leading digit is zero the middle one is the compatibility boundary, which makes a new `0.y.z` safe to take and a new `0.y` the place where a change is allowed to land, and the [changelog](https://github.com/LeoPR/PatchCraft/blob/main/CHANGELOG.md) records each one with the measurement behind it. The suite collects 1657 tests and passes on Python 3.12, 3.13 and 3.14, on Ubuntu and on Windows alike.
132
132
 
133
- Two limits are worth knowing before you depend on it. Every figure on this page was measured on CPU, and no CUDA path has ever executed in the test matrix, so the pipeline does preserve the device you hand it while the exactness numbers stay unverified on GPU. The other limit is that no external project has consumed the published API in real use yet, and that consumption is this project's own stated gate for calling the shape settled.
133
+ Where it applies today: on GPU the functions accept CUDA tensors and keep the device, and the Rust kernel is CPU-only, so it does not accelerate there; every figure on this page is a CPU figure, so check exactness on your device before relying on it. No external project consumes the published API yet, which is the gate this project set for calling the shape settled.
134
134
 
135
135
  ## Documentation
136
136
 
@@ -1,4 +1,4 @@
1
- # STATUS: 2026-09-04
1
+ # STATUS: 2026-09-26
2
2
 
3
3
  > The snapshot. Where things are *right now*, and who each open item waits on.
4
4
  > Where things **are** is [MAP.md](MAP.md); the rules are [AGENTS.md](AGENTS.md);
@@ -6,12 +6,12 @@
6
6
 
7
7
  ## Where the project is
8
8
 
9
- **0.5.4 is on PyPI**, published through Trusted Publishing on a tag push: one
9
+ **0.5.5 is on PyPI**, published through Trusted Publishing on a tag push: one
10
10
  sdist, five `cp312-abi3` platform wheels carrying the Rust accelerator, and one
11
11
  universal `py3-none-any` wheel. There is no extra to enable and no second
12
12
  package.
13
13
 
14
- **1619 tests pass** under `pytest -m "not gpu"`, with 32 skipped and 5
14
+ **1620 tests pass** under `pytest -m "not gpu"`, with 32 skipped and 5
15
15
  deselected, plus two full-sweep gates behind `PATCHCRAFT_SWEEP_FULL=1`
16
16
  that enumerate all 126,736 legal geometries. CI is green on
17
17
  {Ubuntu, Windows} x {3.12, 3.13, 3.14} on the pure path, plus a two-OS job that
@@ -22,13 +22,15 @@ builds the Rust kernel and runs the whole suite through it.
22
22
 
23
23
  ## What is in flight
24
24
 
25
- **Nothing unreleased.** 0.5.4 closed the nine entries that had accumulated
26
- since 0.5.3: two fixes, `Cache` not expanding a leading `~` and `resize`
27
- raising a raw torch error on 13 of the 25 resample-mode x integer-dtype
28
- combinations, the second of which also uncovered a silent `int32` truncation
29
- older than itself. The other seven are documentation, and they close every
30
- item the 2026-09-04 audit left open, including ADR 0004's step 0 and the
31
- `WeightKind` compatibility sentence.
25
+ **Nothing unreleased.** 0.5.5 is a performance release that changes no bit:
26
+ `stitch` folds its window without a Python loop and skips the fold for the
27
+ uniform window on non-overlapping grids, `reconstruct` builds its count map
28
+ with fewer operations, both divide in place, and `tilings` visits only the
29
+ strides that can work. Each was compared against the 0.5.4 code through
30
+ integer views, 53,716 cases with 0 differences, and the full 126,736-geometry
31
+ sweep passes on both paths. Alongside it, the surface pages lost their
32
+ development history, GUIDE section 8 became "Where this applies today", and
33
+ docs/PERFORMANCE.md was re-measured on the code that ships.
32
34
 
33
35
  ## What is blocked, and on whom
34
36
 
@@ -55,7 +57,7 @@ inventing path security is not this library's job, and no library in the
55
57
  ecosystem does it, verified against `torch.hub.set_dir`, pytest's cacheprovider
56
58
  and pip. Asking the question found the real defect, which was the opposite
57
59
  shape: `Cache` was missing the one thing all of them do, so `Cache("~/cache")`
58
- created a directory literally named `~`. Fixed in the unreleased set, with no
60
+ created a directory literally named `~`. Fixed in 0.5.4, with no
59
61
  validation added. [SECURITY.md](SECURITY.md) and THEORY §9.5 now say the path
60
62
  is the caller's and that only `~` is expanded.
61
63