patchcraft 0.5.3__tar.gz → 0.5.4__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.5.3 → patchcraft-0.5.4}/AGENTS.md +24 -1
- {patchcraft-0.5.3 → patchcraft-0.5.4}/CHANGELOG.md +126 -1
- {patchcraft-0.5.3 → patchcraft-0.5.4}/CITATION.cff +2 -2
- {patchcraft-0.5.3 → patchcraft-0.5.4}/CONTRIBUTING.md +7 -2
- {patchcraft-0.5.3 → patchcraft-0.5.4}/MAP.md +3 -3
- {patchcraft-0.5.3/src/patchcraft.egg-info → patchcraft-0.5.4}/PKG-INFO +2 -2
- {patchcraft-0.5.3 → patchcraft-0.5.4}/README.md +1 -1
- {patchcraft-0.5.3 → patchcraft-0.5.4}/README.pt-BR.md +1 -1
- {patchcraft-0.5.3 → patchcraft-0.5.4}/README.pypi.md +1 -1
- {patchcraft-0.5.3 → patchcraft-0.5.4}/SECURITY.md +13 -6
- patchcraft-0.5.4/STATUS.md +97 -0
- patchcraft-0.5.4/docs/ADR/0003-reversibility-classes.md +139 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/ADR/0004-precision-and-effort-parameters.md +61 -78
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/FOCO-1.0.md +1 -1
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/GUIDE.md +4 -4
- patchcraft-0.5.4/docs/STUDIES/2026-09-03-precision-measurements.md +98 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/THEORY.md +3 -2
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/_version.py +3 -3
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/cache.py +5 -1
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/geometry.py +3 -1
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/reconstruct.py +5 -2
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/resize.py +33 -3
- {patchcraft-0.5.3 → patchcraft-0.5.4/src/patchcraft.egg-info}/PKG-INFO +2 -2
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft.egg-info/SOURCES.txt +1 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft.egg-info/scm_file_list.json +1 -0
- patchcraft-0.5.4/src/patchcraft.egg-info/scm_version.json +8 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_cache.py +29 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_resize.py +73 -0
- patchcraft-0.5.3/STATUS.md +0 -85
- patchcraft-0.5.3/docs/ADR/0003-reversibility-classes.md +0 -108
- patchcraft-0.5.3/src/patchcraft.egg-info/scm_version.json +0 -8
- {patchcraft-0.5.3 → patchcraft-0.5.4}/.gitignore +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/.pre-commit-config.yaml +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/.python-version +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/CODE_OF_CONDUCT.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/LICENSE +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/MANIFEST.in +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/accel/Cargo.lock +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/accel/Cargo.toml +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/accel/README.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/accel/src/kernel.rs +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/accel/src/lib.rs +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/ADR/0001-patch-extraction-api.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/ADR/0002-patchify-transform.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/AUXILIARY.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/PERFORMANCE.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/ROADMAP.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/SCOPE.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/STUDIES/2026-08-04-patch-techniques.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/STUDIES/2026-09-03-strata-conformity.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/USAGE.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/design/2026-09-01-fase1-bugs-performance.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/design/2026-09-01-fase2-acelerador.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/docs/design/2026-09-01-fase3-predicado.md +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/pyproject.toml +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/setup.cfg +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/setup.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/__init__.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/_accel.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/_foldgeom.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/extract.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/metrics.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/pair.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/py.typed +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft/stitch.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft.egg-info/dependency_links.txt +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft.egg-info/requires.txt +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/src/patchcraft.egg-info/top_level.txt +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/__init__.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/_datasets.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/_rng.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/conftest.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_accel.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_citation.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_datasets_helper.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_docs_usage.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_exactness.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_extract.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_geometry.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_import.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_metrics.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_pair.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_public_api.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_reconstruct.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_reference.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_rng.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tests/test_stitch.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tools/benchmark.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/tools/check_dist.py +0 -0
- {patchcraft-0.5.3 → patchcraft-0.5.4}/uv.lock +0 -0
|
@@ -89,7 +89,30 @@ literal directory of that name on Linux.
|
|
|
89
89
|
`tools/check_dist.py` holds the matching allowlist. Adding a top-level
|
|
90
90
|
directory means updating both. The gate will tell you; do not silence it.
|
|
91
91
|
|
|
92
|
-
### I10.
|
|
92
|
+
### I10. An ADR carries the choice, not the research behind it
|
|
93
|
+
|
|
94
|
+
Before writing one, split what is being said into what is **determined** and
|
|
95
|
+
what is **chosen**, and put only the second in the ADR.
|
|
96
|
+
|
|
97
|
+
A name is determined: which identifier a parameter carries is a fact about the
|
|
98
|
+
ecosystem the caller already imports, and picking one by taste is a guess made
|
|
99
|
+
permanent by a frozen surface. A measurement is determined. A benchmark table is
|
|
100
|
+
determined. Those belong in `docs/STUDIES/`, dated, with the ADR linking to
|
|
101
|
+
them.
|
|
102
|
+
|
|
103
|
+
What is chosen is whether to do the thing at all, when, and what it costs the
|
|
104
|
+
surface. If that part comes out empty, it was never an ADR: it was a finding,
|
|
105
|
+
and a study is where it goes.
|
|
106
|
+
|
|
107
|
+
Both ADRs written in 2026-09 failed this on the first draft, in the same way
|
|
108
|
+
and for the same reason, which is that writing down everything you learned feels
|
|
109
|
+
like thoroughness and reads as a lab notebook. ADR 0003 mixed a rule with a
|
|
110
|
+
vocabulary for six transforms that never shipped; ADR 0004 mixed the names,
|
|
111
|
+
which precedent decides, with whether to spend a frozen surface on them, which
|
|
112
|
+
only the owner decides. Both came out clearer once separated, and both were
|
|
113
|
+
caught by the owner asking what the document was for.
|
|
114
|
+
|
|
115
|
+
### I11. Do not invent what already exists
|
|
93
116
|
|
|
94
117
|
Check for an official or established solution before writing one. The
|
|
95
118
|
accelerator's packaging, the versioning rule, the citation format and the
|
|
@@ -6,6 +6,130 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.4] - 2026-09-04
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **13 of the 25 resample-mode x integer-dtype combinations raised a raw torch
|
|
14
|
+
error** instead of resizing. THEORY §9.4 accepts any integer tensor on the
|
|
15
|
+
`torch` backend and promises interpolation in float with round + clamp on the
|
|
16
|
+
way back, unconditionally. `_resize_torch` guarded the promotion with
|
|
17
|
+
`mode in {"bilinear", "bicubic"}`, so an integer tensor reached
|
|
18
|
+
`F.interpolate` unpromoted under `nearest`, `area` and `nearest-exact` and
|
|
19
|
+
surfaced `NotImplementedError: "adaptive_avg_pool2d" not implemented for
|
|
20
|
+
'Int'`, naming an internal op the caller never called. The outbound half of
|
|
21
|
+
the same function already did the right thing for every integer dtype with no
|
|
22
|
+
condition on the mode, so only the inbound guard was ever wrong.
|
|
23
|
+
|
|
24
|
+
The suite could not catch it because the two parametrizations that would have
|
|
25
|
+
never crossed: `test_torch_accepts_all_resamples` sweeps all five modes on
|
|
26
|
+
`torch.rand`, and every integer test used the default mode. The regression is
|
|
27
|
+
that crossing, 50 cases, plus the upsampling direction because `area` is
|
|
28
|
+
`adaptive_avg_pool2d` and behaves differently going up.
|
|
29
|
+
|
|
30
|
+
Fixing it by promoting everything through `float32` would have traded the
|
|
31
|
+
loud error for the silent truncation this library exists to prevent, so the
|
|
32
|
+
promotion width is now derived from the dtype's range: the narrowest float
|
|
33
|
+
that represents it exactly, `float32` for `uint8`, `int8` and `int16` and
|
|
34
|
+
`float64` for `int32`. That mirrors `patchcraft.metrics`, which already
|
|
35
|
+
promotes to `float64` for the same reason. **This also fixes a silent defect
|
|
36
|
+
that predates the one above**: on the bilinear path an `int32` tensor was
|
|
37
|
+
already being promoted through `float32`, so `2**24 + 1` came back as
|
|
38
|
+
`2**24`. Measured on 2026-09-04, on every mode. `int64` remains the one dtype
|
|
39
|
+
no float covers, and §9.4 now says so rather than implying otherwise.
|
|
40
|
+
|
|
41
|
+
- **`Cache("~/cache")` created a directory literally named `~`** in the working
|
|
42
|
+
directory instead of using the home directory. `Path(root)` was used without
|
|
43
|
+
`expanduser`. Every established library expands and then stops: verified that
|
|
44
|
+
`torch.hub.set_dir` calls `expanduser` and validates nothing, that pytest's
|
|
45
|
+
cacheprovider does not validate, and that pip passes `--cache-dir` through.
|
|
46
|
+
`Cache` now does the same one thing they do.
|
|
47
|
+
|
|
48
|
+
The question that found this was whether to reject a `namespace` containing
|
|
49
|
+
`..`. The answer is no, and the reason is the owner's: it is not this
|
|
50
|
+
library's job to invent path security, and no library in the ecosystem does
|
|
51
|
+
it. What it was missing was the one thing they all do. `SECURITY.md` and
|
|
52
|
+
THEORY §9.5 now say that the path is the caller's and that only `~` is
|
|
53
|
+
expanded.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- The rule that produced the two rescopings is recorded as invariant I10 in
|
|
59
|
+
`AGENTS.md`: an ADR carries the choice, not the research behind it. Before
|
|
60
|
+
writing one, split what is determined from what is chosen and keep only the
|
|
61
|
+
second; if the chosen part comes out empty it was a finding, and a dated
|
|
62
|
+
study is where it goes. Both ADRs written this month failed it on the first
|
|
63
|
+
draft, in the same way, and both were caught by the owner asking what the
|
|
64
|
+
document was for. `docs/ADR/0001` and `0002` were checked and do not have the
|
|
65
|
+
problem: they are context, decision, consequences and alternatives, at a
|
|
66
|
+
hundred lines each.
|
|
67
|
+
- **ADR 0004 separates what is determined from what is chosen, and what is left
|
|
68
|
+
is nothing.** The owner's point settled the first half: naming is a fact about
|
|
69
|
+
the ecosystem the caller already imports, not a preference of this project,
|
|
70
|
+
and choosing by taste would be a guess made permanent by a frozen surface. So
|
|
71
|
+
the three names are stated as determined by precedent, verified on 2026-09-03
|
|
72
|
+
against the installed libraries: `numpy.sum` and `torch.sum` take `dtype=`,
|
|
73
|
+
`torch.div` takes `rounding_mode=` and documents exactly `None`, `"trunc"` and
|
|
74
|
+
`"floor"`, both `interpolate` and torchvision's `resize` take `antialias=`,
|
|
75
|
+
and `numpy.ufunc.accumulate` exists, which is the collision that rules out
|
|
76
|
+
`accumulate=`.
|
|
77
|
+
|
|
78
|
+
That narrowing left one apparent question, whether to add the knobs before the
|
|
79
|
+
1.0 freeze or after, on the stated ground that afterwards each costs a minor
|
|
80
|
+
release. Applying I10 to it dissolved it, because the premise is false: every
|
|
81
|
+
one of these is a keyword-only argument with a default, so adding one is
|
|
82
|
+
additive and can never be a major, and `reconstruct` has no positional-only
|
|
83
|
+
parameters to make the order matter. Nothing about adding one later is harder
|
|
84
|
+
than adding one now. With the premise gone, YAGNI, the absence of any external
|
|
85
|
+
consumer and Strata §9's default verdict all point the same way, and the ADR's
|
|
86
|
+
own table had already marked every knob "after 1.0" before the question was
|
|
87
|
+
written on top of it. The document now records a design that is not built, and
|
|
88
|
+
stays `Proposed` because none of it is implemented rather than because
|
|
89
|
+
anything is pending.
|
|
90
|
+
- `docs/STUDIES/2026-09-03-precision-measurements.md`. ADR 0004 was carrying
|
|
91
|
+
its own evidence, which made a 227-line document that read as a lab notebook
|
|
92
|
+
where a decision was wanted. The measurements moved here, the ADR kept the
|
|
93
|
+
decision, the names and the order, and it is 176 lines. Each measurement now
|
|
94
|
+
says what it settled, so a number can be checked without reading the argument
|
|
95
|
+
that used it.
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
|
|
99
|
+
- **ADR 0004 step 0 is done**, which the ADR names as the only item that must
|
|
100
|
+
land before 1.0: the `reconstruct` docstring's "widening the dtype does not
|
|
101
|
+
help" now says *input* dtype, and that widening the accumulator does help,
|
|
102
|
+
with the measurement. It was the one live site; earlier counts of seven
|
|
103
|
+
included historical and study files that are not edited.
|
|
104
|
+
- **`WeightKind` is declared an open set** in the GUIDE symbol table and in
|
|
105
|
+
CONTRIBUTING's versioning policy. The choice was made and pinned by test long
|
|
106
|
+
ago; what did not exist was the sentence a consumer reads before writing an
|
|
107
|
+
exhaustive `match`.
|
|
108
|
+
- Four one-line corrections found by the 2026-09-04 audit: FOCO §1 counted
|
|
109
|
+
"four things" over a list of five; MAP and CONTRIBUTING said ten invariants
|
|
110
|
+
after I10 made it eleven; AGENTS.md claimed both rescoped ADRs "shrank by a
|
|
111
|
+
third" when ADR 0003 grew; and `tilings`' docstring promised an ordering by
|
|
112
|
+
`(patch_size, stride)` that the function does not produce. The docstring now
|
|
113
|
+
states the measured order (patch size ascending, exact tile first within each)
|
|
114
|
+
rather than the function changing, because nothing depends on the order and
|
|
115
|
+
a change would be behaviour.
|
|
116
|
+
- **ADR 0003 is accepted, after being rescoped to what it actually governs.**
|
|
117
|
+
Its first draft decided two things at once: a rule about how an exactness
|
|
118
|
+
claim is written, and a three-letter vocabulary plus naming policy for six
|
|
119
|
+
transforms that were under consideration. A month later the rule was
|
|
120
|
+
governing the code and the vocabulary had been used nowhere: `R1`, `R2` and
|
|
121
|
+
`R3` appear in no docstring, no contract section and no page a reader opens,
|
|
122
|
+
and none of rotation, flips, padding, DCT, quantization or dithering shipped.
|
|
123
|
+
What is now accepted is the rule, that an exactness claim is declared per
|
|
124
|
+
regime with a condition the caller can evaluate before calling, with the four
|
|
125
|
+
measured regime axes in its body including the accumulator amendment from
|
|
126
|
+
ADR 0004. The vocabulary is deferred until the first transform that needs it.
|
|
127
|
+
The document also states plainly what its title had obscured: inexact
|
|
128
|
+
operations are not restricted, since `resize` and windowed `stitch` are
|
|
129
|
+
exactly what users come for, and what is forbidden is a docstring implying an
|
|
130
|
+
exactness the operation does not have.
|
|
131
|
+
|
|
132
|
+
|
|
9
133
|
## [0.5.3] - 2026-09-03
|
|
10
134
|
|
|
11
135
|
### Added
|
|
@@ -771,7 +895,8 @@ First public release. Public API stable; signatures will only change in 1.x.
|
|
|
771
895
|
- [`README.md`](README.md) covers installation, the car-vs-track metaphor,
|
|
772
896
|
validation lab.
|
|
773
897
|
|
|
774
|
-
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.
|
|
898
|
+
[Unreleased]: https://github.com/LeoPR/PatchCraft/compare/v0.5.4...HEAD
|
|
899
|
+
[0.5.4]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.4
|
|
775
900
|
[0.5.3]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.3
|
|
776
901
|
[0.5.2]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.2
|
|
777
902
|
[0.5.1]: https://github.com/LeoPR/PatchCraft/releases/tag/v0.5.1
|
|
@@ -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.
|
|
10
|
-
date-released: "2026-09-
|
|
9
|
+
version: "0.5.4"
|
|
10
|
+
date-released: "2026-09-04"
|
|
11
11
|
repository-code: "https://github.com/LeoPR/PatchCraft"
|
|
12
12
|
license: MIT
|
|
13
13
|
abstract: >
|
|
@@ -136,7 +136,7 @@ PatchCraft/
|
|
|
136
136
|
├── README.pypi.md the PyPI page (long_description), links absolute
|
|
137
137
|
├── CONTRIBUTING.md this file
|
|
138
138
|
├── MAP.md one-page wayfinding: the tree, and a want-to/go-to table
|
|
139
|
-
├── AGENTS.md the canonical rules;
|
|
139
|
+
├── AGENTS.md the canonical rules; eleven invariants, each earned
|
|
140
140
|
├── CLAUDE.md thin pointer to AGENTS.md plus tool-specific notes
|
|
141
141
|
├── STATUS.md the snapshot; what is in flight and what waits on whom
|
|
142
142
|
├── CHANGELOG.md Keep-a-Changelog format
|
|
@@ -200,7 +200,7 @@ PatchCraft/
|
|
|
200
200
|
│ ├── THEORY.md distilled design + §9 condition contract; §0 binding scope
|
|
201
201
|
│ ├── ROADMAP.md milestone plan
|
|
202
202
|
│ ├── FOCO-1.0.md what 1.0 freezes and the blockers in the way
|
|
203
|
-
│ ├── ADR/ one file per decision
|
|
203
|
+
│ ├── ADR/ one file per decision; the choice only, evidence in STUDIES/
|
|
204
204
|
│ ├── STUDIES/ background reading behind those decisions
|
|
205
205
|
│ └── design/ one spec per work phase: the alternatives measured and the decision
|
|
206
206
|
├── outreach/ material for presenting the project publicly, not shipped
|
|
@@ -255,6 +255,11 @@ promise that nothing does.
|
|
|
255
255
|
|
|
256
256
|
That fixes what each digit is for here:
|
|
257
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
|
+
|
|
258
263
|
The question that decides it is not "did a returned value change". A bug fix
|
|
259
264
|
changes returned values by definition, and if that forced a `0.Y.0` then a
|
|
260
265
|
patch release could never fix anything. The question is **whether the contract
|
|
@@ -15,7 +15,7 @@ PatchCraft/
|
|
|
15
15
|
│ README.pt-BR.md ........... the same page in Portuguese
|
|
16
16
|
│ README.pypi.md ............ the PyPI page; links absolute, because relative ones break there
|
|
17
17
|
├── MAP.md .................... this file
|
|
18
|
-
├── AGENTS.md ................. the rules, agent-agnostic and canonical;
|
|
18
|
+
├── AGENTS.md ................. the rules, agent-agnostic and canonical; eleven invariants
|
|
19
19
|
├── CLAUDE.md ................. thin; points at AGENTS.md and holds tool-specific notes
|
|
20
20
|
├── STATUS.md ................. the snapshot: where things are now, and who each item waits on
|
|
21
21
|
├── CHANGELOG.md .............. every release, each change with the measurement behind it
|
|
@@ -56,7 +56,7 @@ PatchCraft/
|
|
|
56
56
|
│ ├── AUXILIARY.md .......... test fixtures, lab/, the off-tree conventions
|
|
57
57
|
│ ├── ROADMAP.md ............ milestones, historical
|
|
58
58
|
│ ├── FOCO-1.0.md ........... what 1.0 freezes, and the blockers still in the way
|
|
59
|
-
│ ├── ADR/ .................. one file per decision
|
|
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
|
│
|
|
@@ -84,7 +84,7 @@ PatchCraft/
|
|
|
84
84
|
| Know **whether this library is the right tool** | [docs/SCOPE.md](docs/SCOPE.md) |
|
|
85
85
|
| Know **what the native accelerator is worth**, and re-measure it | [docs/PERFORMANCE.md](docs/PERFORMANCE.md), then `python tools/benchmark.py` |
|
|
86
86
|
| Know **what the project refuses to claim** | [docs/GUIDE.md §8](docs/GUIDE.md#8-what-this-project-does-not-claim) |
|
|
87
|
-
| Understand **why the API looks like this** | [docs/ADR/](docs/ADR/) |
|
|
87
|
+
| Understand **why the API looks like this** | [docs/ADR/](docs/ADR/), and [docs/STUDIES/](docs/STUDIES/) for the measurements behind them |
|
|
88
88
|
| Understand **why a phase was built the way it was** | [docs/design/](docs/design/) |
|
|
89
89
|
| **Contribute**: run the gates, understand the two code paths | [CONTRIBUTING.md](CONTRIBUTING.md) |
|
|
90
90
|
| **Release** it | [CONTRIBUTING.md](CONTRIBUTING.md), the "Releasing" section |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: patchcraft
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
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
|
|
@@ -170,7 +170,7 @@ 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
|
|
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.
|
|
174
174
|
|
|
175
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.
|
|
176
176
|
|
|
@@ -146,7 +146,7 @@ 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
|
-
|
|
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`.
|
|
150
150
|
|
|
151
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.
|
|
152
152
|
|
|
@@ -146,7 +146,7 @@ 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
|
|
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`.
|
|
150
150
|
|
|
151
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.
|
|
152
152
|
|
|
@@ -128,7 +128,7 @@ 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
|
|
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.
|
|
132
132
|
|
|
133
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.
|
|
134
134
|
|
|
@@ -35,12 +35,19 @@ out-of-bounds write rather than an exception, and it is the thing most worth
|
|
|
35
35
|
reporting. `PATCHCRAFT_ACCEL=0` disables the native path entirely, and the
|
|
36
36
|
universal wheel does not contain it at all.
|
|
37
37
|
|
|
38
|
-
**`Cache`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
cache
|
|
43
|
-
|
|
38
|
+
**`Cache` uses the path you give it, and validates nothing beyond expanding
|
|
39
|
+
`~`.** That is deliberate and it is what every established library does with a
|
|
40
|
+
caller-supplied cache directory: `torch.hub.set_dir` calls `expanduser` and
|
|
41
|
+
stops, pytest's cacheprovider does not validate, pip passes its `--cache-dir`
|
|
42
|
+
through. A cache location is an argument the calling code chooses, not data
|
|
43
|
+
read from an untrusted source, so `Cache(root, namespace="../elsewhere")` will
|
|
44
|
+
write outside `root` and that is the caller's decision.
|
|
45
|
+
|
|
46
|
+
The boundary, therefore: **do not derive a cache root or namespace from
|
|
47
|
+
untrusted input.** If you need per-user or per-request caches, hash or
|
|
48
|
+
otherwise sanitise the value in the layer that has the context to do it. This
|
|
49
|
+
library is not the place for path policing, and adding it here would be
|
|
50
|
+
inventing a security mechanism where the ecosystem has a settled convention.
|
|
44
51
|
|
|
45
52
|
**Nothing in the shipped package opens a network connection.** The dataset
|
|
46
53
|
helpers that download anything live in `tests/`, which is not part of the
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# STATUS: 2026-09-04
|
|
2
|
+
|
|
3
|
+
> The snapshot. Where things are *right now*, and who each open item waits on.
|
|
4
|
+
> Where things **are** is [MAP.md](MAP.md); the rules are [AGENTS.md](AGENTS.md);
|
|
5
|
+
> the history is [CHANGELOG.md](CHANGELOG.md). This file does not repeat them.
|
|
6
|
+
|
|
7
|
+
## Where the project is
|
|
8
|
+
|
|
9
|
+
**0.5.4 is on PyPI**, published through Trusted Publishing on a tag push: one
|
|
10
|
+
sdist, five `cp312-abi3` platform wheels carrying the Rust accelerator, and one
|
|
11
|
+
universal `py3-none-any` wheel. There is no extra to enable and no second
|
|
12
|
+
package.
|
|
13
|
+
|
|
14
|
+
**1619 tests pass** under `pytest -m "not gpu"`, with 32 skipped and 5
|
|
15
|
+
deselected, plus two full-sweep gates behind `PATCHCRAFT_SWEEP_FULL=1`
|
|
16
|
+
that enumerate all 126,736 legal geometries. CI is green on
|
|
17
|
+
{Ubuntu, Windows} x {3.12, 3.13, 3.14} on the pure path, plus a two-OS job that
|
|
18
|
+
builds the Rust kernel and runs the whole suite through it.
|
|
19
|
+
|
|
20
|
+
**The surface is 20 names**, pinned by `tests/test_public_api.py` with
|
|
21
|
+
`inspect.signature`.
|
|
22
|
+
|
|
23
|
+
## What is in flight
|
|
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.
|
|
32
|
+
|
|
33
|
+
## What is blocked, and on whom
|
|
34
|
+
|
|
35
|
+
**ADR 0003 is accepted**, rescoped first. It ratifies one rule, that an
|
|
36
|
+
exactness claim is declared per regime with a condition the caller can evaluate
|
|
37
|
+
before calling, and it carries the accumulator amendment in its body: the
|
|
38
|
+
predicate is a property of the geometry **and** the accumulator, since over 76
|
|
39
|
+
legal geometries a `float32` accumulator is exact on 45, precisely the
|
|
40
|
+
power-of-two ones, and a `float64` accumulator on all 76. The three-letter
|
|
41
|
+
vocabulary its first draft proposed is deferred until a transform needs it.
|
|
42
|
+
|
|
43
|
+
**ADR 0004 is `Proposed`, and asks nothing.** It records the precision and
|
|
44
|
+
effort parameters as a design that is not built; the status reflects that none
|
|
45
|
+
of it is implemented, not that a decision is pending. The one question it
|
|
46
|
+
appeared to hold, whether to add the knobs before the 1.0 freeze or after,
|
|
47
|
+
rested on the premise that adding one afterwards is expensive. It is not: each
|
|
48
|
+
is a keyword-only argument with a default, so adding one is additive and is a
|
|
49
|
+
minor release, and `reconstruct` has no positional-only parameters to make the
|
|
50
|
+
order matter. Step A of its plan shipped in 0.5.2. **Waits on nobody.**
|
|
51
|
+
|
|
52
|
+
**The `Cache` path question is closed, and it was not a decision.** It was
|
|
53
|
+
posed as whether to reject a `namespace` containing `..`. The answer is no:
|
|
54
|
+
inventing path security is not this library's job, and no library in the
|
|
55
|
+
ecosystem does it, verified against `torch.hub.set_dir`, pytest's cacheprovider
|
|
56
|
+
and pip. Asking the question found the real defect, which was the opposite
|
|
57
|
+
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
|
|
59
|
+
validation added. [SECURITY.md](SECURITY.md) and THEORY §9.5 now say the path
|
|
60
|
+
is the caller's and that only `~` is expanded.
|
|
61
|
+
|
|
62
|
+
## 1.0 blockers
|
|
63
|
+
|
|
64
|
+
**All six are closed.** The last, B4, closed in 0.5.3 by making `docs/USAGE.md`
|
|
65
|
+
executable rather than by regenerating it: every `>>>` on the page now runs in
|
|
66
|
+
the suite, so it cannot go stale silently again.
|
|
67
|
+
|
|
68
|
+
**And that leaves no decision before 1.0 either.** ADR 0003 is accepted and
|
|
69
|
+
ADR 0004 asks nothing, so what stands between here and 1.0 is the project's own
|
|
70
|
+
gate rather than an open question: an external consumer, which it does not have
|
|
71
|
+
yet.
|
|
72
|
+
|
|
73
|
+
| | |
|
|
74
|
+
|---|---|
|
|
75
|
+
| B1 predicate wrong in fifteen places | closed in 0.5.0 |
|
|
76
|
+
| B2 the suite could not falsify B1 | closed in 0.5.0 |
|
|
77
|
+
| B3 nothing pinned the public surface | closed in 0.5.0 |
|
|
78
|
+
| B4 the two pages a stranger reads | closed in 0.5.3 |
|
|
79
|
+
| B5 THEORY §9 contradicted itself on fp16 | closed in 0.5.2 |
|
|
80
|
+
| B6 enumeration junk and asymmetric guards | closed in 0.5.0 |
|
|
81
|
+
|
|
82
|
+
## What the project still declines to claim
|
|
83
|
+
|
|
84
|
+
No external project has consumed the library, and that consumption is this
|
|
85
|
+
project's own stated gate for calling the shape settled. No CUDA path has ever
|
|
86
|
+
executed, in CI or outside it. Three of the five accelerated wheels, the two
|
|
87
|
+
macOS ones and the aarch64 one, are built and have their contents checked, and
|
|
88
|
+
their kernel has never run in CI.
|
|
89
|
+
|
|
90
|
+
## Also outstanding, outside the 1.0 path
|
|
91
|
+
|
|
92
|
+
**A DOI.** The Strata conformity review
|
|
93
|
+
([docs/STUDIES/2026-09-03-strata-conformity.md](docs/STUDIES/2026-09-03-strata-conformity.md))
|
|
94
|
+
found it to be the one item missing from the publishing pattern, and it is the
|
|
95
|
+
kind that cannot be applied retroactively: Zenodo mints a DOI per release from
|
|
96
|
+
the moment it is connected, and every release before that has none, permanently.
|
|
97
|
+
One authorisation. **Waits on the owner.**
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# ADR 0003: declaring exactness per regime
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted 2026-09-03
|
|
4
|
+
- **Date:** 2026-08-04, rescoped and accepted 2026-09-03
|
|
5
|
+
- **Deciders:** Leonardo Marques de Souza
|
|
6
|
+
- **Relates to:** [ADR 0001](0001-patch-extraction-api.md), [ADR 0004](0004-precision-and-effort-parameters.md), [`THEORY.md`](../THEORY.md) §0, §9, [`STUDIES/2026-08-04-patch-techniques.md`](../STUDIES/2026-08-04-patch-techniques.md) §4, §5
|
|
7
|
+
|
|
8
|
+
> **What this rescoping did.** The original draft decided two things at once: a
|
|
9
|
+
> rule about *how exactness is declared*, and a vocabulary plus naming policy for
|
|
10
|
+
> *six transforms that were being considered*. A month later the rule is
|
|
11
|
+
> governing the code and the vocabulary has never been used: `R1`, `R2` and `R3`
|
|
12
|
+
> appear in no docstring, in no contract section and on no page a reader opens,
|
|
13
|
+
> and none of rotation, flips, padding, DCT, quantization or dithering shipped.
|
|
14
|
+
> The rule is what was accepted. The vocabulary is deferred, and §4 says until
|
|
15
|
+
> when. Freezing unused vocabulary into 1.0 would be inventing work where
|
|
16
|
+
> nothing is broken.
|
|
17
|
+
|
|
18
|
+
## 1. Context: "reversible" means three different things
|
|
19
|
+
|
|
20
|
+
Several operations claim a reversal, and the word covers three unrelated cases.
|
|
21
|
+
|
|
22
|
+
`rot90` and flips reverse **exactly**: the same tensor comes back, any dtype.
|
|
23
|
+
Arbitrary-angle rotation reverses only **approximately**, because the forward and
|
|
24
|
+
inverse interpolations each degrade the signal, and no parameter choice makes it
|
|
25
|
+
exact. Quantization does not reverse at all: it is many-to-one by construction,
|
|
26
|
+
and the useful contract is a measured error bound rather than an inverse.
|
|
27
|
+
|
|
28
|
+
The library already ships operations of the second and third kind, and they are
|
|
29
|
+
not accidents. `resize` from 64 to 32 and back differs from the original by
|
|
30
|
+
0.619 on data in [0, 1], because two thirds of the pixels are gone; that is what
|
|
31
|
+
resizing is. `stitch(weight="hann")` averages the pixels that overlap, so the
|
|
32
|
+
sum survives and the parts do not; that is what seam blending is.
|
|
33
|
+
|
|
34
|
+
**So the problem is not that inexact operations exist.** The problem is that
|
|
35
|
+
nothing in the contract distinguishes them, and a caller cannot tell from a
|
|
36
|
+
signature whether what comes back is the same tensor, a close one, or a
|
|
37
|
+
different thing entirely.
|
|
38
|
+
|
|
39
|
+
## 2. Decision: exactness is declared per regime, not per function
|
|
40
|
+
|
|
41
|
+
Each transform declares, in its docstring and in its [`THEORY.md`](../THEORY.md)
|
|
42
|
+
§9 contract section, the conditions under which its result is exact, and states
|
|
43
|
+
the measured error where it is not.
|
|
44
|
+
|
|
45
|
+
**The condition must be evaluable by the caller before the call.** A statement
|
|
46
|
+
that a result is exact "in the usual case" is not a contract; a statement that
|
|
47
|
+
it is exact when every value of the coverage map is a power of two is one,
|
|
48
|
+
because the caller can compute that from the geometry alone.
|
|
49
|
+
|
|
50
|
+
**One label per function is not writable for this library**, which is the whole
|
|
51
|
+
reason for the rule. `stitch`'s behaviour is selected by its `weight` argument.
|
|
52
|
+
`reconstruct`'s depends on the geometry, on the dtype, and, since ADR 0004
|
|
53
|
+
measured it, on the accumulator. Any single label attached to those names would
|
|
54
|
+
be false over part of their accepted input.
|
|
55
|
+
|
|
56
|
+
### 2.1 The regime axes, as measured
|
|
57
|
+
|
|
58
|
+
**Geometry.** The `extract`/`reconstruct` round trip is bit-exact when every
|
|
59
|
+
value of the overlap coverage map is a power of two, and not otherwise. The
|
|
60
|
+
reason is arithmetic rather than statistical: the fold sums `k` copies of a value
|
|
61
|
+
and divides by `k`, which is exact in binary floating point exactly when `k` is a
|
|
62
|
+
power of two. Every exact tiling qualifies, since all its counts are 1.
|
|
63
|
+
|
|
64
|
+
**Accumulator.** ADR 0004 measured the sentence above to carry an unstated
|
|
65
|
+
premise, which is the accumulator the library happens to use. Over 76 legal
|
|
66
|
+
geometries on float32 input, a float32 accumulator is exact on 45, and those 45
|
|
67
|
+
are precisely the power-of-two ones. A float64 accumulator is exact on all 76.
|
|
68
|
+
The predicate is therefore a property of the geometry **and** the accumulator,
|
|
69
|
+
and a regime row must say which accumulator it holds for.
|
|
70
|
+
|
|
71
|
+
**Dtype, and it runs the opposite way to intuition.** In a geometry where
|
|
72
|
+
float32 and float64 are inexact, `float16` and `bfloat16` are exact, because half
|
|
73
|
+
precision is promoted to a float32 accumulator before the fold and a short
|
|
74
|
+
mantissa times a small count still lands inside 24 bits. No per-function label
|
|
75
|
+
can express an ordering in which the lower precision is the exact one.
|
|
76
|
+
|
|
77
|
+
**Input values, and this is why the suite missed the error.** The round-trip
|
|
78
|
+
tests built their images from `torch.arange`, and integer-valued data comes back
|
|
79
|
+
exact in regimes where random data does not. Any test asserting exactness must
|
|
80
|
+
use random full-mantissa data in the declared dtype, or it is a false-negative
|
|
81
|
+
generator.
|
|
82
|
+
|
|
83
|
+
## 3. The refutation this ADR exists to prevent from returning
|
|
84
|
+
|
|
85
|
+
An earlier revision proposed `k_max <= 4` as the predicate. It is false, and the
|
|
86
|
+
measurement is recorded here so that it cannot come back.
|
|
87
|
+
|
|
88
|
+
It coincides with the correct predicate on square patches, where a count of 3
|
|
89
|
+
cannot occur, and fails on rectangular ones, where it can. Swept over 14,969
|
|
90
|
+
rectangular full-coverage geometries: `k_max <= 4` mispredicts 3,936 of them,
|
|
91
|
+
26 percent, while the power-of-two predicate mispredicts 8. Those 8 fail in the
|
|
92
|
+
safe direction: the power-of-two predicate has **zero false positives**, never
|
|
93
|
+
promising exactness and then failing, and its 8 misses are geometries that happen
|
|
94
|
+
to be exact without having been promised.
|
|
95
|
+
|
|
96
|
+
**A contract may under-promise. It may not over-promise.** That sentence is the
|
|
97
|
+
norm this ADR contributes, and the project has already used it once, to retract
|
|
98
|
+
the wrong predicate in public.
|
|
99
|
+
|
|
100
|
+
## 4. What is deferred, and until when
|
|
101
|
+
|
|
102
|
+
The original draft also proposed a three-letter vocabulary (`R1` bit-exact, `R2`
|
|
103
|
+
measured-approximate, `R3` irreversible), a naming rule reserving `inverse_*`,
|
|
104
|
+
`un*` and `de*` for exact inverses, and a policy sending entirely-inexact
|
|
105
|
+
features to auxiliary packages.
|
|
106
|
+
|
|
107
|
+
None of it is wrong. All of it was written for six transforms that do not exist,
|
|
108
|
+
and it has been used nowhere in a month. It is **deferred until the first
|
|
109
|
+
transform that needs it**, which is the first operation offering an inverse that
|
|
110
|
+
is not exact. At that point this ADR is amended or a successor is written, with
|
|
111
|
+
the vocabulary applied to a real case rather than to six hypothetical ones.
|
|
112
|
+
|
|
113
|
+
Until then the naming rule is worth keeping as a one-line habit rather than a
|
|
114
|
+
frozen policy: **do not give an approximate inverse a name that promises an exact
|
|
115
|
+
one**. `rotate_back`, never `derotate`.
|
|
116
|
+
|
|
117
|
+
## 5. Consequences
|
|
118
|
+
|
|
119
|
+
- **Nothing in the code changes.** No public name moves, no call that worked
|
|
120
|
+
stops working, no new exception is raised. What this ratifies is how the
|
|
121
|
+
contract is written.
|
|
122
|
+
- **Inexact operations are not restricted.** `resize` and `stitch` with a window
|
|
123
|
+
are exactly the operations users come for. What is forbidden is shipping one
|
|
124
|
+
whose docstring implies an exactness it does not have.
|
|
125
|
+
- **The 1.0 freeze gains a rule and not a vocabulary.** [`FOCO-1.0.md`](../FOCO-1.0.md)
|
|
126
|
+
makes accepting this ADR part of the freeze; what gets frozen is the
|
|
127
|
+
requirement that every exactness claim name its regime and be caller-evaluable.
|
|
128
|
+
- **Tests mirror the regime, not the function.** An exactness claim is asserted
|
|
129
|
+
by a sweep over the regime's own condition, on random full-mantissa data.
|
|
130
|
+
`tests/test_exactness.py` is that, and it enumerates the space independently of
|
|
131
|
+
the predicate so it can falsify rather than confirm.
|
|
132
|
+
- **Integers are outside this predicate.** For unmodified patches the integer sum
|
|
133
|
+
is exactly `k*v` and `k*v // k == v` for every `k`, so an integer regime needs
|
|
134
|
+
its own condition, which is that the accumulator does not overflow. ADR 0004
|
|
135
|
+
carries that.
|
|
136
|
+
- **CPU verified only.** Every measurement here was taken on CPU, because no CUDA
|
|
137
|
+
path in this library has ever executed. The power-of-two boundary is argued
|
|
138
|
+
from IEEE arithmetic rather than from an accumulation order, so it should hold
|
|
139
|
+
anywhere, but the caveat stays until a CUDA sweep confirms it.
|