patchcraft 0.2.2__tar.gz → 0.5.1__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.
- {patchcraft-0.2.2 → patchcraft-0.5.1}/.gitignore +7 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/CHANGELOG.md +282 -23
- patchcraft-0.5.1/CONTRIBUTING.md +270 -0
- patchcraft-0.5.1/MANIFEST.in +37 -0
- patchcraft-0.2.2/README.pypi.md → patchcraft-0.5.1/PKG-INFO +79 -6
- {patchcraft-0.2.2 → patchcraft-0.5.1}/README.md +37 -7
- {patchcraft-0.2.2 → patchcraft-0.5.1}/README.pt-BR.md +37 -7
- patchcraft-0.2.2/PKG-INFO → patchcraft-0.5.1/README.pypi.md +37 -46
- patchcraft-0.5.1/accel/Cargo.lock +232 -0
- patchcraft-0.5.1/accel/Cargo.toml +32 -0
- patchcraft-0.5.1/accel/README.md +48 -0
- patchcraft-0.5.1/accel/src/kernel.rs +252 -0
- patchcraft-0.5.1/accel/src/lib.rs +88 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/AUXILIARY.md +13 -8
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/GUIDE.md +47 -33
- patchcraft-0.5.1/docs/PERFORMANCE.md +116 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/ROADMAP.md +2 -1
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/SCOPE.md +17 -11
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/STUDIES/2026-08-04-patch-techniques.md +2 -1
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/THEORY.md +6 -5
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/USAGE.md +16 -8
- patchcraft-0.5.1/docs/superpowers/plans/2026-09-01-fase1-bugs-performance.md +1179 -0
- patchcraft-0.5.1/docs/superpowers/plans/2026-09-01-fase2-acel.md +1643 -0
- patchcraft-0.5.1/docs/superpowers/plans/2026-09-01-fase3-g1-predicado.md +1705 -0
- patchcraft-0.5.1/docs/superpowers/specs/2026-09-01-fase1-bugs-performance-design.md +151 -0
- patchcraft-0.5.1/docs/superpowers/specs/2026-09-01-fase2-acel-design.md +168 -0
- patchcraft-0.5.1/docs/superpowers/specs/2026-09-01-fase3-g1-predicado-design.md +260 -0
- patchcraft-0.5.1/pyproject.toml +130 -0
- patchcraft-0.5.1/setup.cfg +4 -0
- patchcraft-0.5.1/setup.py +81 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/src/patchcraft/__init__.py +17 -1
- patchcraft-0.5.1/src/patchcraft/_accel.py +116 -0
- patchcraft-0.5.1/src/patchcraft/_foldgeom.py +87 -0
- patchcraft-0.5.1/src/patchcraft/_version.py +24 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/src/patchcraft/cache.py +7 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/src/patchcraft/extract.py +32 -1
- {patchcraft-0.2.2 → patchcraft-0.5.1}/src/patchcraft/geometry.py +10 -2
- {patchcraft-0.2.2 → patchcraft-0.5.1}/src/patchcraft/metrics.py +21 -13
- patchcraft-0.5.1/src/patchcraft/reconstruct.py +137 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/src/patchcraft/resize.py +6 -3
- {patchcraft-0.2.2 → patchcraft-0.5.1}/src/patchcraft/stitch.py +102 -112
- patchcraft-0.5.1/src/patchcraft.egg-info/PKG-INFO +188 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/SOURCES.txt +76 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/dependency_links.txt +1 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/requires.txt +13 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/scm_file_list.json +77 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/scm_version.json +8 -0
- patchcraft-0.5.1/src/patchcraft.egg-info/top_level.txt +1 -0
- patchcraft-0.5.1/tests/_rng.py +108 -0
- patchcraft-0.5.1/tests/test_accel.py +223 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_cache.py +22 -0
- patchcraft-0.5.1/tests/test_exactness.py +164 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_extract.py +62 -1
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_geometry.py +39 -11
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_import.py +6 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_metrics.py +47 -0
- patchcraft-0.5.1/tests/test_public_api.py +249 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_reconstruct.py +89 -30
- patchcraft-0.5.1/tests/test_reference.py +92 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_resize.py +11 -0
- patchcraft-0.5.1/tests/test_rng.py +109 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_stitch.py +91 -20
- patchcraft-0.5.1/tools/benchmark.py +165 -0
- patchcraft-0.5.1/tools/check_dist.py +209 -0
- patchcraft-0.5.1/uv.lock +1188 -0
- patchcraft-0.2.2/.github/workflows/release.yml +0 -128
- patchcraft-0.2.2/.github/workflows/test.yml +0 -51
- patchcraft-0.2.2/CONTRIBUTING.md +0 -98
- patchcraft-0.2.2/pyproject.toml +0 -100
- patchcraft-0.2.2/src/patchcraft/reconstruct.py +0 -155
- patchcraft-0.2.2/uv.lock +0 -1041
- {patchcraft-0.2.2 → patchcraft-0.5.1}/.python-version +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/LICENSE +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/ADR/0001-patch-extraction-api.md +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/ADR/0002-patchify-transform.md +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/ADR/0003-reversibility-classes.md +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/docs/FOCO-1.0.md +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/src/patchcraft/pair.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/src/patchcraft/py.typed +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/__init__.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/_datasets.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/conftest.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_datasets_helper.py +0 -0
- {patchcraft-0.2.2 → patchcraft-0.5.1}/tests/test_pair.py +0 -0
|
@@ -3,6 +3,10 @@ __pycache__/
|
|
|
3
3
|
*.py[cod]
|
|
4
4
|
*$py.class
|
|
5
5
|
*.so
|
|
6
|
+
# Built in place by an editable install (setuptools-rust).
|
|
7
|
+
src/patchcraft/_accel_native*
|
|
8
|
+
# Generated at build time by setuptools-scm.
|
|
9
|
+
src/patchcraft/_version.py
|
|
6
10
|
|
|
7
11
|
# Distribution / packaging
|
|
8
12
|
build/
|
|
@@ -53,3 +57,6 @@ runs/
|
|
|
53
57
|
|
|
54
58
|
# Datasets and caches live on Z:
|
|
55
59
|
data/
|
|
60
|
+
|
|
61
|
+
# Rust accelerator build artifacts (accel/Cargo.lock IS committed)
|
|
62
|
+
accel/target/
|
|
@@ -6,8 +6,280 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.1] - 2026-09-02
|
|
10
|
+
|
|
11
|
+
> Numbered `0.6.0` while it was being built. Corrected before release: the
|
|
12
|
+
> middle digit is the compatibility boundary, and this release changes no
|
|
13
|
+
> signature, no name and no output value. See the versioning policy and its
|
|
14
|
+
> errata in [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **The native accelerator ships inside this wheel.** It was going to be a
|
|
19
|
+
second PyPI project, `patchcraft-accel`, reached through
|
|
20
|
+
`pip install patchcraft[accel]`. That project was never published and now
|
|
21
|
+
never will be. A release produces one sdist and six wheels for the single
|
|
22
|
+
`patchcraft` project: five tagged `cp312-abi3-<platform>` with the Rust
|
|
23
|
+
kernel compiled in as `patchcraft._accel_native`, and one `py3-none-any`
|
|
24
|
+
for every other platform. Installers prefer the most specific compatible
|
|
25
|
+
tag, so `pip install patchcraft` picks up the accelerator where one exists
|
|
26
|
+
and the pure wheel everywhere else, with no extra to remember. The pattern
|
|
27
|
+
and its universal fallback follow `coverage`, which publishes the same two
|
|
28
|
+
shapes for one version.
|
|
29
|
+
- The `accel` extra is removed. Nothing depended on it, because it never
|
|
30
|
+
reached the index.
|
|
31
|
+
- The accelerator is now on by default where a wheel carries it, rather than
|
|
32
|
+
opt-in. The two paths are bit-identical by test, and `PATCHCRAFT_ACCEL=0`
|
|
33
|
+
still forces the pure one at runtime.
|
|
34
|
+
- Build backend moves from hatchling to setuptools with setuptools-rust, which
|
|
35
|
+
is what makes one project able to emit both shapes of wheel. `setup.py`
|
|
36
|
+
holds the single decision, driven by `PATCHCRAFT_PURE_PYTHON` and
|
|
37
|
+
`PATCHCRAFT_REQUIRE_EXTENSION`; everything else stays declarative in
|
|
38
|
+
`pyproject.toml`. Installing from the sdist compiles the extension when a
|
|
39
|
+
Rust toolchain is present and degrades to a pure install when it is not.
|
|
40
|
+
- License metadata follows PEP 639: the SPDX expression `MIT` with
|
|
41
|
+
`license-files`, and the superseded `License :: OSI Approved` classifier is
|
|
42
|
+
gone. setuptools rejects carrying both.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- **The version comes from the git tag.** `setuptools-scm` derives it at build
|
|
47
|
+
time, so no file in the source states a version, the release procedure loses
|
|
48
|
+
its bump step, and a tag and a release can no longer disagree.
|
|
49
|
+
`patchcraft.__version__` still works: the build writes a generated
|
|
50
|
+
`_version.py` that `__init__.py` imports, which costs nothing at import time,
|
|
51
|
+
with `importlib.metadata` as a fallback for a tree that was never built.
|
|
52
|
+
Measured before choosing: `importlib.metadata` alone was the wrong default,
|
|
53
|
+
because it took 56 ms on first call and reported a stale version in an
|
|
54
|
+
editable install.
|
|
55
|
+
- `docs/PERFORMANCE.md`, which is where the accelerator's measurements live.
|
|
56
|
+
It carries the test machine, the versions, the date, the reproduction
|
|
57
|
+
command and what the numbers do not say, and its table is pasted from
|
|
58
|
+
`tools/benchmark.py --markdown` rather than retyped.
|
|
59
|
+
- The Rust kernel is now always built in release mode, including in an
|
|
60
|
+
editable install. It was following the build command, so a development
|
|
61
|
+
checkout compiled a debug kernel: measured here, that turned a 14x speedup
|
|
62
|
+
into 2.1x, which made local benchmarks meaningless and made the accelerated
|
|
63
|
+
CI job exercise code no user runs.
|
|
64
|
+
- `patchcraft.__version__` is re-exported explicitly, so `mypy --strict` in a
|
|
65
|
+
consuming project still accepts it. Moving it out of a literal and into an
|
|
66
|
+
import had quietly made it an implicit re-export, which strict mode rejects.
|
|
67
|
+
- `tools/benchmark.py` measures the accelerated fold against the pure one and
|
|
68
|
+
refuses to report a timing unless the two results are bit-identical. The
|
|
69
|
+
numbers in the documentation come from it, and it is the reproduction
|
|
70
|
+
command they cite.
|
|
71
|
+
- Every build checkout uses `fetch-depth: 0`. The default shallow clone hides
|
|
72
|
+
the tags, and setuptools-scm's fallback for that is a silent
|
|
73
|
+
`0.1.dev1+g<sha>`, a version that walks backwards rather than an error.
|
|
74
|
+
- `tools/check_dist.py` grew `--expect-version` and `--require-universal`, and
|
|
75
|
+
it now rejects any artifact carrying a local version segment. PyPI refuses
|
|
76
|
+
those on upload, so this turns a late and confusing server-side rejection
|
|
77
|
+
into an early local one. It also fixes a bug in the job that checks the
|
|
78
|
+
native wheels, which demanded a universal wheel that job never has.
|
|
79
|
+
- Linux aarch64 wheels, which the previous four-target matrix did not cover.
|
|
80
|
+
- `tools/check_dist.py`, run before every upload. It fails the release if a
|
|
81
|
+
platform wheel lost its extension, which `optional=True` would otherwise
|
|
82
|
+
let through as a silently slow wheel, or if the universal wheel gained one.
|
|
83
|
+
- The versioning policy now names the resolver rule it follows, which is that
|
|
84
|
+
`0.y` is the compatibility boundary in Cargo, npm and PEP 440 alike, and it
|
|
85
|
+
carries an errata for the two releases numbered before the rule was written.
|
|
86
|
+
- Version numbers are gone from the documentation surface. The three cover
|
|
87
|
+
pages carry none at all, since the PyPI badge already shows the current
|
|
88
|
+
release, and the manual keeps only the one the citation entry needs. The
|
|
89
|
+
release archaeology those pages used to recite lives here, and the rest is
|
|
90
|
+
in the git history.
|
|
91
|
+
- `MANIFEST.in`, which puts `accel/` in the sdist so the source path can build
|
|
92
|
+
the extension, and keeps `lab/`, `.superpowers/` and cargo's `target/` out.
|
|
93
|
+
|
|
94
|
+
- **Corrected: the hand-rolled and library tile-and-blend results are not
|
|
95
|
+
bit-identical.** The three cover pages and GUIDE section 1 claimed they were,
|
|
96
|
+
and the GUIDE block asserted it. Re-measured: they agree to 1.17e-05 on a
|
|
97
|
+
value in [0, 1], differing on 24500 of 49152 elements. The cause is the 0.3.0
|
|
98
|
+
separable denominator, where `stitch` sums two 1-D window folds while the
|
|
99
|
+
hand-rolled twin folds a replicated 2-D kernel. `stitch(weight="uniform")`
|
|
100
|
+
is still bit-identical to `reconstruct`. Nothing about the round-trip
|
|
101
|
+
contract changed; the documentation was simply asserting more than the code
|
|
102
|
+
delivered.
|
|
103
|
+
- Documentation corrections found by auditing every page against the code:
|
|
104
|
+
the changelog's reference links did not define the two most recent versions;
|
|
105
|
+
`docs/AUXILIARY.md` listed wheel contents that predate three releases;
|
|
106
|
+
`docs/SCOPE.md` still promised "pure-Python single-threaded", which the
|
|
107
|
+
rayon-parallel kernel contradicts; `docs/ROADMAP.md` still named hatchling;
|
|
108
|
+
GUIDE said the library "is not faster", that the no-zstandard branch is
|
|
109
|
+
untested, and that CI is six cells; the cover pages pointed at `USAGE.md` as
|
|
110
|
+
a walkthrough of 20 symbols when it covers 18 and says so. `CONTRIBUTING.md`
|
|
111
|
+
told a contributor to run `pytest` after a `uv sync` that does not install it.
|
|
112
|
+
|
|
113
|
+
### Notes
|
|
114
|
+
|
|
115
|
+
- No signature, no name and no output value changed. The public surface is the
|
|
116
|
+
same 20 names, still pinned by `tests/test_public_api.py`.
|
|
117
|
+
- Measured on torch 2.14, CPU, mean over the dev machine: `reconstruct` at
|
|
118
|
+
3x512x512 p=32 s=16 goes 11.4 ms to 2.5 ms (4.5x), at 3x2048x2048 p=64 s=32
|
|
119
|
+
477.9 ms to 31.8 ms (15.0x); `stitch[hann]` 21.6 ms to 7.0 ms (3.1x) and
|
|
120
|
+
488.7 ms to 38.8 ms (12.6x). The gain is algorithmic rather than a thread
|
|
121
|
+
count: the pure `F.fold` measured between 365 and 465 ms on that largest
|
|
122
|
+
case at 4, 8, 16 and 36 torch threads.
|
|
123
|
+
|
|
124
|
+
## [0.5.0] - 2026-09-02
|
|
125
|
+
|
|
126
|
+
### Added
|
|
127
|
+
|
|
128
|
+
- Python 3.14 is supported and declared. The classifiers list it, the CI
|
|
129
|
+
matrix runs the full suite plus `ruff check` and `mypy --strict` against it
|
|
130
|
+
on Ubuntu and Windows, and all six cells are green. The floor stays at 3.12
|
|
131
|
+
because `cache.py` uses PEP 695 generic syntax, which 3.11 cannot parse.
|
|
132
|
+
|
|
133
|
+
### Changed
|
|
134
|
+
|
|
135
|
+
- `tilings`/`paired_tilings` no longer emit degenerate single-patch overlap
|
|
136
|
+
specs: with one patch the stride is unobservable and the spec duplicated
|
|
137
|
+
the exact tile. `tilings((28, 28), allow_overlap=True)` now returns 73
|
|
138
|
+
specs instead of 100; `paired_tilings((14, 14), (28, 28), allow_overlap=True)`
|
|
139
|
+
returns 27 instead of 40.
|
|
140
|
+
- The development lock moved to torch 2.14.0 and torchvision 0.29.0, with the
|
|
141
|
+
linters and the rest of the toolchain along with it. The full
|
|
142
|
+
126,736-geometry exactness sweep passes on torch 2.14, so the count-map
|
|
143
|
+
contract is unchanged by the upgrade. Runtime requirements are untouched and
|
|
144
|
+
stay at `torch>=2.6`.
|
|
145
|
+
|
|
9
146
|
### Fixed
|
|
10
147
|
|
|
148
|
+
- **Public retraction: the documented exactness boundary was wrong.** The old
|
|
149
|
+
wording (`k_max <= 4`, "`stride == patch_size / 2`", "within ~1 ULP" outside
|
|
150
|
+
the rule) was measured false: the error grows with the pixel's coverage
|
|
151
|
+
count (up to 19 ULP at count 81 in float32). The correct contract
|
|
152
|
+
(ADR 0003): the `extract`/`reconstruct` round trip is bit-exact iff every
|
|
153
|
+
value of the overlap count map is a power of two, which is always true
|
|
154
|
+
at `stride == patch_size`, and outside the rule the per-pixel error is
|
|
155
|
+
bounded by `(k+1)·eps·|v|`, with `k` the pixel's coverage count.
|
|
156
|
+
Docstrings and docs (SCOPE, THEORY, GUIDE, USAGE, READMEs) now state this
|
|
157
|
+
form; THEORY §9.1 records the extract-truncates / reconstruct-rejects
|
|
158
|
+
asymmetry.
|
|
159
|
+
- Packaging: the sdist no longer carries `.superpowers/`, an untracked
|
|
160
|
+
directory of agent tooling that hatchling swept in (62 files). The same
|
|
161
|
+
class of leak as the `lab/` files that reached PyPI in 0.2.0.
|
|
162
|
+
- Documentation: GUIDE section 6 still printed the pre-0.5.0 enumeration
|
|
163
|
+
output and described the single-patch duplicates as an open wart; section 7
|
|
164
|
+
asserted `__version__ == "0.4.0"`; section 8 and the BibTeX entry quoted
|
|
165
|
+
0.4.0 and its test counts. All four now match the release.
|
|
166
|
+
- Prose: the em-dashes that entered the docstrings, docs and tests during
|
|
167
|
+
0.3.0 to 0.5.0 are rewritten as ordinary sentences, per the project's
|
|
168
|
+
writing rule.
|
|
169
|
+
- Test suite: round-trip assertions now run on seeded full-mantissa noise
|
|
170
|
+
(integer ramps and widened-float32 data could mask ULP-level errors); a
|
|
171
|
+
falsification suite (`tests/test_exactness.py`) enumerates the 126,736
|
|
172
|
+
legal geometries, samples 256 (seeded; full sweep via
|
|
173
|
+
`PATCHCRAFT_SWEEP_FULL=1`), and pins both halves of the predicate; a naive
|
|
174
|
+
loop-based reference (`tests/test_reference.py`) cross-checks the fast
|
|
175
|
+
paths; the 20-name public surface is frozen by `tests/test_public_api.py`;
|
|
176
|
+
the no-`zstandard` cache branch is now covered.
|
|
177
|
+
|
|
178
|
+
### Notes
|
|
179
|
+
|
|
180
|
+
- No signature changed. The public surface is exactly the 20 names of 0.4.0,
|
|
181
|
+
now pinned by test.
|
|
182
|
+
|
|
183
|
+
## [0.4.0] - 2026-09-01
|
|
184
|
+
|
|
185
|
+
> Source milestone, never uploaded to PyPI. Its contents reached
|
|
186
|
+
> users inside 0.5.0. See the versioning policy in
|
|
187
|
+
> [`CONTRIBUTING.md`](CONTRIBUTING.md) for why the number was minted
|
|
188
|
+
> early and why that no longer happens.
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
### Added
|
|
192
|
+
|
|
193
|
+
- Optional native accelerator `patchcraft-accel` (`pip install patchcraft[accel]`):
|
|
194
|
+
a Rust/pyo3 kernel for the overlap fold of `reconstruct`/`stitch`. Prebuilt
|
|
195
|
+
abi3 wheels (Python 3.12+) for Windows x64, Linux x86_64 (manylinux), macOS
|
|
196
|
+
arm64 and macOS x86_64; self-contained, no system dependencies, no torch
|
|
197
|
+
linkage. When it is absent, ABI-mismatched, ineligible (CUDA/integer
|
|
198
|
+
tensors), or disabled via `PATCHCRAFT_ACCEL=0`, every path falls back
|
|
199
|
+
silently to pure torch.
|
|
200
|
+
- `patchcraft.accel_available()` reports whether the accelerator is active.
|
|
201
|
+
|
|
202
|
+
### Performance
|
|
203
|
+
|
|
204
|
+
- Overlap fold with the accelerator, mean of `lab/bench_phase2.py` on the
|
|
205
|
+
dev machine: reconstruct 3x512x512 p=32 s=16 17.727 ms -> 2.609 ms (6.80x);
|
|
206
|
+
stitch[hann] same geometry 23.714 ms -> 8.894 ms (2.67x); 3x2048x2048 p=64
|
|
207
|
+
s=32 reconstruct 469.814 ms -> 32.458 ms (14.47x). The accelerated `stitch`
|
|
208
|
+
also skips the full pre-multiply pass over the patches tensor.
|
|
209
|
+
|
|
210
|
+
### Notes
|
|
211
|
+
|
|
212
|
+
- Numeric equivalence accel vs pure: bit-exact (`torch.equal`) across the
|
|
213
|
+
test grid (overlap x rectangular x stride∤patch x C in {1,3,4} x f32/f64).
|
|
214
|
+
- Linux validation: wheel
|
|
215
|
+
`patchcraft_accel-0.1.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl`
|
|
216
|
+
installed and the test suite passed accelerated on Ubuntu 26.04 (WSL),
|
|
217
|
+
built via the manylinux maturin container.
|
|
218
|
+
|
|
219
|
+
## [0.3.0] - 2026-09-01
|
|
220
|
+
|
|
221
|
+
> Source milestone, never uploaded to PyPI. Its contents reached
|
|
222
|
+
> users inside 0.5.0. See the versioning policy in
|
|
223
|
+
> [`CONTRIBUTING.md`](CONTRIBUTING.md) for why the number was minted
|
|
224
|
+
> early and why that no longer happens.
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
### Performance
|
|
228
|
+
|
|
229
|
+
- `extract` uses a strided-window view (`Tensor.unfold`) for `dilation == 1`,
|
|
230
|
+
one copy instead of im2col + permute-contiguous: 13-21x faster on CPU,
|
|
231
|
+
bit-exact. `dilation > 1` still uses `F.unfold`.
|
|
232
|
+
- `reconstruct` skips `F.fold` entirely on non-overlapping grids (a pure
|
|
233
|
+
rearrangement, 27x faster on CPU) and computes the overlap count map in
|
|
234
|
+
closed form O(H+W) instead of folding a tensor of ones.
|
|
235
|
+
- `stitch` builds its denominator from two 1-D window folds (the kernels are
|
|
236
|
+
separable) instead of a second 2-D `F.fold`. `uniform` stays bit-exact vs
|
|
237
|
+
`reconstruct`; `hann`/`gaussian` may differ by ULPs (summation order).
|
|
238
|
+
- `patch_metrics`/`per_patch_mse` materialize one f64 tensor instead of
|
|
239
|
+
three and sync the device once instead of three times.
|
|
240
|
+
|
|
241
|
+
### Fixed
|
|
242
|
+
|
|
243
|
+
- `resize` with `backend="pil"` clamped integer tensors to the uint8 range
|
|
244
|
+
before casting; values outside [0, 255] used to wrap (300 became 44).
|
|
245
|
+
- Removed the machine-local `cache_dir = "Z:\caches\pytest"` from
|
|
246
|
+
`pyproject.toml` (created a literal `Z:\caches\...` directory on
|
|
247
|
+
Linux/macOS).
|
|
248
|
+
|
|
249
|
+
### Documentation
|
|
250
|
+
|
|
251
|
+
- Corrected stale 0.2.1 version strings on all cover pages and in GUIDE.
|
|
252
|
+
- Corrected the gaussian-kernel floor claim in `stitch` (exp(-4) at corners,
|
|
253
|
+
not exp(-2) everywhere).
|
|
254
|
+
- Corrected the bit-exactness rule in THEORY/USAGE (power-of-two count map).
|
|
255
|
+
- SCOPE §4.4 no longer claims `reconstruct` lacks a dtype guard.
|
|
256
|
+
- CHANGELOG: link targets for [Unreleased], [0.2.2], [0.2.1].
|
|
257
|
+
|
|
258
|
+
### Internal
|
|
259
|
+
|
|
260
|
+
- `reconstruct`/`stitch` share one fold-geometry validator
|
|
261
|
+
(`patchcraft._foldgeom.check_fold_geometry`); error messages unchanged.
|
|
262
|
+
|
|
263
|
+
## [0.2.2] - 2026-08-30
|
|
264
|
+
|
|
265
|
+
Documentation release. No behaviour changes, so every call that worked on 0.2.1
|
|
266
|
+
works identically here, and the only file under `src/` that changed is a
|
|
267
|
+
docstring.
|
|
268
|
+
|
|
269
|
+
### Fixed
|
|
270
|
+
|
|
271
|
+
- **`reconstruct`'s docstring claimed the overlap round trip returns the
|
|
272
|
+
original image.** It said each pixel comes back as "the average of all patches
|
|
273
|
+
covering it, the same as the original when patches came from `extract`
|
|
274
|
+
unmodified". The second half is false. Measured on a 16x16 image with
|
|
275
|
+
`patch_size=4, stride=1`, unmodified patches straight from `extract` come back
|
|
276
|
+
with `torch.equal` False and a maximum absolute error of 2.4e-07 in float32.
|
|
277
|
+
|
|
278
|
+
The docstring now states the real rule: the round trip is bit-exact when every
|
|
279
|
+
value in the count map is a power of two, because dividing a float by a power
|
|
280
|
+
of two is the one division that never rounds. It also says that widening the
|
|
281
|
+
dtype does not rescue it, since the deciding axis is the geometry rather than
|
|
282
|
+
the precision.
|
|
11
283
|
- **`pair()`'s docstring double-applied the stride, and could silently
|
|
12
284
|
misplace every patch for a caller who trusted it.** It said patch `k` has
|
|
13
285
|
its top-left at LR pixel `(row * sh_lr, col * sw_lr)`, but `PatchMeta.row`
|
|
@@ -37,32 +309,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
37
309
|
Reported from an API review done for the QSR prototype, which uses patch
|
|
38
310
|
position as a context feature.
|
|
39
311
|
|
|
40
|
-
## [0.2.2] 2026-08-30
|
|
41
|
-
|
|
42
|
-
Documentation release. No behaviour changes, so every call that worked on 0.2.1
|
|
43
|
-
works identically here, and the only file under `src/` that changed is a
|
|
44
|
-
docstring.
|
|
45
|
-
|
|
46
|
-
### Fixed
|
|
47
|
-
|
|
48
|
-
- **`reconstruct`'s docstring claimed the overlap round trip returns the
|
|
49
|
-
original image.** It said each pixel comes back as "the average of all patches
|
|
50
|
-
covering it, the same as the original when patches came from `extract`
|
|
51
|
-
unmodified". The second half is false. Measured on a 16x16 image with
|
|
52
|
-
`patch_size=4, stride=1`, unmodified patches straight from `extract` come back
|
|
53
|
-
with `torch.equal` False and a maximum absolute error of 2.4e-07 in float32.
|
|
54
|
-
|
|
55
|
-
The docstring now states the real rule: the round trip is bit-exact when every
|
|
56
|
-
value in the count map is a power of two, because dividing a float by a power
|
|
57
|
-
of two is the one division that never rounds. It also says that widening the
|
|
58
|
-
dtype does not rescue it, since the deciding axis is the geometry rather than
|
|
59
|
-
the precision.
|
|
60
|
-
|
|
61
312
|
### Changed
|
|
62
313
|
|
|
63
314
|
- **The README is now a call page and the manual moved to `docs/GUIDE.md`.** The
|
|
64
315
|
README had reached 864 lines, which made it a manual printed on the front
|
|
65
|
-
door. It is
|
|
316
|
+
door. It is 156 lines now, and it answers what the library is, where you are
|
|
66
317
|
getting into, and which of `reconstruct` or `stitch` you want. Nothing was
|
|
67
318
|
discarded: every measurement, table and long example moved into the guide,
|
|
68
319
|
which gained its own table of contents.
|
|
@@ -233,7 +484,7 @@ kernel so each pixel "trusts" patches closer to its center more.
|
|
|
233
484
|
on overlap). With `"hann"` it strongly suppresses seams at the
|
|
234
485
|
cost of zeroing image corners that fall on Hann's edge-weight-zero
|
|
235
486
|
region (documented artifact). With `"gaussian"`
|
|
236
|
-
(`sigma = max(1,
|
|
487
|
+
(per-axis `sigma = max(1.0, n / 4.0)`) it blends smoothly with no
|
|
237
488
|
corner artifact.
|
|
238
489
|
|
|
239
490
|
Floating-point patches only, because window kernels are float-valued and we
|
|
@@ -336,5 +587,13 @@ First public release. Public API stable; signatures will only change in 1.x.
|
|
|
336
587
|
- [`README.md`](README.md) covers installation, the car-vs-track metaphor,
|
|
337
588
|
validation lab.
|
|
338
589
|
|
|
590
|
+
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.1...HEAD
|
|
591
|
+
[0.5.1]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.1
|
|
592
|
+
[0.5.0]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.0
|
|
593
|
+
|
|
594
|
+
<!-- 0.4.0 and 0.3.0 are deliberately unlinked: they are source milestones that
|
|
595
|
+
were never tagged and never released, as their own entries say. -->
|
|
596
|
+
[0.2.2]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.2.2
|
|
597
|
+
[0.2.1]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.2.1
|
|
339
598
|
[0.2.0]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.2.0
|
|
340
599
|
[0.1.0]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.1.0
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Contributing to PatchCraft
|
|
2
|
+
|
|
3
|
+
If you've cloned this repo and want to run the test suite, understand how the project is laid out, or follow the validation conventions, this is the page. For installation and usage, see the [README](README.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Run tests
|
|
8
|
+
|
|
9
|
+
The test runner and the linters live in the `dev` extra, so a plain `uv sync`
|
|
10
|
+
does not install them:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
uv sync --locked --extra cache --extra dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
pytest
|
|
18
|
+
pytest -m "not gpu" # skip GPU-requiring tests
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
CI runs the full suite on every push and PR. See [`.github/workflows/test.yml`](.github/workflows/test.yml). Matrix is `{ubuntu-latest, windows-latest} × {python 3.12, 3.13, 3.14}` for the pure path, plus a second job on both operating systems for the accelerated one. To run the same checks locally before pushing:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
ruff check src tests
|
|
25
|
+
mypy --strict src
|
|
26
|
+
pytest -m "not gpu"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## The two paths
|
|
32
|
+
|
|
33
|
+
The overlapping fold in `reconstruct` and `stitch` has a Rust implementation
|
|
34
|
+
in [`accel/`](accel/), compiled into the wheel on the platforms CI builds for.
|
|
35
|
+
Everything else runs the same operation in torch. The two are bit-identical by
|
|
36
|
+
test, so a contribution has to keep both green rather than pick one.
|
|
37
|
+
|
|
38
|
+
Which one your checkout runs depends on whether the extension got built:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
python -c "import patchcraft; print(patchcraft.accel_available())"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`uv sync --extra cache --extra dev` builds it when a Rust toolchain is on
|
|
45
|
+
PATH, always in release mode. Two environment variables override that, and CI
|
|
46
|
+
sets both explicitly rather than relying on what happens to be installed:
|
|
47
|
+
|
|
48
|
+
| Variable | Effect |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `PATCHCRAFT_PURE_PYTHON=1` | Skip the extension. How the universal wheel is built. |
|
|
51
|
+
| `PATCHCRAFT_REQUIRE_EXTENSION=1` | Make a Rust build failure fatal instead of degrading to the pure path. |
|
|
52
|
+
| `PATCHCRAFT_ACCEL=0` | Runtime only: ignore an extension that is already built. |
|
|
53
|
+
|
|
54
|
+
The Rust kernel has its own tests, which need no Python at all:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
cargo test --manifest-path accel/Cargo.toml
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
One local wrinkle worth knowing. If your checkout sits under a path with
|
|
61
|
+
non-ASCII characters, `setuptools-rust` mis-decodes the artifact path that
|
|
62
|
+
cargo reports and the copy step fails. Point the build at an ASCII directory
|
|
63
|
+
and it works:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
CARGO_TARGET_DIR=/tmp/pc-target uv sync --extra cache --extra dev
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Layout
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
PatchCraft/
|
|
75
|
+
├── pyproject.toml package metadata, build backend, cibuildwheel targets
|
|
76
|
+
├── setup.py the one build decision: with or without the Rust extension
|
|
77
|
+
├── tools/check_dist.py gate: extension present, versions agree, tag respected
|
|
78
|
+
├── tools/benchmark.py accelerated vs pure, and proves they agree first
|
|
79
|
+
├── MANIFEST.in what the sdist carries
|
|
80
|
+
├── README.md the call page, canonical, English
|
|
81
|
+
├── README.pt-BR.md the same call page in Portuguese
|
|
82
|
+
├── README.pypi.md the PyPI page (long_description), links absolute
|
|
83
|
+
├── CONTRIBUTING.md this file
|
|
84
|
+
├── CHANGELOG.md Keep-a-Changelog format
|
|
85
|
+
├── LICENSE MIT
|
|
86
|
+
├── .python-version 3.13
|
|
87
|
+
├── .gitignore ignores archive/, venvs, caches, outputs
|
|
88
|
+
├── .github/workflows/
|
|
89
|
+
│ ├── test.yml matrix CI on PRs/main, plus the accelerated job
|
|
90
|
+
│ └── release.yml publishes to PyPI on vX.Y.Z tag push (Trusted Publishing)
|
|
91
|
+
├── src/patchcraft/ library core, one-image-at-a-time primitives
|
|
92
|
+
│ ├── __init__.py re-exports the full public API and the generated version
|
|
93
|
+
│ ├── extract.py patches via strided view or F.unfold; Patchify (ADR 0002)
|
|
94
|
+
│ ├── reconstruct.py inverse via F.fold + count map, closed-form fast path
|
|
95
|
+
│ ├── stitch.py weighted reassembly for modified patches
|
|
96
|
+
│ ├── geometry.py pre-flight: num_patches, tilings, scale_factor, paired_tilings
|
|
97
|
+
│ ├── metrics.py patch_metrics, per_patch_mse, per_patch_psnr
|
|
98
|
+
│ ├── pair.py LR↔HR pairing; PatchPair, PatchMeta
|
|
99
|
+
│ ├── resize.py resize with PIL or torch backends
|
|
100
|
+
│ ├── cache.py content-addressed disk cache
|
|
101
|
+
│ ├── _accel.py bridge to the optional native accelerator, falls back silently
|
|
102
|
+
│ └── _foldgeom.py shared fold-geometry validation for reconstruct and stitch
|
|
103
|
+
├── accel/ the Rust crate compiled into this wheel (pyo3, setuptools-rust)
|
|
104
|
+
│ ├── Cargo.toml crate metadata; the version there is internal
|
|
105
|
+
│ ├── src/kernel.rs the gather-fold kernel, pure Rust, cargo-tested
|
|
106
|
+
│ ├── src/lib.rs pyo3 glue, exports patchcraft._accel_native
|
|
107
|
+
│ └── README.md how to build it, and how to check it is active
|
|
108
|
+
├── tests/ pytest suite (contract tests for src/)
|
|
109
|
+
│ ├── test_extract.py extract + Patchify
|
|
110
|
+
│ ├── test_reconstruct.py
|
|
111
|
+
│ ├── test_stitch.py
|
|
112
|
+
│ ├── test_geometry.py num_patches + tilings + scale_factor + paired_tilings
|
|
113
|
+
│ ├── test_metrics.py
|
|
114
|
+
│ ├── test_pair.py
|
|
115
|
+
│ ├── test_resize.py
|
|
116
|
+
│ ├── test_cache.py
|
|
117
|
+
│ ├── test_exactness.py falsifies the count-map predicate over the legal space
|
|
118
|
+
│ ├── test_reference.py naive loop-based reference for the fast paths
|
|
119
|
+
│ ├── test_public_api.py freezes the 20 names, their signatures and carrier fields
|
|
120
|
+
│ ├── test_accel.py native path, skipped unless the extension was built
|
|
121
|
+
│ ├── test_datasets_helper.py label_subset
|
|
122
|
+
│ ├── test_import.py
|
|
123
|
+
│ ├── _rng.py audited round-trip helpers (data generation, bit equality)
|
|
124
|
+
│ ├── test_rng.py tests for those helpers
|
|
125
|
+
│ └── _datasets.py dev-only fixtures (MNIST, etc), NOT public API
|
|
126
|
+
├── lab/ ephemeral experiments; see lab/README.md
|
|
127
|
+
│ ├── README.md bench rules (tracked; the scripts themselves are not)
|
|
128
|
+
│ └── .gitignore ignores everything else (tracked)
|
|
129
|
+
├── docs/
|
|
130
|
+
│ ├── GUIDE.md the manual, every README claim measured with its output
|
|
131
|
+
│ ├── PERFORMANCE.md what the accelerator is worth, and how to re-measure
|
|
132
|
+
│ ├── USAGE.md REPL walkthrough of every public symbol (behind, see B4)
|
|
133
|
+
│ ├── SCOPE.md responsibilities matrix + parallelization analysis
|
|
134
|
+
│ ├── AUXILIARY.md tests/_datasets, lab/, Z:\ conventions (NOT part of the wheel)
|
|
135
|
+
│ ├── THEORY.md distilled design + §9 condition contract; §0 binding scope
|
|
136
|
+
│ ├── ROADMAP.md milestone plan
|
|
137
|
+
│ ├── FOCO-1.0.md what 1.0 freezes and the blockers in the way
|
|
138
|
+
│ ├── ADR/ one file per decision that shaped the API
|
|
139
|
+
│ ├── STUDIES/ background reading behind those decisions
|
|
140
|
+
│ └── superpowers/ the design specs and implementation plans per work phase
|
|
141
|
+
└── archive/ reference-only; gitignored (pruned 2026-05-17, only HISTORY.md kept)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Validation lab
|
|
147
|
+
|
|
148
|
+
The library is "one image in, one tensor out" by design, but you only know it works once you run it end-to-end on real images. That happens in two places, neither of which is part of the shipped package:
|
|
149
|
+
|
|
150
|
+
- [`tests/`](tests/) holds the formal pytest suite that defines the contract from [`docs/THEORY.md`](docs/THEORY.md) §9.
|
|
151
|
+
- [`lab/`](lab/) holds ephemeral scripts and notebooks for fast hypothesis-checking. See [`lab/README.md`](lab/README.md) for the bench rules; outputs go to `Z:\outputs\patchcraft\` (off-tree).
|
|
152
|
+
|
|
153
|
+
Datasets used by tests/lab are downloaded lazily into `Z:\caches\datasets\<name>\` on first use; they do not ship with the package and are never bundled into the wheel.
|
|
154
|
+
|
|
155
|
+
The dev framework that makes this all work (fixtures, dataset helpers, `Z:\` conventions) is documented in [`docs/AUXILIARY.md`](docs/AUXILIARY.md).
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Versioning policy
|
|
160
|
+
|
|
161
|
+
The scheme is [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html),
|
|
162
|
+
expressed in the [PEP 440](https://peps.python.org/pep-0440/) form that PyPI
|
|
163
|
+
accepts. Two rules matter more than the arithmetic, because both were broken
|
|
164
|
+
once and both cost work to undo.
|
|
165
|
+
|
|
166
|
+
**The number names a published artifact, not a unit of work.** Finishing a
|
|
167
|
+
phase, merging a branch and landing a refactor are not releases, and none of
|
|
168
|
+
them touches `__version__`. Everything between releases accumulates under
|
|
169
|
+
`[Unreleased]` in [`CHANGELOG.md`](CHANGELOG.md), and the number is assigned
|
|
170
|
+
in the release commit itself. Versions 0.3.0 and 0.4.0 came from the opposite
|
|
171
|
+
habit: each internal work phase minted a minor, so three numbers existed
|
|
172
|
+
before any of them reached PyPI.
|
|
173
|
+
|
|
174
|
+
**A published version is frozen.** PyPI refuses a filename it has already
|
|
175
|
+
seen, so a version that shipped can only be superseded, never edited. That is
|
|
176
|
+
also why a failed pipeline never costs a number: `skip-existing` makes the
|
|
177
|
+
upload a no-op and the same tag can be re-run.
|
|
178
|
+
|
|
179
|
+
**While the project is pre-1.0, the middle digit is the compatibility
|
|
180
|
+
boundary.** SemVer itself leaves `0.y.z` open, saying only that "anything MAY
|
|
181
|
+
change at any time", so the meaning comes from the resolvers instead, and they
|
|
182
|
+
agree: Cargo expands `^0.2.3` to `>=0.2.3, <0.3.0`, npm does the same, and
|
|
183
|
+
PEP 440's `~=0.5.0` means `>=0.5.0, <0.6.0`. In every one of them a `0.y` bump
|
|
184
|
+
is the announcement that something may break, and a `0.y.z` bump is the
|
|
185
|
+
promise that nothing does.
|
|
186
|
+
|
|
187
|
+
That fixes what each digit is for here:
|
|
188
|
+
|
|
189
|
+
| Change | Bump | Why |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| An output value, a signature, a name or documented behaviour changes | `0.Y.0` | Resolvers stop at the y boundary, so this is where a break belongs |
|
|
192
|
+
| New functionality that breaks nothing, or a fix | `0.y.Z` | Anyone pinned to the y-series should get it |
|
|
193
|
+
| Documentation, tests, CI, packaging or tooling only | `0.y.Z` | Still a release if it is published, still no break |
|
|
194
|
+
| Nothing published | nothing | Accumulate under `[Unreleased]` |
|
|
195
|
+
|
|
196
|
+
After 1.0 the ordinary reading applies: breaking is major, additive is minor,
|
|
197
|
+
fixed is patch. What 1.0 freezes is written in
|
|
198
|
+
[`docs/FOCO-1.0.md`](docs/FOCO-1.0.md).
|
|
199
|
+
|
|
200
|
+
### Errata
|
|
201
|
+
|
|
202
|
+
The rule above was written after the fact, and two releases predate it.
|
|
203
|
+
|
|
204
|
+
`0.4.0` should have been `0.3.1`. It added `accel_available()` and nothing
|
|
205
|
+
else: no signature changed, and the accelerated path was bit-exact against the
|
|
206
|
+
pure one. Additive and non-breaking is a z-bump under the boundary rule, and
|
|
207
|
+
minting a y announced a break that never happened.
|
|
208
|
+
|
|
209
|
+
`0.5.1` was briefly numbered `0.6.0` before release. It changes only how the
|
|
210
|
+
package is built and shipped, so it is a z-bump.
|
|
211
|
+
|
|
212
|
+
Two others were right and are worth recording as the shape to copy. `0.3.0`
|
|
213
|
+
moved `stitch` output by ULPs through a different summation order, which is an
|
|
214
|
+
output change and belongs at a y boundary. `0.5.0` made `tilings` return 73
|
|
215
|
+
specs where it returned 100, which is squarely a break.
|
|
216
|
+
|
|
217
|
+
The accelerator in [`accel/`](accel/) has no version of its own to manage. It
|
|
218
|
+
is compiled into this wheel, so it ships when `patchcraft` ships. The version
|
|
219
|
+
in `accel/Cargo.toml` is internal and nothing reads it; what the Python side
|
|
220
|
+
actually checks is `_ABI_VERSION` in `accel/src/lib.rs`, and a mismatch there
|
|
221
|
+
means a silent fall back to the pure path rather than a crash.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Where the version comes from
|
|
226
|
+
|
|
227
|
+
The git tag, and nowhere else. `setuptools-scm` derives it at build time, so
|
|
228
|
+
no file in the source states a version and there is no literal to forget.
|
|
229
|
+
`v0.5.1` produces `0.5.1`; the leading `v` is stripped for you.
|
|
230
|
+
|
|
231
|
+
At build time setuptools-scm writes `src/patchcraft/_version.py`, which
|
|
232
|
+
`__init__.py` imports and re-exports as `patchcraft.__version__`. That file is
|
|
233
|
+
generated and gitignored, so a checkout that was never built falls back to
|
|
234
|
+
`importlib.metadata` and then to `0+unknown`. The fallback exists so an
|
|
235
|
+
un-built tree still imports; it is not a supported way to learn the version.
|
|
236
|
+
|
|
237
|
+
Anything other than a clean tree sitting exactly on a tag produces a version
|
|
238
|
+
with a local segment, like `0.5.2.dev0+gb4edfd81.d20260902` from a dirty tree
|
|
239
|
+
or `0.1.dev1+gb4edfd81` from a checkout whose tags were never fetched. PyPI
|
|
240
|
+
refuses local segments outright, so such a build can never be published by
|
|
241
|
+
accident, and `tools/check_dist.py` rejects it before the upload rather than
|
|
242
|
+
letting the upload fail.
|
|
243
|
+
|
|
244
|
+
That last point is why every `actions/checkout` that builds uses
|
|
245
|
+
`fetch-depth: 0`. The default shallow clone hides the tags, and the failure is
|
|
246
|
+
silent: a version that walks backwards rather than an error.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Releasing (maintainer only)
|
|
251
|
+
|
|
252
|
+
1. Close the `[Unreleased]` section in [`CHANGELOG.md`](CHANGELOG.md) as `[X.Y.Z] YYYY-MM-DD`.
|
|
253
|
+
2. Update the `version` field of the BibTeX entry in [`docs/GUIDE.md`](docs/GUIDE.md) section 9. It is the one version string left in the documentation, because a citation needs a concrete one.
|
|
254
|
+
3. Update [`docs/ROADMAP.md`](docs/ROADMAP.md) milestone checkboxes.
|
|
255
|
+
4. Commit: `release: vX.Y.Z`.
|
|
256
|
+
5. Tag + push: `git tag -a vX.Y.Z -m "..."` then `git push origin vX.Y.Z`.
|
|
257
|
+
6. `release.yml` fires automatically: validates, builds, publishes to PyPI via Trusted Publishing, then creates the GitHub Release with every artifact attached.
|
|
258
|
+
|
|
259
|
+
There is no version to bump in step 4, and no way for a tag and a release to
|
|
260
|
+
disagree. The `validate` job still cross-checks that setuptools-scm derived
|
|
261
|
+
exactly what the tag says, which catches a dirty or shallow checkout before
|
|
262
|
+
anything is built.
|
|
263
|
+
|
|
264
|
+
One release produces one sdist and six wheels: five `cp312-abi3-<platform>`
|
|
265
|
+
wheels with the Rust extension inside, and one `py3-none-any` wheel for
|
|
266
|
+
everywhere else. `tools/check_dist.py` runs before the upload and fails the
|
|
267
|
+
release if a platform wheel lost its extension, if the universal wheel gained
|
|
268
|
+
one, if the artifacts disagree about the version, or if any of them is not the
|
|
269
|
+
version the tag names. Publishing is a single project, so there is nothing to
|
|
270
|
+
register on PyPI beyond the publisher that already exists.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# The sdist is the reproducible source snapshot: the product (`src/`, `accel/`)
|
|
2
|
+
# plus the track that proves it works (`tests/`, `docs/`). `accel/` has to be
|
|
3
|
+
# here, because an sdist build compiles it when a Rust toolchain is present.
|
|
4
|
+
#
|
|
5
|
+
# The `prune` lines below are load-bearing rather than decorative, and the
|
|
6
|
+
# reason changed with setuptools-scm. Its file finder offers every git-tracked
|
|
7
|
+
# file to the sdist, so this file stopped being an allowlist and became a
|
|
8
|
+
# blocklist over everything git knows about. Measured: with no prune for it,
|
|
9
|
+
# `.vscode/settings.json` reached the sdist.
|
|
10
|
+
#
|
|
11
|
+
# Note that `lab/` is only *mostly* ignored: `lab/README.md` and
|
|
12
|
+
# `lab/.gitignore` are tracked, so the finder offers them and the prune is what
|
|
13
|
+
# stops them. The rest of `lab/` is scratch work that no commit can reproduce,
|
|
14
|
+
# and patchcraft-0.2.0.tar.gz shipped three such files to PyPI.
|
|
15
|
+
#
|
|
16
|
+
# Adding a tracked directory to this repository ships it to PyPI by default.
|
|
17
|
+
# `tools/check_dist.py` holds the matching allowlist and will fail the release
|
|
18
|
+
# for anything unexpected, which is the reminder to come back here.
|
|
19
|
+
|
|
20
|
+
include LICENSE
|
|
21
|
+
include CHANGELOG.md CONTRIBUTING.md
|
|
22
|
+
include README.md README.pt-BR.md README.pypi.md
|
|
23
|
+
include pyproject.toml setup.py uv.lock .python-version
|
|
24
|
+
|
|
25
|
+
recursive-include src *.py
|
|
26
|
+
include src/patchcraft/py.typed
|
|
27
|
+
recursive-include tests *.py
|
|
28
|
+
recursive-include docs *.md
|
|
29
|
+
recursive-include accel *.rs *.toml *.lock *.md
|
|
30
|
+
prune accel/target
|
|
31
|
+
recursive-include tools *.py
|
|
32
|
+
|
|
33
|
+
prune lab
|
|
34
|
+
prune archive
|
|
35
|
+
prune .superpowers
|
|
36
|
+
prune .github
|
|
37
|
+
prune .vscode
|