rivin-perm 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rivin_perm-1.0.0/BINARY_APPLICATIONS.md +327 -0
- rivin_perm-1.0.0/CHANGELOG.md +143 -0
- rivin_perm-1.0.0/CONTRIBUTING.md +140 -0
- rivin_perm-1.0.0/CUDA.md +123 -0
- rivin_perm-1.0.0/LICENSE +21 -0
- rivin_perm-1.0.0/LOW_RANK_DISPATCH_RESULTS.md +167 -0
- rivin_perm-1.0.0/LOW_RANK_SHOWCASES.md +235 -0
- rivin_perm-1.0.0/MANIFEST.in +140 -0
- rivin_perm-1.0.0/PACKAGING.md +74 -0
- rivin_perm-1.0.0/PKG-INFO +697 -0
- rivin_perm-1.0.0/README.md +667 -0
- rivin_perm-1.0.0/REPOSITORY_LAYOUT.md +22 -0
- rivin_perm-1.0.0/RESEARCH_CORRECTIONS.md +24 -0
- rivin_perm-1.0.0/benchmark.py +186 -0
- rivin_perm-1.0.0/benchmark_binary.py +134 -0
- rivin_perm-1.0.0/benchmark_low_rank.py +188 -0
- rivin_perm-1.0.0/benchmark_native_dispatch.py +258 -0
- rivin_perm-1.0.0/benchmark_weighted_rook.py +371 -0
- rivin_perm-1.0.0/cycle_geodesic.py +262 -0
- rivin_perm-1.0.0/experiment_random_cubic.py +443 -0
- rivin_perm-1.0.0/notes/benchmarks/README.md +188 -0
- rivin_perm-1.0.0/notes/benchmarks/SCHUR.md +210 -0
- rivin_perm-1.0.0/notes/benchmarks/WEIGHTED_FRONTIER_NATIVE.md +127 -0
- rivin_perm-1.0.0/notes/benchmarks/WEIGHTED_MATCHINGS.md +147 -0
- rivin_perm-1.0.0/notes/benchmarks/WEIGHTED_ROOK_DISPATCH.md +64 -0
- rivin_perm-1.0.0/notes/benchmarks/check_schur_orbits.py +237 -0
- rivin_perm-1.0.0/notes/benchmarks/polynomial_baselines.cpp +138 -0
- rivin_perm-1.0.0/notes/benchmarks/polynomial_compare.py +906 -0
- rivin_perm-1.0.0/notes/benchmarks/schur_compare.py +722 -0
- rivin_perm-1.0.0/notes/benchmarks/schur_compare_wrapper.cu +220 -0
- rivin_perm-1.0.0/notes/benchmarks/summarize_polynomial_comparison.py +116 -0
- rivin_perm-1.0.0/notes/benchmarks/summarize_weighted_matching_study.py +431 -0
- rivin_perm-1.0.0/notes/benchmarks/weighted_matching_frontier.py +231 -0
- rivin_perm-1.0.0/notes/benchmarks/weighted_matching_frontier_native.cpp +344 -0
- rivin_perm-1.0.0/notes/benchmarks/weighted_matching_frontier_native.py +392 -0
- rivin_perm-1.0.0/notes/benchmarks/weighted_matching_partition.cpp +91 -0
- rivin_perm-1.0.0/notes/benchmarks/weighted_matching_partition.py +181 -0
- rivin_perm-1.0.0/notes/benchmarks/weighted_matching_statistics.py +325 -0
- rivin_perm-1.0.0/notes/benchmarks/weighted_matching_study.py +613 -0
- rivin_perm-1.0.0/notes/benchmarks/weighted_rook_dispatch.py +200 -0
- rivin_perm-1.0.0/notes/frontier/verify_binary_grid_4x4x5_transfer.py +126 -0
- rivin_perm-1.0.0/perm.py +1259 -0
- rivin_perm-1.0.0/perm_binary.py +1017 -0
- rivin_perm-1.0.0/perm_binary_applications.py +552 -0
- rivin_perm-1.0.0/perm_binary_cuda.cu +482 -0
- rivin_perm-1.0.0/perm_binary_cuda.py +214 -0
- rivin_perm-1.0.0/perm_binary_omp.c +550 -0
- rivin_perm-1.0.0/perm_c_omp.c +1136 -0
- rivin_perm-1.0.0/perm_cuda.py +556 -0
- rivin_perm-1.0.0/perm_enum_cuda.cu +482 -0
- rivin_perm-1.0.0/perm_float.py +370 -0
- rivin_perm-1.0.0/perm_mod3.py +471 -0
- rivin_perm-1.0.0/perm_mod3_cuda.cu +385 -0
- rivin_perm-1.0.0/perm_mod3_omp.c +327 -0
- rivin_perm-1.0.0/perm_modp_cuda.cu +1413 -0
- rivin_perm-1.0.0/perm_numba.py +253 -0
- rivin_perm-1.0.0/perm_parallel.py +164 -0
- rivin_perm-1.0.0/perm_parallel_cuda.cu +619 -0
- rivin_perm-1.0.0/perm_polynomial.py +1719 -0
- rivin_perm-1.0.0/perm_polynomial_omp.c +1854 -0
- rivin_perm-1.0.0/perm_proj_cuda.cu +499 -0
- rivin_perm-1.0.0/pyproject.toml +65 -0
- rivin_perm-1.0.0/rivin_perm.egg-info/PKG-INFO +697 -0
- rivin_perm-1.0.0/rivin_perm.egg-info/SOURCES.txt +112 -0
- rivin_perm-1.0.0/rivin_perm.egg-info/dependency_links.txt +1 -0
- rivin_perm-1.0.0/rivin_perm.egg-info/requires.txt +10 -0
- rivin_perm-1.0.0/rivin_perm.egg-info/top_level.txt +7 -0
- rivin_perm-1.0.0/schur_permanents.txt +49 -0
- rivin_perm-1.0.0/setup.cfg +4 -0
- rivin_perm-1.0.0/setup.py +127 -0
- rivin_perm-1.0.0/showcase_low_rank.py +298 -0
- rivin_perm-1.0.0/statistics_helpers.py +130 -0
- rivin_perm-1.0.0/tests/README.md +16 -0
- rivin_perm-1.0.0/tests/test_binary_applications.py +301 -0
- rivin_perm-1.0.0/tests/test_binary_cuda_wrappers.py +163 -0
- rivin_perm-1.0.0/tests/test_binary_frontier.py +227 -0
- rivin_perm-1.0.0/tests/test_binary_native.py +167 -0
- rivin_perm-1.0.0/tests/test_binary_permanent.py +504 -0
- rivin_perm-1.0.0/tests/test_core.py +238 -0
- rivin_perm-1.0.0/tests/test_cuda_crt_planning.py +220 -0
- rivin_perm-1.0.0/tests/test_cuda_device_binary.py +175 -0
- rivin_perm-1.0.0/tests/test_cuda_device_modular.py +156 -0
- rivin_perm-1.0.0/tests/test_cuda_device_parallel.py +100 -0
- rivin_perm-1.0.0/tests/test_cuda_device_schur_glynn.py +169 -0
- rivin_perm-1.0.0/tests/test_cuda_device_utilities.py +284 -0
- rivin_perm-1.0.0/tests/test_cuda_wrappers.py +136 -0
- rivin_perm-1.0.0/tests/test_cycle_geodesic.py +175 -0
- rivin_perm-1.0.0/tests/test_dispatch_efficiency.py +238 -0
- rivin_perm-1.0.0/tests/test_generic_native.py +211 -0
- rivin_perm-1.0.0/tests/test_low_rank_dispatch.py +158 -0
- rivin_perm-1.0.0/tests/test_low_rank_showcases.py +145 -0
- rivin_perm-1.0.0/tests/test_perm_parallel_api.py +48 -0
- rivin_perm-1.0.0/tests/test_polynomial_benchmark.py +452 -0
- rivin_perm-1.0.0/tests/test_polynomial_native.py +693 -0
- rivin_perm-1.0.0/tests/test_polynomial_permanent.py +1783 -0
- rivin_perm-1.0.0/tests/test_random_cubic_experiment.py +59 -0
- rivin_perm-1.0.0/tests/test_reference_values.py +66 -0
- rivin_perm-1.0.0/tests/test_research_statistics.py +229 -0
- rivin_perm-1.0.0/tests/test_schur_benchmark.py +260 -0
- rivin_perm-1.0.0/tests/test_schur_cpu_glynn.py +518 -0
- rivin_perm-1.0.0/tests/test_source_distribution.py +185 -0
- rivin_perm-1.0.0/tests/test_weighted_matching_frontier.py +180 -0
- rivin_perm-1.0.0/tests/test_weighted_matching_frontier_native.py +531 -0
- rivin_perm-1.0.0/tests/test_weighted_matching_partition.py +170 -0
- rivin_perm-1.0.0/tests/test_weighted_matching_statistics.py +252 -0
- rivin_perm-1.0.0/tests/test_weighted_matching_study.py +334 -0
- rivin_perm-1.0.0/tests/test_weighted_matching_summary.py +220 -0
- rivin_perm-1.0.0/tests/test_weighted_rook_dispatch.py +337 -0
- rivin_perm-1.0.0/tests/test_weighted_rook_planner.py +209 -0
- rivin_perm-1.0.0/tests/test_weighted_rook_source_dispatch.py +405 -0
- rivin_perm-1.0.0/tools/check_distribution.py +70 -0
- rivin_perm-1.0.0/tools/check_installed.py +95 -0
- rivin_perm-1.0.0/tools/extract_sdist.py +32 -0
- rivin_perm-1.0.0/tools/prepare_release.py +99 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Binary permanents: algorithms and application cases
|
|
2
|
+
|
|
3
|
+
The permanent of a zero-one biadjacency matrix is the number of perfect
|
|
4
|
+
matchings of the corresponding bipartite graph. This repository therefore
|
|
5
|
+
keeps binary matrices as a separate exact problem class rather than sending
|
|
6
|
+
them through the general integer kernel.
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from perm_binary import binary_permanent
|
|
10
|
+
|
|
11
|
+
binary_permanent([[1, 1], [1, 1]]) # 2
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The default input contract is strict: entries must be integral zeroes or ones.
|
|
15
|
+
`strict=False` explicitly replaces an integral matrix by its support matrix.
|
|
16
|
+
Rectangular inputs are rejected because this API counts perfect matchings of
|
|
17
|
+
both complete bipartition classes.
|
|
18
|
+
|
|
19
|
+
## Specialized exact algorithm
|
|
20
|
+
|
|
21
|
+
Before exponential work, the implementation:
|
|
22
|
+
|
|
23
|
+
1. finds a perfect matching, returning zero immediately if Hall feasibility
|
|
24
|
+
fails;
|
|
25
|
+
2. uses alternating-cycle strongly connected components relative to that
|
|
26
|
+
matching to factor the matrix into independent square blocks; and
|
|
27
|
+
3. bounds each block with the minimum of `n!`, row/column degree products, and
|
|
28
|
+
rigorously rounded grouped Bregman--Minc bounds.
|
|
29
|
+
|
|
30
|
+
The native residue kernel uses fixed-sign Glynn, so it visits `2^(n-1)` sign
|
|
31
|
+
states. It fixes the densest column, assigns low-degree columns to frequently
|
|
32
|
+
toggled Gray bits, and chooses the cheaper orientation between the matrix and
|
|
33
|
+
its transpose. Signed row sums are small. A Gray transition touches only
|
|
34
|
+
the set bits of one packed column; a precomputed ratio updates the product of
|
|
35
|
+
nonzero row sums while a separate counter handles zero factors.
|
|
36
|
+
|
|
37
|
+
The fast path uses 31-bit Montgomery arithmetic without division in the hot
|
|
38
|
+
loop. A 64-bit Montgomery fallback preserves modular correctness for every
|
|
39
|
+
supported odd signed-64-bit prime greater than the matrix order. Because a
|
|
40
|
+
binary permanent is nonnegative, exact CRT reconstruction needs a modulus
|
|
41
|
+
product greater than the upper bound, not twice that bound.
|
|
42
|
+
|
|
43
|
+
The portable fallback is a definition-level subset dynamic program with
|
|
44
|
+
arbitrary-size Python integers. It is intended for correctness, small
|
|
45
|
+
problems, and installations without OpenMP; dense large blocks should use the
|
|
46
|
+
native backend.
|
|
47
|
+
|
|
48
|
+
## Live-column frontier algorithm
|
|
49
|
+
|
|
50
|
+
`binary_permanent_frontier` is an exact alternative for sparse matrices whose
|
|
51
|
+
support admits a favorable row order. After each row, a state records which
|
|
52
|
+
currently live columns have already received their matching edge. Processing
|
|
53
|
+
a row selects one adjacent unused column. When the last row adjacent to a
|
|
54
|
+
column has been processed, that column must already be selected; the bit can
|
|
55
|
+
then be forgotten. Different partial matchings with the same live state are
|
|
56
|
+
combined using arbitrary-size Python integers.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from perm_binary import (
|
|
60
|
+
FrontierStateLimitExceeded,
|
|
61
|
+
binary_permanent_frontier,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
K4 = [[0, 1, 1, 1],
|
|
65
|
+
[1, 0, 1, 1],
|
|
66
|
+
[1, 1, 0, 1],
|
|
67
|
+
[1, 1, 1, 0]]
|
|
68
|
+
|
|
69
|
+
binary_permanent_frontier(K4) # 9
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The full signature is
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
binary_permanent_frontier(
|
|
76
|
+
matrix, *, row_order=None, max_states=500_000, strict=True
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`row_order=None` invokes a deterministic greedy heuristic. It tries to keep
|
|
81
|
+
the live-column frontier small, but it neither computes nor certifies an
|
|
82
|
+
optimal path decomposition. A supplied order must contain every original
|
|
83
|
+
row index exactly once; it is interpreted before any internal reordering.
|
|
84
|
+
Runtime depends on the distinct reachable states, not on frontier width
|
|
85
|
+
alone, so even a plausible width estimate is not a memory guarantee.
|
|
86
|
+
|
|
87
|
+
`max_states` must be a positive integer. It bounds the number of distinct
|
|
88
|
+
states retained in any one layer. If the next layer would exceed it, an
|
|
89
|
+
explicit call raises `FrontierStateLimitExceeded`; it never changes algorithms
|
|
90
|
+
behind the caller's back. The exception exposes the configured limit, the
|
|
91
|
+
minimum observed state count, and the number of processed rows.
|
|
92
|
+
|
|
93
|
+
The high-level `binary_permanent` dispatcher has deliberately different
|
|
94
|
+
semantics. Only `backend="auto"` may try the frontier route on an eligible
|
|
95
|
+
matching-factor block. If that attempt reaches its internal state cap, the
|
|
96
|
+
dispatcher discards it and continues with the exact native backend when
|
|
97
|
+
available, or the existing portable subset DP otherwise. Explicit
|
|
98
|
+
`backend="native"` and `backend="python"` preserve their selected algorithms.
|
|
99
|
+
|
|
100
|
+
### Cubic adjacency matrices and cycle covers
|
|
101
|
+
|
|
102
|
+
For the adjacency matrix `A` of an undirected graph, `per(A)` counts directed
|
|
103
|
+
permutation cycle covers: every vertex has one selected outgoing and one
|
|
104
|
+
selected incoming arc. In particular, a transposition selects both
|
|
105
|
+
orientations of one undirected edge and is a permitted directed 2-cycle. This
|
|
106
|
+
is not the same statistic as an oriented spanning 2-factor that requires
|
|
107
|
+
cycles of length at least three.
|
|
108
|
+
|
|
109
|
+
Sparse cubic adjacency matrices are therefore natural frontier candidates,
|
|
110
|
+
but cubicity by itself does not promise that the greedy order will be good.
|
|
111
|
+
The asymmetric 42-vertex evaluation instance has exact permanent `1,361,137`.
|
|
112
|
+
Its independently recorded row order has at most 14 live columns and 2,944
|
|
113
|
+
reachable states, allowing an exact integer frontier computation in a tiny
|
|
114
|
+
fraction of the time required by generic sign-vector enumeration. Other
|
|
115
|
+
cubic instances can have much broader frontiers; the state cap makes that
|
|
116
|
+
instance dependence explicit. Dense matrices, and sparse matrices with poor
|
|
117
|
+
orders, remain better candidates for the native fixed-sign Glynn backend.
|
|
118
|
+
|
|
119
|
+
## Application 1: bipartite dimers
|
|
120
|
+
|
|
121
|
+
`grid_biadjacency` and `grid_dimer_count` construct parity-bipartitioned
|
|
122
|
+
Cartesian grids with open boundaries and selected periodic axes.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from perm_binary_applications import grid_dimer_count
|
|
126
|
+
|
|
127
|
+
grid_dimer_count((4, 4)) # 36
|
|
128
|
+
grid_dimer_count((2, 2, 4)) # 121
|
|
129
|
+
grid_dimer_count((2, 3, 4)) # 1845
|
|
130
|
+
grid_dimer_count((4, 4), periodic_axes=(0, 1)) # 272
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Open two-dimensional grids are planar Kasteleyn oracles, not a claim that the
|
|
134
|
+
generic permanent is the best planar-dimer algorithm. Doubly periodic grids
|
|
135
|
+
live on a torus and admit fixed-genus Pfaffian treatments. The proposed
|
|
136
|
+
`4 x 4 x 5` grid with periodic x/y axes and open z boundaries is instead a
|
|
137
|
+
genuinely nonplanar 80-vertex benchmark. Its biadjacency matrix has order 40,
|
|
138
|
+
224 ones, and row/column degree multiset `5^16 6^24`.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from perm_binary_applications import grid_biadjacency
|
|
142
|
+
|
|
143
|
+
matrix = grid_biadjacency((4, 4, 5), periodic_axes=(0, 1))
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The certified bound is
|
|
147
|
+
|
|
148
|
+
`1,393,140,695,040,000,000`,
|
|
149
|
+
|
|
150
|
+
so two descending 31-bit CRT primes suffice. The GH200 host CPU, using 64
|
|
151
|
+
OpenMP threads, computed
|
|
152
|
+
|
|
153
|
+
`per(matrix) = 2,551,276,235,535,120`.
|
|
154
|
+
|
|
155
|
+
The two required residues were:
|
|
156
|
+
|
|
157
|
+
| prime | residue | native kernel wall time |
|
|
158
|
+
|---:|---:|---:|
|
|
159
|
+
| 2,147,483,647 | 1,238,389,710 | 121.97 s |
|
|
160
|
+
| 2,147,483,629 | 1,259,774,250 | 120.42 s |
|
|
161
|
+
|
|
162
|
+
Their 242.38-second sum (4.04 minutes) excludes the independent unused-prime
|
|
163
|
+
holdout. This is an exact finite-volume benchmark, not a novelty claim:
|
|
164
|
+
transfer-matrix and other specialized dimer methods also exist.
|
|
165
|
+
|
|
166
|
+
The optional source-only CUDA path computed the same two residues in 10.25
|
|
167
|
+
and 10.13 seconds, and the complete Python normalization, matching,
|
|
168
|
+
two-residue CRT, and validation path took 22.73 seconds. That is a 10.66x
|
|
169
|
+
end-to-end speedup over the specialized 64-thread CPU call on this host. The
|
|
170
|
+
sm_90 kernel uses 56 registers per thread, 1 KiB of shared memory per block,
|
|
171
|
+
and no stack or spills; a representative order-24 Compute Sanitizer run
|
|
172
|
+
reported zero errors. It is built and imported separately through
|
|
173
|
+
`perm_binary_cuda.py` and does not add a CUDA dependency to the package.
|
|
174
|
+
|
|
175
|
+
As an algorithmically independent check, the standalone layer-transfer
|
|
176
|
+
verifier in `notes/frontier/verify_binary_grid_4x4x5_transfer.py` returned the
|
|
177
|
+
same integer in 2.54 seconds. It is deliberately separate from the permanent
|
|
178
|
+
implementation and demonstrates why highly structured lattices should also
|
|
179
|
+
be compared with problem-specific transfer methods.
|
|
180
|
+
|
|
181
|
+
### Anisotropic polynomial
|
|
182
|
+
|
|
183
|
+
`weighted_grid_biadjacency` assigns an integer weight by edge axis, while
|
|
184
|
+
`axis_dimer_profile` computes small exact coefficient oracles.
|
|
185
|
+
`axis_dimer_profile_mod` uses the coefficient-valued Glynn backend to compute
|
|
186
|
+
the complete profile modulo any odd signed-64-bit integer, avoiding separate
|
|
187
|
+
evaluation and interpolation calls. The corresponding constant and marked
|
|
188
|
+
edge matrices may instead be passed to `perm_polynomial.affine_permanent` for
|
|
189
|
+
certified exact integer coefficients via CRT. For the open
|
|
190
|
+
`2 x 2 x 2` cube,
|
|
191
|
+
|
|
192
|
+
`Z(w) = 4 + 4 w^2 + w^4`.
|
|
193
|
+
|
|
194
|
+
For the `4 x 4 x 5` periodic-x/y target, the interpolation proposal in the
|
|
195
|
+
source conversation can be sharpened:
|
|
196
|
+
|
|
197
|
+
- each of the 16 length-five z-columns contains at most two vertical dimers,
|
|
198
|
+
so `deg Z <= 32`;
|
|
199
|
+
- every layer has 16 vertices, forcing the number of inter-layer dimers—and
|
|
200
|
+
hence the total vertical count—to be even;
|
|
201
|
+
- therefore `Z(w) = Q(w^2)` with `deg Q <= 16`, so 17 evaluations in
|
|
202
|
+
`u = w^2` suffice rather than 41 generic values;
|
|
203
|
+
- at `w=0`, the five toroidal layers decouple, giving the exact holdout
|
|
204
|
+
`Z(0) = 272^5 = 1,488,827,973,632`.
|
|
205
|
+
|
|
206
|
+
The exact-integer profile DP remains intentionally guarded to small orders.
|
|
207
|
+
The modular affine-polynomial backend scales exponentially in the
|
|
208
|
+
bipartition order but carries every coefficient through a single sign-vector
|
|
209
|
+
enumeration; exact large profiles can subsequently be reconstructed from
|
|
210
|
+
enough coprime modular runs when a coefficient bound is available.
|
|
211
|
+
|
|
212
|
+
## Application 2: restricted permutations
|
|
213
|
+
|
|
214
|
+
A binary matrix can encode allowed positions of a permutation. Its permanent
|
|
215
|
+
counts the permitted permutations exactly. In particular, `derangement_matrix`
|
|
216
|
+
returns `J-I`, whose permanent is the subfactorial `!n`.
|
|
217
|
+
|
|
218
|
+
Block diagonal all-ones matrices are also provided as exact Bregman--Minc
|
|
219
|
+
extremizers. A matrix with `k` blocks of order `d` has permanent `(d!)^k`;
|
|
220
|
+
these cases test arbitrary-size results and the SCC factorization path.
|
|
221
|
+
|
|
222
|
+
## Application 3: Latin transversals, correctly qualified
|
|
223
|
+
|
|
224
|
+
A Latin transversal is a three-dimensional matching and is naturally a
|
|
225
|
+
three-dimensional zero-one permanent—not one ordinary matrix permanent. The
|
|
226
|
+
reference `latin_transversal_count` instead uses the exact coefficient-
|
|
227
|
+
extraction identity
|
|
228
|
+
|
|
229
|
+
`T(L) = sum_S (-1)^(n-|S|) per(A_S)`,
|
|
230
|
+
|
|
231
|
+
where `A_S[i,j]` is one when the symbol `L[i,j]` belongs to `S`. It makes
|
|
232
|
+
`2^n` ordinary binary-permanent calls and is explicitly guarded to small
|
|
233
|
+
orders. Tests include cyclic group tables through order nine and the
|
|
234
|
+
order-four Klein group table, which has eight transversals; even cyclic tables
|
|
235
|
+
having zero must not be generalized to all even Latin squares.
|
|
236
|
+
|
|
237
|
+
## Application 4: partial matchings and rook polynomials
|
|
238
|
+
|
|
239
|
+
`perm_polynomial.rook_polynomial` returns all partial-matching counts of a
|
|
240
|
+
square binary matrix. Entry `k` counts placements of `k` ones with distinct
|
|
241
|
+
rows and columns. It uses the identity
|
|
242
|
+
|
|
243
|
+
`[x^k] per(J + xA) = (n-k)! r_k`
|
|
244
|
+
|
|
245
|
+
to obtain every coefficient in one coefficient-valued Glynn traversal per
|
|
246
|
+
CRT prime in the portable fallback. The native rook specialization instead
|
|
247
|
+
packs all required pseudo-Mersenne primes with the coefficient dimension, so
|
|
248
|
+
one sign-vector traversal produces every modular vector. Exact fixed-point
|
|
249
|
+
augmented-matrix Bregman--Minc bounds are applied directly to the
|
|
250
|
+
partial-matching counts. An independent characteristic-two Cauchy--Binet
|
|
251
|
+
determinant supplies the coefficient parities, which halves the CRT range and
|
|
252
|
+
selects the unique lift `x` or `x + P` below the certified bound.
|
|
253
|
+
|
|
254
|
+
On the dense SHA-generated order-33 evaluation board, this reduces four
|
|
255
|
+
sequential native CRT traversals (369.26 seconds on the GH200 host CPU) to one
|
|
256
|
+
fused traversal and exact reconstruction (44.00 seconds). Increasing the
|
|
257
|
+
order from 33 to 37 multiplies the fixed-sign state count by 16 and requires
|
|
258
|
+
five packed 27-bit fields rather than four. The fresh order-37 `/1` evaluation
|
|
259
|
+
instance completed in 1,212.81 seconds (20.21 minutes) on the same 64-core
|
|
260
|
+
host.
|
|
261
|
+
|
|
262
|
+
## Weighted partial matchings and occupancy
|
|
263
|
+
|
|
264
|
+
`weighted_rook_polynomial(W)` supports arbitrary nonnegative integer edge
|
|
265
|
+
weights. Its coefficient `r[k]` sums the products of the edge weights over
|
|
266
|
+
all size-`k` matchings. Disconnected bipartite support components factor
|
|
267
|
+
exactly, including diagonal and star components; the returned tuple still
|
|
268
|
+
contains `n+1` entries with trailing zeros.
|
|
269
|
+
|
|
270
|
+
For nonnegative rational activity `z`, `Z(z)=sum(r[k]*z**k)` is the finite
|
|
271
|
+
monomer-dimer partition function with unit monomer weights. The matching-size
|
|
272
|
+
probability is `r[k]*z**k/Z(z)`. In a source checkout:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from fractions import Fraction
|
|
276
|
+
from perm_polynomial import weighted_rook_polynomial
|
|
277
|
+
from notes.benchmarks.weighted_matching_statistics import exact_occupancy_statistics
|
|
278
|
+
|
|
279
|
+
coefficients = weighted_rook_polynomial([[1, 2], [3, 4]])
|
|
280
|
+
statistics = exact_occupancy_statistics(coefficients, Fraction(1, 2))
|
|
281
|
+
assert statistics["partition"] == Fraction(17, 2)
|
|
282
|
+
assert statistics["mean"] == Fraction(20, 17)
|
|
283
|
+
assert statistics["variance"] == Fraction(110, 289)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
One complete coefficient vector supports many activity queries. The exact
|
|
287
|
+
statistics helper retains rational probabilities and moments; only its
|
|
288
|
+
convenience logarithm is floating point. Its separate belief-propagation
|
|
289
|
+
comparison returns numerical approximations and explicit convergence status.
|
|
290
|
+
Convergence of the message iteration does not certify approximation accuracy.
|
|
291
|
+
|
|
292
|
+
The [public application study](notes/benchmarks/WEIGHTED_MATCHINGS.md) compares
|
|
293
|
+
native coefficients, compiled subset DP, polynomial-valued Ryser, and an
|
|
294
|
+
independent implementation of the Butera-Pernici frontier approach. It also
|
|
295
|
+
compares the cost of reusing coefficients with a compiled exact per-activity
|
|
296
|
+
partition-and-moment query. The study includes dense and structured cases,
|
|
297
|
+
fixed-matrix rescaling, per-method process memory, and retained losses.
|
|
298
|
+
|
|
299
|
+
## Boundary conventions
|
|
300
|
+
|
|
301
|
+
- A periodic axis must have even length to preserve the parity bipartition.
|
|
302
|
+
- Periodic extent two is rejected: its positive and negative bonds have the
|
|
303
|
+
same endpoints, producing a multigraph that cannot be represented faithfully
|
|
304
|
+
by a zero-one matrix.
|
|
305
|
+
- An odd-volume grid has zero close-packed dimer coverings. The high-level
|
|
306
|
+
count/partition/profile functions return zero (or `(0,)`), while the matrix
|
|
307
|
+
constructors reject its unequal parity classes because the result would not
|
|
308
|
+
be a square biadjacency matrix.
|
|
309
|
+
- Merely having three nontrivial dimensions does not prove nonplanarity; for
|
|
310
|
+
example, `2 x 2 x k` grid graphs are planar.
|
|
311
|
+
|
|
312
|
+
## Primary references
|
|
313
|
+
|
|
314
|
+
- L. G. Valiant, [*The Complexity of Computing the Permanent*](https://doi.org/10.1016/0304-3975(79)90044-6).
|
|
315
|
+
- P. W. Kasteleyn, [*The Statistics of Dimers on a Lattice. I. The Number of Dimer Arrangements on a Quadratic Lattice*](https://doi.org/10.1016/0031-8914(61)90063-5).
|
|
316
|
+
- D. Cimasoni and N. Reshetikhin, [*Dimers on Surface Graphs and Spin Structures. I*](https://doi.org/10.1007/s00220-007-0302-7).
|
|
317
|
+
- I. Beichl and F. Sullivan, [*Approximating the Permanent via Importance Sampling with Application to the Dimer Covering Problem*](https://doi.org/10.1006/jcph.1998.6149).
|
|
318
|
+
- Y. Huh and S. Oh, [*Dense Dimer Packings in the 3D Cubic Lattice*](https://doi.org/10.1088/1402-4896/ae5605).
|
|
319
|
+
- L. M. Brègman, [*Some Properties of Nonnegative Matrices and Their Permanents*](https://www.mathnet.ru/eng/dan/v211/i1/p27).
|
|
320
|
+
- D. Donovan, K. Johnson, and I. M. Wanless, [*Permanents and Determinants of Latin Squares*](https://doi.org/10.1002/jcd.21418).
|
|
321
|
+
- A. A. Taranenko, [*Multidimensional Permanents and an Upper Bound on the Number of Transversals in Latin Squares*](https://doi.org/10.1002/jcd.21413).
|
|
322
|
+
|
|
323
|
+
These references motivate and validate the application families. They do not
|
|
324
|
+
support the stronger claims that the `4 x 4 x 5` value is previously unknown,
|
|
325
|
+
that every three-dimensional dimer instance requires a permanent algorithm,
|
|
326
|
+
or that a Hofstadter Hamiltonian permanent is automatically a dimer partition
|
|
327
|
+
function. Those claims are intentionally not made here.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This file records user-visible library, build, and support changes. Historical
|
|
4
|
+
research results remain in their dated notes and session logs.
|
|
5
|
+
|
|
6
|
+
## Unreleased
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Clean wheel/sdist installation checks covering the installed public API,
|
|
11
|
+
optional Numba, and strict native-extension availability; distribution CI
|
|
12
|
+
for portable and Linux accelerator wheels.
|
|
13
|
+
- `PERM_BUILD_OPENMP=required` to prevent native release builds from silently
|
|
14
|
+
falling back when compilation fails.
|
|
15
|
+
- Reproducible low-rank showcases with explanations of weighted assignments,
|
|
16
|
+
proper complex Gaussian intensity moments, and exact unlabeled profile
|
|
17
|
+
probabilities. Include a grouped-column comparator and an optional
|
|
18
|
+
checkout-only full-rank covariance example with certified approximation.
|
|
19
|
+
- Exact low-rank integer computation in `matrix_permanent`, selected by a
|
|
20
|
+
conservative rank/order/parallelism cost rule. New keyword-only `method`
|
|
21
|
+
choices allow explicit generic or low-rank computation, with a documented
|
|
22
|
+
rank/state budget and `LowRankLimitExceeded` for oversized explicit requests.
|
|
23
|
+
Every CRT prime is preflighted before using the specialization; its CRT bound
|
|
24
|
+
and signed reconstruction are shared with the existing general path.
|
|
25
|
+
- A compiled weighted partial-matching frontier source tool with exact modular
|
|
26
|
+
arithmetic/CRT, reusable column slots, explicit state and native allocation
|
|
27
|
+
limits, independent correctness tests and recorded build provenance. Public
|
|
28
|
+
weighted-rook dispatch is unchanged.
|
|
29
|
+
- A public weighted-matching application study with exact rational occupancy
|
|
30
|
+
distributions, mean and variance, damped monomer-dimer belief propagation,
|
|
31
|
+
an independent Butera-Pernici-style frontier baseline, and a compiled exact
|
|
32
|
+
per-activity partition/moment baseline. Source tools retain fixed-input
|
|
33
|
+
weight scaling, isolated process RSS, raw timings, and replayable CRT records.
|
|
34
|
+
- A general `perm_c_ext.permanent_mod_p` entry point and independent native
|
|
35
|
+
definition/parallel tests, plus a reproducible benchmark across matrix
|
|
36
|
+
families, modulus ranges, and thread counts.
|
|
37
|
+
- Exact `principal_permanent_polynomial` and modular
|
|
38
|
+
`principal_permanent_polynomial_mod` support for the coefficient vector of
|
|
39
|
+
`per(I + xA)`.
|
|
40
|
+
- Exact `weighted_rook_polynomial` support for nonnegative integer matrices,
|
|
41
|
+
including a fused native multi-prime path.
|
|
42
|
+
- A stable `polynomial_backend_capabilities` query for source tools and
|
|
43
|
+
benchmarks that need to report optional native hooks without importing a
|
|
44
|
+
private extension module.
|
|
45
|
+
- Repository support tiers, packaging boundaries, CUDA inventory, and
|
|
46
|
+
contributor guidance.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Name the pip distribution `rivin-perm`, preserving all Python import paths.
|
|
51
|
+
Declare metadata in `pyproject.toml`, target Python 3.10 and later, require
|
|
52
|
+
NumPy 1.23.5–2.x at runtime, and build native modules with NumPy 2.x headers
|
|
53
|
+
targeting the NumPy 1.23 API.
|
|
54
|
+
- Build sdists from an explicit public-file allowlist, including reviewed tests;
|
|
55
|
+
omit the three research-only CUDA utilities previously included by name.
|
|
56
|
+
- Reduce weighted-rook dispatch overhead with a dense-row connectivity check
|
|
57
|
+
and bitmask component traversal. Decomposed factors with at most three
|
|
58
|
+
vertices on the smaller side use a bounded exact subset calculation;
|
|
59
|
+
standalone connected inputs retain their CRT backend. Research transcripts
|
|
60
|
+
now distinguish exact small factors from modular factors and replay both.
|
|
61
|
+
- Factor disconnected bipartite support components in `weighted_rook_polynomial`
|
|
62
|
+
and convolve their exact coefficient vectors. Diagonal and star components
|
|
63
|
+
use direct formulas; rectangular components are padded internally and the
|
|
64
|
+
original output length is preserved.
|
|
65
|
+
- General exact integer computations use native Glynn for orders at least
|
|
66
|
+
four with odd moduli in the direct-multiplication range, while retaining
|
|
67
|
+
Ryser for tiny matrices, even moduli, and wider arithmetic. The explicit
|
|
68
|
+
Ryser entry point and compatibility with older extensions are preserved.
|
|
69
|
+
- Cache matrix-independent fixed-point factors used by rook-polynomial bounds
|
|
70
|
+
in bounded caches, reducing repeated small-matrix call overhead.
|
|
71
|
+
- The single-prime backend benchmark now compares general Ryser kernels on
|
|
72
|
+
the same matrix with one thread for each backend.
|
|
73
|
+
- The native rook-polynomial engine can pack up to sixteen same-width
|
|
74
|
+
pseudo-Mersenne prime fields in a fused traversal.
|
|
75
|
+
- Documentation now distinguishes installed public APIs, optional packaged
|
|
76
|
+
accelerators, manually built source tools, validated prototypes, and
|
|
77
|
+
private or archival evidence.
|
|
78
|
+
- Answer-dependent experiment captures and source pins are classified as
|
|
79
|
+
private evaluator evidence regardless of their directory.
|
|
80
|
+
- CUDA sources and selected wrappers are documented as checkout/sdist
|
|
81
|
+
reproducibility material rather than wheel-installed modules.
|
|
82
|
+
- The sdist excludes private checkout-only oracle/integrity tests; its
|
|
83
|
+
remaining test tree is self-contained and skips only unavailable optional
|
|
84
|
+
accelerators or explicitly gated device runs.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- Decode the characteristic-three portable result using Python integers before
|
|
89
|
+
bit operations, preserving compatibility with NumPy 1.23.5 as well as 2.x.
|
|
90
|
+
- Make weighted-study workers portable to macOS: report unavailable CPU pinning,
|
|
91
|
+
reject unsupported explicit pinning, and normalize peak RSS using host units.
|
|
92
|
+
- Preserve Schur queue parent-loss and timeout cleanup on macOS with a POSIX
|
|
93
|
+
pipe supervisor, while retaining Linux's direct PDEATHSIG worker path.
|
|
94
|
+
Process identity probes no longer rely on procfs outside Linux or silently
|
|
95
|
+
interpret inspection errors as worker death.
|
|
96
|
+
- Stop the weighted pseudo-Mersenne prime search when its monotone safety
|
|
97
|
+
condition rules out every remaining candidate, preserving selected primes
|
|
98
|
+
and fallback decisions.
|
|
99
|
+
- Correct the full-Haar permanent second moment in research analyses;
|
|
100
|
+
replace unequal and same-sample fitted KS comparisons with common held-out
|
|
101
|
+
distance diagnostics and retain sample/provenance archives for new runs.
|
|
102
|
+
- Replace numerical cycle-path permanent evaluation with a tested analytic
|
|
103
|
+
product, with explicit phase conventions and exact even-midpoint zeros.
|
|
104
|
+
- Revise manuscript novelty claims, statistical interpretations, and
|
|
105
|
+
unsupported finite-field proofs; document withdrawn historical figures
|
|
106
|
+
and remaining empirical verification requirements.
|
|
107
|
+
- Convert NumPy integer scalars to Python integers before exact bounds and
|
|
108
|
+
portable modular arithmetic, preventing fixed-width overflow from silently
|
|
109
|
+
corrupting permanent results.
|
|
110
|
+
- Preserve depth-first finishing order when splitting binary matching graphs
|
|
111
|
+
into strongly connected components, avoiding unnecessary merging of
|
|
112
|
+
independent blocks.
|
|
113
|
+
|
|
114
|
+
### Verification
|
|
115
|
+
|
|
116
|
+
- Start the September 5 independent verification campaign: standalone Schur
|
|
117
|
+
reconstruction audits and resumable finite-field rank enumeration reproduce
|
|
118
|
+
all eight retained prime-field counts through 23 and the F9 count. Polynomial
|
|
119
|
+
audits replay the retained order-36 interpolation and CRT transcript;
|
|
120
|
+
missing projection-residue arrays are explicitly reported. Reconstruction guarantees
|
|
121
|
+
are explicitly conditional on residues; computation records and fresh
|
|
122
|
+
cross-checks have separately stated coverage.
|
|
123
|
+
- Complete six fresh scalar GPU checks across principal orders 36/38 and rook
|
|
124
|
+
order 40; all agree with retained vectors. Freeze challenges before execution,
|
|
125
|
+
retain source snapshots, and resume from completed calls after interruption.
|
|
126
|
+
- Add a separate full-matrix Schur-45 CUDA Ryser checker with indexed block
|
|
127
|
+
checkpoints and exact input/build manifests. Small GPU and actual-block
|
|
128
|
+
pilots pass; the full independent modular traversal is running. The final
|
|
129
|
+
default suite passes 1,280 tests; future completion must be read from the
|
|
130
|
+
campaign result rather than inferred from a partial run.
|
|
131
|
+
- Principal-permanent and Krawtchouk prototype tests compare against
|
|
132
|
+
independent small-order definitions and seeded random cases.
|
|
133
|
+
- Retained long computations carry canonical hashes and independent
|
|
134
|
+
certificates; their assessments state which checks are recomputed and which
|
|
135
|
+
executions are preserved evidence.
|
|
136
|
+
|
|
137
|
+
## Historical version note
|
|
138
|
+
|
|
139
|
+
`setup.py` currently reports version `1.0.0`. The repository does not have a
|
|
140
|
+
matching release tag or a pre-existing changelog from which trustworthy
|
|
141
|
+
release dates and earlier version boundaries can be reconstructed. Earlier
|
|
142
|
+
changes are therefore left in git history and dated session logs instead of
|
|
143
|
+
being assigned invented release entries here.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Contributions should preserve exactness, portable fallbacks, reproducibility,
|
|
4
|
+
and the separation between installed APIs and research evidence. Read the
|
|
5
|
+
[`repository support levels`](REPOSITORY_LAYOUT.md) before
|
|
6
|
+
changing a backend or moving a file.
|
|
7
|
+
|
|
8
|
+
## Development setup
|
|
9
|
+
|
|
10
|
+
From a source checkout:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
python -m venv .venv
|
|
14
|
+
. .venv/bin/activate
|
|
15
|
+
python -m pip install --upgrade pip
|
|
16
|
+
python -m pip install -e ".[dev,numba]"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Linux source builds normally attempt the optional OpenMP extensions. To force
|
|
20
|
+
a portable installation while working on fallback behavior:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
PERM_BUILD_OPENMP=0 python -m pip install -e .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To rebuild the optional extensions in place:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
python setup.py build_ext --inplace
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Do not assume a clean worktree. Inspect `git status --short` before editing,
|
|
33
|
+
preserve unrelated changes, and keep refactors separate from algorithmic
|
|
34
|
+
changes.
|
|
35
|
+
|
|
36
|
+
## Test matrix
|
|
37
|
+
|
|
38
|
+
The default suite is the required baseline:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m pytest -q
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
It exercises portable code, installed extensions when present, source-wrapper
|
|
45
|
+
contracts, independent oracles, and quick retained-value checks. CUDA device
|
|
46
|
+
tests remain skipped unless explicitly enabled.
|
|
47
|
+
[`tests/README.md`](tests/README.md) maps the suite into portable, native,
|
|
48
|
+
source-wrapper, device, prototype, and repository-integrity groups.
|
|
49
|
+
|
|
50
|
+
The source distribution includes the reviewed public test suite. Both
|
|
51
|
+
checkout and unpacked sdist tests must collect and pass independently.
|
|
52
|
+
Distribution checks install real wheels/sdists outside the checkout; follow
|
|
53
|
+
[PACKAGING.md](PACKAGING.md) before preparing a pip release.
|
|
54
|
+
|
|
55
|
+
For generic or polynomial native work, require rather than silently skip the
|
|
56
|
+
extensions:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
python setup.py build_ext --inplace
|
|
60
|
+
PERM_REQUIRE_NATIVE_TESTS=1 \
|
|
61
|
+
python -m pytest -q tests/test_generic_native.py tests/test_polynomial_native.py
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Run the smallest focused module while iterating, then the full default suite.
|
|
65
|
+
Finish documentation and source changes with:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git diff --check
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### CUDA tests
|
|
72
|
+
|
|
73
|
+
CUDA changes require the opt-in device modules on a compatible host:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
PERM_RUN_CUDA_TESTS=1 \
|
|
77
|
+
PERM_CUDA_TEST_ARCH=native \
|
|
78
|
+
PERM_CUDA_ARCH=native \
|
|
79
|
+
python -m pytest -q \
|
|
80
|
+
tests/test_cuda_device_binary.py \
|
|
81
|
+
tests/test_cuda_device_modular.py \
|
|
82
|
+
tests/test_cuda_device_parallel.py \
|
|
83
|
+
tests/test_cuda_device_schur_glynn.py \
|
|
84
|
+
tests/test_cuda_device_utilities.py
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The order-41 and order-47 regressions require
|
|
88
|
+
`PERM_RUN_CUDA_LONG_TESTS=1` and `PERM_RUN_CUDA_FRONTIER_TESTS=1`,
|
|
89
|
+
respectively. They are deliberate long runs, not ordinary CI. See
|
|
90
|
+
[`CUDA.md`](CUDA.md#device-tests) for details.
|
|
91
|
+
|
|
92
|
+
## Exactness requirements
|
|
93
|
+
|
|
94
|
+
An optimized exact backend needs more than agreement on one large answer.
|
|
95
|
+
Include, as applicable:
|
|
96
|
+
|
|
97
|
+
- definition-level exhaustive tests at small order;
|
|
98
|
+
- seeded random comparisons with an independent implementation;
|
|
99
|
+
- edge cases for empty, singular, signed, sparse, and oversized inputs;
|
|
100
|
+
- a rigorous coefficient or permanent bound;
|
|
101
|
+
- CRT range, parity, divisibility, or centered-lift checks;
|
|
102
|
+
- deterministic instance and output hashes for retained long runs; and
|
|
103
|
+
- a foreign-modulus or otherwise independent holdout for expensive results.
|
|
104
|
+
|
|
105
|
+
Unsafe arithmetic ranges must be rejected or sent to a proven fallback. A
|
|
106
|
+
benchmark speedup is not permission to weaken an input contract.
|
|
107
|
+
|
|
108
|
+
## Benchmarks and retained results
|
|
109
|
+
|
|
110
|
+
Benchmark scripts should have a quick safe default, deterministic seeds or
|
|
111
|
+
instance labels, machine-readable output, repeat consistency checks, and an
|
|
112
|
+
explicit opt-in such as `--allow-large` for expensive exponential work.
|
|
113
|
+
|
|
114
|
+
When retaining a result, record:
|
|
115
|
+
|
|
116
|
+
- the exact command and relevant environment variables;
|
|
117
|
+
- CPU/GPU identity, compiler, architecture, and thread count;
|
|
118
|
+
- source and executable hashes when binaries matter;
|
|
119
|
+
- kernel and wall time separately when useful;
|
|
120
|
+
- the complete output or a canonical output hash; and
|
|
121
|
+
- the independent checks that passed.
|
|
122
|
+
|
|
123
|
+
Do not commit generated shared objects, compiler output, logs, or LaTeX
|
|
124
|
+
intermediates. Curated captures and figures are evidence only when their
|
|
125
|
+
provenance is documented.
|
|
126
|
+
|
|
127
|
+
## Documentation and compatibility
|
|
128
|
+
|
|
129
|
+
- Update README usage when a Tier-1 API changes.
|
|
130
|
+
- Update `REPOSITORY_LAYOUT.md` when support or packaging status changes.
|
|
131
|
+
- Update `CUDA.md` when a CUDA contract, build, or test gate changes.
|
|
132
|
+
- Add user-visible changes to the Unreleased section of `CHANGELOG.md`.
|
|
133
|
+
- Preserve current import paths and documented return conventions unless a
|
|
134
|
+
migration and compatibility plan is explicit.
|
|
135
|
+
- Treat native/CUDA file paths as interfaces: build files, tests, verifiers,
|
|
136
|
+
and retained hashes currently refer to them directly.
|
|
137
|
+
|
|
138
|
+
Historical session logs describe what happened at a date; do not silently
|
|
139
|
+
rewrite them to look current. Put current instructions in README, this guide,
|
|
140
|
+
or an adjacent assessment instead.
|