gpgraph-v2 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.
@@ -0,0 +1,283 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 3
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
10
+
11
+ [[package]]
12
+ name = "crossbeam-deque"
13
+ version = "0.8.6"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
16
+ dependencies = [
17
+ "crossbeam-epoch",
18
+ "crossbeam-utils",
19
+ ]
20
+
21
+ [[package]]
22
+ name = "crossbeam-epoch"
23
+ version = "0.9.18"
24
+ source = "registry+https://github.com/rust-lang/crates.io-index"
25
+ checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
26
+ dependencies = [
27
+ "crossbeam-utils",
28
+ ]
29
+
30
+ [[package]]
31
+ name = "crossbeam-utils"
32
+ version = "0.8.21"
33
+ source = "registry+https://github.com/rust-lang/crates.io-index"
34
+ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
35
+
36
+ [[package]]
37
+ name = "either"
38
+ version = "1.15.0"
39
+ source = "registry+https://github.com/rust-lang/crates.io-index"
40
+ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
41
+
42
+ [[package]]
43
+ name = "gpgraph-core"
44
+ version = "1.0.0"
45
+ dependencies = [
46
+ "ndarray",
47
+ "numpy",
48
+ "pyo3",
49
+ "rayon",
50
+ "rustc-hash",
51
+ ]
52
+
53
+ [[package]]
54
+ name = "heck"
55
+ version = "0.5.0"
56
+ source = "registry+https://github.com/rust-lang/crates.io-index"
57
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
58
+
59
+ [[package]]
60
+ name = "libc"
61
+ version = "0.2.185"
62
+ source = "registry+https://github.com/rust-lang/crates.io-index"
63
+ checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
64
+
65
+ [[package]]
66
+ name = "matrixmultiply"
67
+ version = "0.3.10"
68
+ source = "registry+https://github.com/rust-lang/crates.io-index"
69
+ checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
70
+ dependencies = [
71
+ "autocfg",
72
+ "rawpointer",
73
+ ]
74
+
75
+ [[package]]
76
+ name = "ndarray"
77
+ version = "0.17.2"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d"
80
+ dependencies = [
81
+ "matrixmultiply",
82
+ "num-complex",
83
+ "num-integer",
84
+ "num-traits",
85
+ "portable-atomic",
86
+ "portable-atomic-util",
87
+ "rawpointer",
88
+ ]
89
+
90
+ [[package]]
91
+ name = "num-complex"
92
+ version = "0.4.6"
93
+ source = "registry+https://github.com/rust-lang/crates.io-index"
94
+ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
95
+ dependencies = [
96
+ "num-traits",
97
+ ]
98
+
99
+ [[package]]
100
+ name = "num-integer"
101
+ version = "0.1.46"
102
+ source = "registry+https://github.com/rust-lang/crates.io-index"
103
+ checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
104
+ dependencies = [
105
+ "num-traits",
106
+ ]
107
+
108
+ [[package]]
109
+ name = "num-traits"
110
+ version = "0.2.19"
111
+ source = "registry+https://github.com/rust-lang/crates.io-index"
112
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
113
+ dependencies = [
114
+ "autocfg",
115
+ ]
116
+
117
+ [[package]]
118
+ name = "numpy"
119
+ version = "0.28.0"
120
+ source = "registry+https://github.com/rust-lang/crates.io-index"
121
+ checksum = "778da78c64ddc928ebf5ad9df5edf0789410ff3bdbf3619aed51cd789a6af1e2"
122
+ dependencies = [
123
+ "libc",
124
+ "ndarray",
125
+ "num-complex",
126
+ "num-integer",
127
+ "num-traits",
128
+ "pyo3",
129
+ "pyo3-build-config",
130
+ "rustc-hash",
131
+ ]
132
+
133
+ [[package]]
134
+ name = "once_cell"
135
+ version = "1.21.4"
136
+ source = "registry+https://github.com/rust-lang/crates.io-index"
137
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
138
+
139
+ [[package]]
140
+ name = "portable-atomic"
141
+ version = "1.13.1"
142
+ source = "registry+https://github.com/rust-lang/crates.io-index"
143
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
144
+
145
+ [[package]]
146
+ name = "portable-atomic-util"
147
+ version = "0.2.7"
148
+ source = "registry+https://github.com/rust-lang/crates.io-index"
149
+ checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
150
+ dependencies = [
151
+ "portable-atomic",
152
+ ]
153
+
154
+ [[package]]
155
+ name = "proc-macro2"
156
+ version = "1.0.106"
157
+ source = "registry+https://github.com/rust-lang/crates.io-index"
158
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
159
+ dependencies = [
160
+ "unicode-ident",
161
+ ]
162
+
163
+ [[package]]
164
+ name = "pyo3"
165
+ version = "0.28.3"
166
+ source = "registry+https://github.com/rust-lang/crates.io-index"
167
+ checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
168
+ dependencies = [
169
+ "libc",
170
+ "once_cell",
171
+ "portable-atomic",
172
+ "pyo3-build-config",
173
+ "pyo3-ffi",
174
+ "pyo3-macros",
175
+ ]
176
+
177
+ [[package]]
178
+ name = "pyo3-build-config"
179
+ version = "0.28.3"
180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
181
+ checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
182
+ dependencies = [
183
+ "target-lexicon",
184
+ ]
185
+
186
+ [[package]]
187
+ name = "pyo3-ffi"
188
+ version = "0.28.3"
189
+ source = "registry+https://github.com/rust-lang/crates.io-index"
190
+ checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
191
+ dependencies = [
192
+ "libc",
193
+ "pyo3-build-config",
194
+ ]
195
+
196
+ [[package]]
197
+ name = "pyo3-macros"
198
+ version = "0.28.3"
199
+ source = "registry+https://github.com/rust-lang/crates.io-index"
200
+ checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
201
+ dependencies = [
202
+ "proc-macro2",
203
+ "pyo3-macros-backend",
204
+ "quote",
205
+ "syn",
206
+ ]
207
+
208
+ [[package]]
209
+ name = "pyo3-macros-backend"
210
+ version = "0.28.3"
211
+ source = "registry+https://github.com/rust-lang/crates.io-index"
212
+ checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
213
+ dependencies = [
214
+ "heck",
215
+ "proc-macro2",
216
+ "pyo3-build-config",
217
+ "quote",
218
+ "syn",
219
+ ]
220
+
221
+ [[package]]
222
+ name = "quote"
223
+ version = "1.0.45"
224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
225
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
226
+ dependencies = [
227
+ "proc-macro2",
228
+ ]
229
+
230
+ [[package]]
231
+ name = "rawpointer"
232
+ version = "0.2.1"
233
+ source = "registry+https://github.com/rust-lang/crates.io-index"
234
+ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
235
+
236
+ [[package]]
237
+ name = "rayon"
238
+ version = "1.12.0"
239
+ source = "registry+https://github.com/rust-lang/crates.io-index"
240
+ checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
241
+ dependencies = [
242
+ "either",
243
+ "rayon-core",
244
+ ]
245
+
246
+ [[package]]
247
+ name = "rayon-core"
248
+ version = "1.13.0"
249
+ source = "registry+https://github.com/rust-lang/crates.io-index"
250
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
251
+ dependencies = [
252
+ "crossbeam-deque",
253
+ "crossbeam-utils",
254
+ ]
255
+
256
+ [[package]]
257
+ name = "rustc-hash"
258
+ version = "2.1.2"
259
+ source = "registry+https://github.com/rust-lang/crates.io-index"
260
+ checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
261
+
262
+ [[package]]
263
+ name = "syn"
264
+ version = "2.0.117"
265
+ source = "registry+https://github.com/rust-lang/crates.io-index"
266
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
267
+ dependencies = [
268
+ "proc-macro2",
269
+ "quote",
270
+ "unicode-ident",
271
+ ]
272
+
273
+ [[package]]
274
+ name = "target-lexicon"
275
+ version = "0.13.5"
276
+ source = "registry+https://github.com/rust-lang/crates.io-index"
277
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
278
+
279
+ [[package]]
280
+ name = "unicode-ident"
281
+ version = "1.0.24"
282
+ source = "registry+https://github.com/rust-lang/crates.io-index"
283
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
@@ -0,0 +1,10 @@
1
+ [workspace]
2
+ members = ["crates/gpgraph-core"]
3
+ resolver = "2"
4
+
5
+ [workspace.package]
6
+ version = "1.0.0"
7
+ edition = "2021"
8
+ rust-version = "1.75"
9
+ license = "MIT"
10
+ authors = ["Luis Perez"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luis Perez
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,218 @@
1
+ Metadata-Version: 2.4
2
+ Name: gpgraph-v2
3
+ Version: 1.0.0
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Science/Research
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Rust
13
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
14
+ Requires-Dist: numpy>=2.0
15
+ Requires-Dist: networkx>=3.6
16
+ Requires-Dist: gpmap-v2>=1.0
17
+ Requires-Dist: tqdm>=4.67
18
+ Requires-Dist: typing-extensions>=4.12
19
+ Requires-Dist: matplotlib>=3.10 ; extra == 'plot'
20
+ Requires-Dist: streamlit>=1.56 ; extra == 'streamlit'
21
+ Requires-Dist: matplotlib>=3.10 ; extra == 'streamlit'
22
+ Provides-Extra: plot
23
+ Provides-Extra: streamlit
24
+ License-File: LICENSE
25
+ Summary: NetworkX-backed, Rust-accelerated graphs over genotype-phenotype maps.
26
+ Keywords: genotype,phenotype,epistasis,fitness-landscape,networkx,graph
27
+ Author-email: Luis Perez <lperezmo@users.noreply.github.com>
28
+ Requires-Python: >=3.11
29
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
30
+ Project-URL: Homepage, https://github.com/lperezmo/gpgraph-v2
31
+ Project-URL: Issues, https://github.com/lperezmo/gpgraph-v2/issues
32
+ Project-URL: Repository, https://github.com/lperezmo/gpgraph-v2
33
+
34
+ # gpgraph-v2
35
+
36
+ [![CI](https://github.com/lperezmo/gpgraph-v2/actions/workflows/ci.yml/badge.svg)](https://github.com/lperezmo/gpgraph-v2/actions/workflows/ci.yml)
37
+ [![PyPI](https://img.shields.io/pypi/v/gpgraph-v2.svg)](https://pypi.org/project/gpgraph-v2/)
38
+ [![Python](https://img.shields.io/pypi/pyversions/gpgraph-v2.svg)](https://pypi.org/project/gpgraph-v2/)
39
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
40
+ [![Open in Streamlit](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://gpgraph-v2.streamlit.app)
41
+
42
+ NetworkX-backed, Rust-accelerated graphs over genotype-phenotype maps.
43
+
44
+ gpgraph-v2 lifts a [gpmap-v2](https://github.com/lperezmo/gpmap-v2) `GenotypePhenotypeMap` into a
45
+ NetworkX `DiGraph`. Nodes carry the row attributes from the map; edges connect neighbors under
46
+ a chosen distance metric (Hamming or codon). Fixation models (SSWM, ratio, Moran, McCandlish)
47
+ populate an edge `prob` attribute for evolutionary trajectory analysis.
48
+
49
+ This is a clean-break rewrite of [harmslab/gpgraph](https://github.com/harmslab/gpgraph).
50
+ Hot paths live in Rust via PyO3 + rayon; fixation models stay in vectorized numpy.
51
+
52
+ ## Why v2
53
+
54
+ - **Fast.** Neighbor detection runs in Rust with rayon parallelism. Biallelic cutoff-1 and
55
+ cutoff-2 hit a bit-flip fast path: `O(N * L^cutoff)` instead of `O(N^2 * L)`.
56
+ - **Typed.** Full type hints, `mypy --strict` in CI.
57
+ - **Modern tooling.** `uv` + `maturin` + `pyproject.toml`. Releases via
58
+ `python-semantic-release`. OIDC-based PyPI publishing.
59
+ - **Consumes gpmap-v2.** Speaks the locked `SCHEMA.md` contract; reads `binary_packed`,
60
+ `n_mutations`, and `phenotypes` (not the removed v1 `fitnesses`).
61
+ - **No Cython, no `setup.py`, no `.c` blobs.**
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ pip install gpgraph-v2
67
+ ```
68
+
69
+ Or with uv:
70
+
71
+ ```bash
72
+ uv add gpgraph-v2
73
+ ```
74
+
75
+ Plotting support is optional. For matplotlib:
76
+
77
+ ```bash
78
+ pip install "gpgraph-v2[plot]"
79
+ ```
80
+
81
+ Python 3.11+. Prebuilt wheels ship for Linux (x86_64, aarch64), macOS (x86_64, aarch64),
82
+ and Windows (x64).
83
+
84
+ ## Quick start
85
+
86
+ ```python
87
+ from gpmap import GenotypePhenotypeMap
88
+ from gpgraph import GenotypePhenotypeGraph
89
+
90
+ gpm = GenotypePhenotypeMap(
91
+ wildtype="AAA",
92
+ genotypes=["AAA", "AAT", "ATA", "TAA", "ATT", "TAT", "TTA", "TTT"],
93
+ phenotypes=[0.1, 0.2, 0.2, 0.6, 0.4, 0.6, 1.0, 1.1],
94
+ stdeviations=[0.05] * 8,
95
+ )
96
+
97
+ G = GenotypePhenotypeGraph.from_gpm(gpm)
98
+ G.number_of_nodes() # 8
99
+ G.number_of_edges() # 24 directed (12 undirected pairs)
100
+
101
+ G.add_model(column="phenotypes", model="sswm")
102
+ G.edges[(0, 1)]["prob"] # SSWM fixation probability from AAA -> AAT
103
+ ```
104
+
105
+ Available fixation models: `"sswm"`, `"ratio"`, `"moran"`, `"mcclandish"`. Pass
106
+ `population_size=N` as a keyword for the last two. Pass any `f(fi, fj, **kw) -> float`
107
+ for a custom model.
108
+
109
+ ## Forward paths and flux
110
+
111
+ ```python
112
+ from gpgraph.paths import forward_paths_prob, paths_prob_to_edges_flux
113
+
114
+ G.add_model(column="phenotypes", model="sswm")
115
+ paths = forward_paths_prob(G, source=0, target=7)
116
+ flux = paths_prob_to_edges_flux(paths)
117
+ ```
118
+
119
+ ## Plotting
120
+
121
+ ```python
122
+ from gpgraph.pyplot import draw_gpgraph, draw_paths
123
+
124
+ fig, ax = draw_gpgraph(G) # Hamming-weight vertical layout
125
+ draw_paths(G, source=0, target=7) # paths flux overlay
126
+ ```
127
+
128
+ ## Benchmarks
129
+
130
+ Rust kernels vs a pure-Python pairwise reference. Windows 11, Ryzen, release build.
131
+ Run via `uv run pytest tests/benchmarks/ --benchmark-only`.
132
+
133
+ | problem | bit-flip (Rust) | pairwise (Rust) | pairwise (Python) |
134
+ |---|---|---|---|
135
+ | N=256, L=6, cutoff=1 | 77 us | 284 us | 29.3 ms |
136
+ | N=1024, L=8, cutoff=1| 167 us | 1.2 ms | (skipped, minutes)|
137
+
138
+ At L=6 the Rust bit-flip path is ~380x faster than a naive Python loop. Past
139
+ N = 1000 on biallelic data the bit-flip specialization pulls away from the
140
+ rayon-parallel pairwise version by another ~7x.
141
+
142
+ ## Dispatch policy (neighbor kernels)
143
+
144
+ `gpgraph.neighbors.get_neighbors` chooses the fastest available implementation:
145
+
146
+ | problem shape | kernel |
147
+ |---|---|
148
+ | user-supplied `f(g1, g2, cutoff=...) -> bool` | pure Python `O(N^2)` |
149
+ | Hamming, biallelic `binary_packed`, cutoff <= 2 | Rust bit-flip (`O(N * C(L, cutoff))`) |
150
+ | Hamming, biallelic `binary_packed`, larger cutoff | Rust rayon-parallel packed pairwise |
151
+ | Hamming, non-binary alphabet | Rust rayon-parallel string pairwise |
152
+ | codon | Rust rayon-parallel codon pairwise |
153
+
154
+ See [SCHEMA.md](SCHEMA.md) for the full node/edge contract.
155
+
156
+ ## Development
157
+
158
+ ```bash
159
+ git clone https://github.com/lperezmo/gpgraph-v2
160
+ cd gpgraph-v2
161
+ uv sync
162
+ uv run maturin develop --release
163
+ uv run pytest
164
+ uv run ruff check python/gpgraph tests
165
+ ```
166
+
167
+ After editing Rust:
168
+
169
+ ```bash
170
+ uv run maturin develop --release && uv run pytest
171
+ ```
172
+
173
+ ## Consuming from another local project
174
+
175
+ During co-development with gpmap-v2, point at the local source:
176
+
177
+ ```toml
178
+ [tool.uv.sources]
179
+ gpgraph-v2 = { path = "/absolute/path/to/gpgraph-v2", editable = true }
180
+
181
+ [project]
182
+ dependencies = ["gpgraph-v2"]
183
+ ```
184
+
185
+ Imports remain `from gpgraph import GenotypePhenotypeGraph`.
186
+
187
+ ## Migration from v1 (`harmslab/gpgraph`)
188
+
189
+ gpgraph-v2 is not wire-compatible with v1. Key differences:
190
+
191
+ - Distribution name is `gpgraph-v2` on PyPI; import path is still `gpgraph`.
192
+ - Construction: use `GenotypePhenotypeGraph.from_gpm(gpm, ...)`. The v1 two-step
193
+ `G = GenotypePhenotypeGraph(); G.add_gpm(gpm)` is removed.
194
+ - Reads gpmap-v2 columns: node attribute is `phenotypes`, not `fitnesses`.
195
+ - `__repr__` no longer renders a matplotlib figure as a side effect.
196
+ - Matplotlib is an optional extra (`gpgraph-v2[plot]`); the core install is headless.
197
+ - The `_nx_wrapper` introspection shim in `pyplot/primitives.py` is gone. NetworkX is
198
+ version-pinned to `>=3.6`.
199
+ - `gpgraph.neighbor_functions` is renamed `gpgraph.neighbors` and the pairwise
200
+ `get_neighbors` returns a NumPy `(E, 2) int64` array instead of a list of tuples.
201
+ - Python 3.11+.
202
+
203
+ ## Releases
204
+
205
+ Releases are driven by [`python-semantic-release`](https://python-semantic-release.readthedocs.io/)
206
+ on merge to `main`. Commits follow [Conventional Commits](https://www.conventionalcommits.org/):
207
+
208
+ - `fix: ...` -> patch
209
+ - `feat: ...` -> minor
210
+ - `feat!: ...` or a `BREAKING CHANGE:` footer -> major
211
+
212
+ `CHANGELOG.md`, version bumps, Git tags, GitHub Releases, wheel builds, and PyPI uploads
213
+ all happen automatically.
214
+
215
+ ## License
216
+
217
+ MIT. See [LICENSE](LICENSE).
218
+