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.
Files changed (52) hide show
  1. mixingmatrix-0.2.0/CHANGELOG.md +192 -0
  2. mixingmatrix-0.2.0/CITATION.cff +62 -0
  3. mixingmatrix-0.2.0/CODE_OF_CONDUCT.md +63 -0
  4. mixingmatrix-0.2.0/CONTRIBUTING.md +101 -0
  5. mixingmatrix-0.2.0/LICENSE +21 -0
  6. mixingmatrix-0.2.0/MANIFEST.in +20 -0
  7. mixingmatrix-0.2.0/PKG-INFO +244 -0
  8. mixingmatrix-0.2.0/README.md +204 -0
  9. mixingmatrix-0.2.0/benchmarks/README.md +47 -0
  10. mixingmatrix-0.2.0/benchmarks/measure.py +188 -0
  11. mixingmatrix-0.2.0/docs/api.md +131 -0
  12. mixingmatrix-0.2.0/docs/cookbook.md +283 -0
  13. mixingmatrix-0.2.0/docs/incremental.md +115 -0
  14. mixingmatrix-0.2.0/docs/internals.md +233 -0
  15. mixingmatrix-0.2.0/docs/limits.md +107 -0
  16. mixingmatrix-0.2.0/docs/plan.md +285 -0
  17. mixingmatrix-0.2.0/docs/quickstart.md +126 -0
  18. mixingmatrix-0.2.0/examples/incremental.py +90 -0
  19. mixingmatrix-0.2.0/examples/quickstart.py +90 -0
  20. mixingmatrix-0.2.0/paper.bib +99 -0
  21. mixingmatrix-0.2.0/paper.md +126 -0
  22. mixingmatrix-0.2.0/pyproject.toml +75 -0
  23. mixingmatrix-0.2.0/setup.cfg +4 -0
  24. mixingmatrix-0.2.0/src/mixingmatrix/__init__.py +93 -0
  25. mixingmatrix-0.2.0/src/mixingmatrix/_graph.py +164 -0
  26. mixingmatrix-0.2.0/src/mixingmatrix/api.py +269 -0
  27. mixingmatrix-0.2.0/src/mixingmatrix/baselines.py +152 -0
  28. mixingmatrix-0.2.0/src/mixingmatrix/certificates.py +572 -0
  29. mixingmatrix-0.2.0/src/mixingmatrix/cli.py +135 -0
  30. mixingmatrix-0.2.0/src/mixingmatrix/evaluate.py +271 -0
  31. mixingmatrix-0.2.0/src/mixingmatrix/incremental.py +325 -0
  32. mixingmatrix-0.2.0/src/mixingmatrix/operators.py +243 -0
  33. mixingmatrix-0.2.0/src/mixingmatrix/problem.py +220 -0
  34. mixingmatrix-0.2.0/src/mixingmatrix/projection.py +279 -0
  35. mixingmatrix-0.2.0/src/mixingmatrix/simulate.py +82 -0
  36. mixingmatrix-0.2.0/src/mixingmatrix/solution.py +271 -0
  37. mixingmatrix-0.2.0/src/mixingmatrix/solvers/__init__.py +49 -0
  38. mixingmatrix-0.2.0/src/mixingmatrix/solvers/admm.py +479 -0
  39. mixingmatrix-0.2.0/src/mixingmatrix/solvers/base.py +221 -0
  40. mixingmatrix-0.2.0/src/mixingmatrix/solvers/conic.py +100 -0
  41. mixingmatrix-0.2.0/src/mixingmatrix/solvers/firstorder.py +271 -0
  42. mixingmatrix-0.2.0/src/mixingmatrix/spectral.py +377 -0
  43. mixingmatrix-0.2.0/src/mixingmatrix.egg-info/SOURCES.txt +49 -0
  44. mixingmatrix-0.2.0/tests/conftest.py +46 -0
  45. mixingmatrix-0.2.0/tests/test_certificates.py +160 -0
  46. mixingmatrix-0.2.0/tests/test_cli.py +63 -0
  47. mixingmatrix-0.2.0/tests/test_evaluate.py +132 -0
  48. mixingmatrix-0.2.0/tests/test_incremental.py +153 -0
  49. mixingmatrix-0.2.0/tests/test_large.py +389 -0
  50. mixingmatrix-0.2.0/tests/test_properties.py +81 -0
  51. mixingmatrix-0.2.0/tests/test_solve.py +165 -0
  52. 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