mixingmatrix 0.2.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.
- mixingmatrix-0.2.0/CHANGELOG.md +192 -0
- mixingmatrix-0.2.0/CITATION.cff +62 -0
- mixingmatrix-0.2.0/CODE_OF_CONDUCT.md +63 -0
- mixingmatrix-0.2.0/CONTRIBUTING.md +101 -0
- mixingmatrix-0.2.0/LICENSE +21 -0
- mixingmatrix-0.2.0/MANIFEST.in +20 -0
- mixingmatrix-0.2.0/PKG-INFO +244 -0
- mixingmatrix-0.2.0/README.md +204 -0
- mixingmatrix-0.2.0/benchmarks/README.md +47 -0
- mixingmatrix-0.2.0/benchmarks/measure.py +188 -0
- mixingmatrix-0.2.0/docs/api.md +131 -0
- mixingmatrix-0.2.0/docs/cookbook.md +283 -0
- mixingmatrix-0.2.0/docs/incremental.md +115 -0
- mixingmatrix-0.2.0/docs/internals.md +233 -0
- mixingmatrix-0.2.0/docs/limits.md +107 -0
- mixingmatrix-0.2.0/docs/plan.md +285 -0
- mixingmatrix-0.2.0/docs/quickstart.md +126 -0
- mixingmatrix-0.2.0/examples/incremental.py +90 -0
- mixingmatrix-0.2.0/examples/quickstart.py +90 -0
- mixingmatrix-0.2.0/paper.bib +99 -0
- mixingmatrix-0.2.0/paper.md +126 -0
- mixingmatrix-0.2.0/pyproject.toml +75 -0
- mixingmatrix-0.2.0/setup.cfg +4 -0
- mixingmatrix-0.2.0/src/mixingmatrix/__init__.py +93 -0
- mixingmatrix-0.2.0/src/mixingmatrix/_graph.py +164 -0
- mixingmatrix-0.2.0/src/mixingmatrix/api.py +269 -0
- mixingmatrix-0.2.0/src/mixingmatrix/baselines.py +152 -0
- mixingmatrix-0.2.0/src/mixingmatrix/certificates.py +572 -0
- mixingmatrix-0.2.0/src/mixingmatrix/cli.py +135 -0
- mixingmatrix-0.2.0/src/mixingmatrix/evaluate.py +271 -0
- mixingmatrix-0.2.0/src/mixingmatrix/incremental.py +325 -0
- mixingmatrix-0.2.0/src/mixingmatrix/operators.py +243 -0
- mixingmatrix-0.2.0/src/mixingmatrix/problem.py +220 -0
- mixingmatrix-0.2.0/src/mixingmatrix/projection.py +279 -0
- mixingmatrix-0.2.0/src/mixingmatrix/simulate.py +82 -0
- mixingmatrix-0.2.0/src/mixingmatrix/solution.py +271 -0
- mixingmatrix-0.2.0/src/mixingmatrix/solvers/__init__.py +49 -0
- mixingmatrix-0.2.0/src/mixingmatrix/solvers/admm.py +479 -0
- mixingmatrix-0.2.0/src/mixingmatrix/solvers/base.py +221 -0
- mixingmatrix-0.2.0/src/mixingmatrix/solvers/conic.py +100 -0
- mixingmatrix-0.2.0/src/mixingmatrix/solvers/firstorder.py +271 -0
- mixingmatrix-0.2.0/src/mixingmatrix/spectral.py +377 -0
- mixingmatrix-0.2.0/src/mixingmatrix.egg-info/SOURCES.txt +49 -0
- mixingmatrix-0.2.0/tests/conftest.py +46 -0
- mixingmatrix-0.2.0/tests/test_certificates.py +160 -0
- mixingmatrix-0.2.0/tests/test_cli.py +63 -0
- mixingmatrix-0.2.0/tests/test_evaluate.py +132 -0
- mixingmatrix-0.2.0/tests/test_incremental.py +153 -0
- mixingmatrix-0.2.0/tests/test_large.py +389 -0
- mixingmatrix-0.2.0/tests/test_properties.py +81 -0
- mixingmatrix-0.2.0/tests/test_solve.py +165 -0
- mixingmatrix-0.2.0/tests/test_solvers.py +180 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 -- scaling
|
|
4
|
+
|
|
5
|
+
### OSQP is now a required dependency
|
|
6
|
+
|
|
7
|
+
It was listed as the optional `[fast]` extra, but the SciPy fallback is roughly
|
|
8
|
+
100x slower -- optional in name only, and a trap for anyone who installed
|
|
9
|
+
without it. `pip install mixingmatrix` now gets it. The SciPy backend remains
|
|
10
|
+
selectable as `qp_backend="scipy"`, where it serves as an independent
|
|
11
|
+
implementation to check OSQP against rather than as a fallback; the fallback
|
|
12
|
+
warning is gone, and a missing OSQP now raises with instructions instead of
|
|
13
|
+
silently degrading.
|
|
14
|
+
|
|
15
|
+
`solve` now runs at n = 10^4. What was in the way was memory, not the
|
|
16
|
+
eigendecomposition: the solver stored five dense `n x n` arrays, which is
|
|
17
|
+
3.7 GB at that size.
|
|
18
|
+
|
|
19
|
+
### The change
|
|
20
|
+
|
|
21
|
+
* **The scaled dual is exactly low rank.** `U_new = U + (B - Q - Z_new)` and
|
|
22
|
+
`Z_new = V - Delta` give `U_new = Delta` identically -- the `U` cancels -- so
|
|
23
|
+
the dual is the rank-2-to-9 spectral correction from the first iteration
|
|
24
|
+
onward, and `Z` is `B - Q + U_prev - U`. Every matrix in the iteration is
|
|
25
|
+
now `diag + edges + P diag(c) P^T`, held by the new
|
|
26
|
+
`mixingmatrix.operators.SymOperator`.
|
|
27
|
+
* **A matrix-free ADMM driver** (`_admm_matrix_free`) built on that, selected
|
|
28
|
+
automatically above `dense_below` nodes. Memory `O(m + nk)` instead of
|
|
29
|
+
`O(n^2)`: 3.7 GB to a few megabytes at n = 10^4. It is the *same iteration*, not
|
|
30
|
+
an approximation -- `tests/test_large.py` asserts it reproduces the dense
|
|
31
|
+
driver's iterate to 1e-9.
|
|
32
|
+
* **The certificate is stored in factored form** `(Vp, Yp, Vm, Ym)` rather than
|
|
33
|
+
as an assembled `G`. Reduced costs, `tr G`, `q^T G q` and `||G||_*` all
|
|
34
|
+
become small products in the factors, and `||G||_* = tr(Yp) + tr(Ym)` is now
|
|
35
|
+
exact rather than an eigendecomposition.
|
|
36
|
+
* **Sparse paths throughout**: `slem`, `is_valid`, `stationary_distribution`,
|
|
37
|
+
`gossip`, all four baselines, and `Solution.weights`, which is now built
|
|
38
|
+
straight from the edge weights. `Solution.to_dense()` refuses above 16384
|
|
39
|
+
nodes instead of allocating 2 GB.
|
|
40
|
+
|
|
41
|
+
### The inner QP, which turned out to be the real ceiling at n = 10^4
|
|
42
|
+
|
|
43
|
+
With the dense arrays gone, profiling put **74 s in OSQP's setup** and 1.8 s in
|
|
44
|
+
every projection at n = 10^4, against 0.17 s for the whole spectral step -- and
|
|
45
|
+
the setup time did not move with the tolerance, which is the signature of
|
|
46
|
+
fill-in rather than iteration count.
|
|
47
|
+
|
|
48
|
+
* **Split the linear map out of the inner QP.** `P = 4I + 2 A^T A` is the edge
|
|
49
|
+
adjacency matrix; introducing `y = A w` leaves a diagonal Hessian and a KKT
|
|
50
|
+
system with the incidence matrix's sparsity. Identical solutions to 1e-16.
|
|
51
|
+
Setup at n = 10^4: 96 s to 10.5 s. FDLA's unconstrained solve moved to the
|
|
52
|
+
matching saddle system for the same reason.
|
|
53
|
+
* **Polishing off above n = 2000.** It re-factorises the active-set system and
|
|
54
|
+
at this size reported "no active set detected" while doubling the solve.
|
|
55
|
+
|
|
56
|
+
Together: 74 s of setup and 1.8 s per projection became **7.8 s and 0.39 s**,
|
|
57
|
+
roughly 100x more ADMM iterations per second at n = 10^4.
|
|
58
|
+
|
|
59
|
+
What remains is fill-in, and it belongs to the graph rather than the code: the
|
|
60
|
+
same n = 10^4 solve sets up in 7.8 s on a random 4-regular graph and **0.04 s**
|
|
61
|
+
on a 100x100 grid. The scaling benchmark now reports both families.
|
|
62
|
+
|
|
63
|
+
### A spectral step that converges the subspace, not the eigenvectors
|
|
64
|
+
|
|
65
|
+
The epigraph prox uses `sum_{active} (lambda_i - sign(lambda_i) s) v_i v_i^T`,
|
|
66
|
+
which is unchanged by any rotation within a degenerate group -- it needs the
|
|
67
|
+
span. ARPACK's convergence test is per eigenpair, so it restarts trying to
|
|
68
|
+
separate eigenvalues that are genuinely equal, and at an SLEM optimum they are:
|
|
69
|
+
a 100x100 grid at n = 10^4 has an active set of 30 packed inside 1e-4, and cost
|
|
70
|
+
1767 products per iteration.
|
|
71
|
+
|
|
72
|
+
* New `spectral.subspace_extreme`: block subspace iteration with a
|
|
73
|
+
caller-supplied acceptance test, warm-started across outer iterations. The
|
|
74
|
+
ADMM's test is "every pair above the threshold converged, and no inactive
|
|
75
|
+
Ritz value can reach the threshold given its own residual bound", so a
|
|
76
|
+
converged return is exactly as rigorous as ARPACK's. It also applies the
|
|
77
|
+
operator to `k` vectors per call rather than one.
|
|
78
|
+
* Non-convergence falls back to ARPACK rather than proceeding on an unverified
|
|
79
|
+
subspace.
|
|
80
|
+
* **Guarded**, because block iteration converges at rate
|
|
81
|
+
`|lambda_{k+1}/lambda_k|` and an expander's extreme spectrum is flat: three
|
|
82
|
+
consecutive failures switch it off for the solve. Unguarded it was 1.7x
|
|
83
|
+
*worse* on expanders while being 2.25x better on grids; guarded it is never
|
|
84
|
+
worse and up to 2.25x better.
|
|
85
|
+
|
|
86
|
+
### Two fixes worth 3x at n = 1000
|
|
87
|
+
|
|
88
|
+
* The Lanczos window `k` ratcheted up early (when rho is small and half the
|
|
89
|
+
spectrum is active) and never came back down, sitting at 48 while the active
|
|
90
|
+
set was 3-5. Letting it shrink to `2 k_active + 4`: 564 matvecs per
|
|
91
|
+
iteration to 234, same answer.
|
|
92
|
+
* The ARPACK tolerance was divided by `n`, asking for 1e-8 at n = 1000 and
|
|
93
|
+
buying only restarts.
|
|
94
|
+
|
|
95
|
+
### rho did not scale either
|
|
96
|
+
|
|
97
|
+
`rho` sets the epigraph threshold through `rho * sum_{active}(a_i - s) = 1`, so
|
|
98
|
+
it decides how much of the spectrum is active -- and the active set is exactly
|
|
99
|
+
what the spectral step must compute. A fixed `rho = 1` therefore got worse as
|
|
100
|
+
graphs grew, and the *first* iterations of a large solve were its most
|
|
101
|
+
expensive. Eight iterations from a cold start at n = 10^4, random 4-regular:
|
|
102
|
+
|
|
103
|
+
| rho | active set | per iteration |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| 1 | 30 | 10996 ms |
|
|
106
|
+
| 10 | 8 | 1980 ms |
|
|
107
|
+
| 100 | 2 | 1184 ms |
|
|
108
|
+
|
|
109
|
+
Identical SLEM in every row. Boyd's residual-balancing heuristic does correct
|
|
110
|
+
`rho`, but only every 25 iterations, and at 11 s an iteration a 45-second
|
|
111
|
+
budget never reaches 25 -- the heuristic was outrun by the problem it exists to
|
|
112
|
+
fix.
|
|
113
|
+
|
|
114
|
+
`rho` now starts at `sqrt(n) / 8` above n = 2000 and stays exactly `1.0` below
|
|
115
|
+
it. The threshold is empirical: a naive size-scaling *regressed* n = 1000
|
|
116
|
+
(SLEM 0.850490 against 0.848264 at the same budget), because a larger `rho`
|
|
117
|
+
also slows the outer iteration and below a few thousand nodes that costs more
|
|
118
|
+
than the cheaper spectral step buys. At a 45-second budget, n = 10^4:
|
|
119
|
+
**3 iterations became 91**.
|
|
120
|
+
|
|
121
|
+
### Honest about what this does and does not buy
|
|
122
|
+
|
|
123
|
+
Memory is solved; convergence is not. At n = 10^4 a time-limited solve returns
|
|
124
|
+
a feasible matrix that beats Metropolis-Hastings with a large but rigorous
|
|
125
|
+
certified gap -- not a converged optimum. `docs/internals.md` and `docs/limits.md` say which method
|
|
126
|
+
to use at which size, and what each one actually delivers.
|
|
127
|
+
|
|
128
|
+
`method="smoothing"` now raises above 2000 nodes rather than attempting a
|
|
129
|
+
dense eigendecomposition: its softmax gradient weights every eigenvalue, so it
|
|
130
|
+
needs the full spectrum by construction.
|
|
131
|
+
|
|
132
|
+
## 0.1.0
|
|
133
|
+
|
|
134
|
+
First release.
|
|
135
|
+
|
|
136
|
+
### Core
|
|
137
|
+
|
|
138
|
+
* `solve(G)` -- fastest-mixing Markov chain weights, with `method="auto"`
|
|
139
|
+
choosing an exact conic solve for small graphs (when CVXPY is installed) and
|
|
140
|
+
ADMM otherwise.
|
|
141
|
+
* `Solution` with node-label round-tripping, a SLEM **recomputed from the
|
|
142
|
+
returned matrix** on every access, `support` / `zero_edges`, and a
|
|
143
|
+
`summary()` that never prints success over a run that hit a limit.
|
|
144
|
+
* `time_limit` returns the best feasible iterate rather than nothing; `callback`
|
|
145
|
+
can stop a solve and get the same guarantee.
|
|
146
|
+
|
|
147
|
+
### Certificates
|
|
148
|
+
|
|
149
|
+
* `Solution.certified_gap` -- a rigorous bound from the dual, valid even for an
|
|
150
|
+
under-converged solve.
|
|
151
|
+
* `can_remove` / `can_improve` -- single-edge certificates. Removal is always a
|
|
152
|
+
proof; addition is a proof only when the reduced cost is nonnegative, and the
|
|
153
|
+
API is explicit about the asymmetry.
|
|
154
|
+
* `update(sol, G_new)` -- skips the solve entirely for a certified change, warm
|
|
155
|
+
starts a priced removal (1.5x-17x fewer iterations on the measured families),
|
|
156
|
+
and falls back to a cold solve when the certificate says a warm start will not
|
|
157
|
+
help.
|
|
158
|
+
|
|
159
|
+
### Baselines and evaluation
|
|
160
|
+
|
|
161
|
+
* `metropolis_hastings`, `max_degree`, `best_constant` (solved in closed form),
|
|
162
|
+
`lazy_random_walk`.
|
|
163
|
+
* `slem`, `spectral_gap`, `consensus_rounds`, `mixing_time`,
|
|
164
|
+
`stationary_distribution`, `is_valid`.
|
|
165
|
+
* `compare(G)` -- one table, the optimum against the usual weights, on the
|
|
166
|
+
user's own graph.
|
|
167
|
+
* `gossip`, `rounds_to_consensus` -- run the chain instead of trusting the
|
|
168
|
+
eigenvalue.
|
|
169
|
+
|
|
170
|
+
### Variants
|
|
171
|
+
|
|
172
|
+
* `allow_negative=True` -- FDLA (Xiao & Boyd 2004).
|
|
173
|
+
* `stationary=pi` -- the fastest reversible chain with a given stationary
|
|
174
|
+
distribution, through the same parameterisation rather than a second code
|
|
175
|
+
path.
|
|
176
|
+
* `symmetric=False` raises `NotImplementedError` rather than solving something
|
|
177
|
+
else.
|
|
178
|
+
|
|
179
|
+
### Solvers
|
|
180
|
+
|
|
181
|
+
* `admm` (default) -- exact epigraph prox, dense below n=400 and Lanczos above,
|
|
182
|
+
with the inner QP tolerance and the ARPACK tolerance both tied to the outer
|
|
183
|
+
residual.
|
|
184
|
+
* `smoothing` -- accelerated projected gradient on a softmax surrogate;
|
|
185
|
+
moderate accuracy, no dual variable.
|
|
186
|
+
* `subgradient` -- the honest naive method, reported as `stalled` because it
|
|
187
|
+
certifies nothing.
|
|
188
|
+
* `cvxpy` -- exact interior-point reference.
|
|
189
|
+
|
|
190
|
+
### Command line
|
|
191
|
+
|
|
192
|
+
`mixingmatrix solve|compare|info`, reading five graph formats and writing four.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
title: "mixingmatrix: optimal mixing matrices for graphs"
|
|
3
|
+
message: "If you use this software, please cite it and the papers it implements."
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- given-names: Raghuram
|
|
7
|
+
email: raghuram87@gmail.com
|
|
8
|
+
repository-code: "https://github.com/raghuram87/mixingmatrix"
|
|
9
|
+
license: MIT
|
|
10
|
+
version: 0.2.0
|
|
11
|
+
keywords:
|
|
12
|
+
- fastest mixing Markov chain
|
|
13
|
+
- distributed averaging
|
|
14
|
+
- consensus
|
|
15
|
+
- gossip
|
|
16
|
+
- spectral graph theory
|
|
17
|
+
- semidefinite programming
|
|
18
|
+
references:
|
|
19
|
+
- type: article
|
|
20
|
+
title: "Fastest Mixing Markov Chain on a Graph"
|
|
21
|
+
authors:
|
|
22
|
+
- family-names: Boyd
|
|
23
|
+
given-names: Stephen
|
|
24
|
+
- family-names: Diaconis
|
|
25
|
+
given-names: Persi
|
|
26
|
+
- family-names: Xiao
|
|
27
|
+
given-names: Lin
|
|
28
|
+
journal: "SIAM Review"
|
|
29
|
+
volume: 46
|
|
30
|
+
issue: 4
|
|
31
|
+
year: 2004
|
|
32
|
+
doi: 10.1137/S0036144503423264
|
|
33
|
+
- type: article
|
|
34
|
+
title: "Fast linear iterations for distributed averaging"
|
|
35
|
+
authors:
|
|
36
|
+
- family-names: Xiao
|
|
37
|
+
given-names: Lin
|
|
38
|
+
- family-names: Boyd
|
|
39
|
+
given-names: Stephen
|
|
40
|
+
journal: "Systems & Control Letters"
|
|
41
|
+
volume: 53
|
|
42
|
+
issue: 1
|
|
43
|
+
year: 2004
|
|
44
|
+
doi: 10.1016/j.sysconle.2004.02.022
|
|
45
|
+
- type: article
|
|
46
|
+
title: "Distributed Optimization and Statistical Learning via the Alternating Direction Method of Multipliers"
|
|
47
|
+
authors:
|
|
48
|
+
- family-names: Boyd
|
|
49
|
+
given-names: Stephen
|
|
50
|
+
- family-names: Parikh
|
|
51
|
+
given-names: Neal
|
|
52
|
+
- family-names: Chu
|
|
53
|
+
given-names: Eric
|
|
54
|
+
- family-names: Peleato
|
|
55
|
+
given-names: Borja
|
|
56
|
+
- family-names: Eckstein
|
|
57
|
+
given-names: Jonathan
|
|
58
|
+
journal: "Foundations and Trends in Machine Learning"
|
|
59
|
+
volume: 3
|
|
60
|
+
issue: 1
|
|
61
|
+
year: 2011
|
|
62
|
+
doi: 10.1561/2200000016
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, colour, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behaviour that contributes to a positive environment:
|
|
18
|
+
|
|
19
|
+
* Demonstrating empathy and kindness toward other people
|
|
20
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
21
|
+
* Giving and gracefully accepting constructive feedback
|
|
22
|
+
* Accepting responsibility and apologising to those affected by our mistakes,
|
|
23
|
+
and learning from the experience
|
|
24
|
+
* Focusing on what is best for the overall community
|
|
25
|
+
|
|
26
|
+
Examples of unacceptable behaviour:
|
|
27
|
+
|
|
28
|
+
* The use of sexualised language or imagery, and sexual attention or advances
|
|
29
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
30
|
+
* Public or private harassment
|
|
31
|
+
* Publishing others' private information, such as a physical or email address,
|
|
32
|
+
without their explicit permission
|
|
33
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
34
|
+
professional setting
|
|
35
|
+
|
|
36
|
+
## Enforcement Responsibilities
|
|
37
|
+
|
|
38
|
+
Project maintainers are responsible for clarifying and enforcing our standards
|
|
39
|
+
of acceptable behaviour and will take appropriate and fair corrective action in
|
|
40
|
+
response to any behaviour that they deem inappropriate, threatening, offensive,
|
|
41
|
+
or harmful.
|
|
42
|
+
|
|
43
|
+
## Scope
|
|
44
|
+
|
|
45
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
46
|
+
an individual is officially representing the community in public spaces.
|
|
47
|
+
|
|
48
|
+
## Enforcement
|
|
49
|
+
|
|
50
|
+
Instances of abusive, harassing, or otherwise unacceptable behaviour may be
|
|
51
|
+
reported to the project maintainers at raghuram87@gmail.com. All complaints
|
|
52
|
+
will be reviewed and investigated promptly and fairly.
|
|
53
|
+
|
|
54
|
+
Project maintainers are obligated to respect the privacy and security of the
|
|
55
|
+
reporter of any incident.
|
|
56
|
+
|
|
57
|
+
## Attribution
|
|
58
|
+
|
|
59
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
60
|
+
version 2.1, available at
|
|
61
|
+
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
|
62
|
+
|
|
63
|
+
[homepage]: https://www.contributor-covenant.org
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Getting set up
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install -e ".[dev,all]"
|
|
7
|
+
pytest
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
OSQP is a required dependency and is installed with the package.
|
|
11
|
+
|
|
12
|
+
## Invariants to preserve
|
|
13
|
+
|
|
14
|
+
1. `Solution.slem` is recomputed from `Solution.weights` on every access, never
|
|
15
|
+
taken from a solver's own objective. A new solver returns weights and
|
|
16
|
+
effort; the package scores it.
|
|
17
|
+
2. `status == "optimal"` means the solver met its stopping test. A run that
|
|
18
|
+
stopped on a limit warns and reports the limit it hit.
|
|
19
|
+
3. `EdgeCertificate.is_proof` is `True` only when the mathematics establishes
|
|
20
|
+
the claim. `can_improve` is asymmetric by design — see
|
|
21
|
+
`docs/incremental.md`.
|
|
22
|
+
4. Node labels round-trip. Integer indexing stays inside `GraphIndex`.
|
|
23
|
+
|
|
24
|
+
## Adding a solver
|
|
25
|
+
|
|
26
|
+
One function and one decorator:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from mixingmatrix.solvers.base import SolveOptions, SolverOutput, register_solver
|
|
30
|
+
|
|
31
|
+
@register_solver("my-method")
|
|
32
|
+
def my_method(problem, opts: SolveOptions) -> SolverOutput:
|
|
33
|
+
w = ... # edge weights, feasible
|
|
34
|
+
return SolverOutput(w=w, status="optimal", iterations=k)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`problem` is a `MixingProblem`: `to_matrix`, `to_sparse`, `edge_gradient`,
|
|
38
|
+
`objective`, `metropolis_weights`, and the constraint data `A`, `q`, `nonneg`.
|
|
39
|
+
Use `EdgeProjector` for the projection rather than writing your own.
|
|
40
|
+
|
|
41
|
+
See `docs/internals.md` for how the solver works.
|
|
42
|
+
|
|
43
|
+
Then import the module in `mixingmatrix/solvers/__init__.py` so the decorator runs.
|
|
44
|
+
`tests/test_solvers.py::test_available_methods_are_all_usable` picks it up
|
|
45
|
+
automatically and will hold you to feasibility.
|
|
46
|
+
|
|
47
|
+
If your method has no optimality certificate, report `stalled`, not `optimal`,
|
|
48
|
+
and leave `dual_residual` as `nan`.
|
|
49
|
+
|
|
50
|
+
## Adding a baseline
|
|
51
|
+
|
|
52
|
+
Put it in `baselines.py`, return a `scipy.sparse.csr_matrix`, and add it to
|
|
53
|
+
`BASELINES`. `tests/test_evaluate.py::test_baselines_are_feasible` will check
|
|
54
|
+
it across every graph family in `conftest.py`.
|
|
55
|
+
|
|
56
|
+
## Tests
|
|
57
|
+
|
|
58
|
+
* `test_solve.py` — core contract, edge cases, known closed-form optima.
|
|
59
|
+
* `test_certificates.py` — each certificate is checked by making the change and
|
|
60
|
+
re-solving, not just by internal consistency.
|
|
61
|
+
* `test_incremental.py` — the update path agrees with a cold solve.
|
|
62
|
+
* `test_large.py` — the matrix-free driver matches the dense one, and nothing
|
|
63
|
+
densifies at scale.
|
|
64
|
+
* `test_properties.py` — Hypothesis, for the feasibility invariants.
|
|
65
|
+
* `test_solvers.py`, `test_evaluate.py`, `test_cli.py` — the rest.
|
|
66
|
+
|
|
67
|
+
Numbers quoted in the README come from `benchmarks/measure.py`. If you change
|
|
68
|
+
something that moves them, re-run it and update the tables rather than leaving
|
|
69
|
+
a claim that used to be true.
|
|
70
|
+
|
|
71
|
+
## Style
|
|
72
|
+
|
|
73
|
+
`ruff check` and `ruff format` with the settings in `pyproject.toml`. Comments
|
|
74
|
+
explain *why*, not what; the code already says what.
|
|
75
|
+
|
|
76
|
+
## Cutting a release
|
|
77
|
+
|
|
78
|
+
1. Update `CHANGELOG.md` and bump the version in `pyproject.toml`,
|
|
79
|
+
`CITATION.cff` and `src/mixingmatrix/__init__.py`.
|
|
80
|
+
2. Build and validate:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python -m build
|
|
84
|
+
python -m twine check dist/*
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
3. Install the wheel into a clean environment and import it from there, not
|
|
88
|
+
from the source tree — this is what catches a packaging mistake before it
|
|
89
|
+
reaches an index where a version can never be replaced.
|
|
90
|
+
4. Rehearse on TestPyPI, install from it, then upload to PyPI.
|
|
91
|
+
5. Tag and release:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
git tag -a vX.Y.Z -m "mixingmatrix X.Y.Z"
|
|
95
|
+
git push origin vX.Y.Z
|
|
96
|
+
gh release create vX.Y.Z --title "mixingmatrix X.Y.Z" --generate-notes
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If the repository is linked to Zenodo, a DOI is minted from the release
|
|
100
|
+
automatically; `.zenodo.json` controls its metadata. The link must be in place
|
|
101
|
+
*before* the release is created, as Zenodo only sees releases made after it.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Raghuram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# The sdist doubles as the archived artefact a journal submission points at, so
|
|
2
|
+
# it carries the documentation, the paper and the community files -- not just
|
|
3
|
+
# the importable code.
|
|
4
|
+
include LICENSE
|
|
5
|
+
include README.md
|
|
6
|
+
include CHANGELOG.md
|
|
7
|
+
include CITATION.cff
|
|
8
|
+
include CONTRIBUTING.md
|
|
9
|
+
include CODE_OF_CONDUCT.md
|
|
10
|
+
include paper.md
|
|
11
|
+
include paper.bib
|
|
12
|
+
recursive-include docs *.md
|
|
13
|
+
recursive-include examples *.py
|
|
14
|
+
recursive-include benchmarks *.py *.md
|
|
15
|
+
recursive-include tests *.py
|
|
16
|
+
|
|
17
|
+
# Build and test cruft.
|
|
18
|
+
global-exclude *.py[cod]
|
|
19
|
+
global-exclude __pycache__
|
|
20
|
+
prune src/mixingmatrix.egg-info
|