IsoSpecPy 2.5.0__tar.gz → 2.6.1__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 (74) hide show
  1. {isospecpy-2.5.0 → isospecpy-2.6.1}/PKG-INFO +63 -9
  2. {isospecpy-2.5.0 → isospecpy-2.6.1}/README.md +61 -6
  3. isospecpy-2.6.1/VERSION +1 -0
  4. {isospecpy-2.5.0 → isospecpy-2.6.1}/pyproject.toml +1 -2
  5. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/aligned_ptr.h +59 -9
  6. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/allocator.h +8 -1
  7. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/composition.h +9 -0
  8. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/cwrapper.cpp +165 -65
  9. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/cwrapper.h +49 -2
  10. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/dirtyAllocator.h +2 -1
  11. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/fasta.cpp +28 -1
  12. isospecpy-2.6.1/src/IsoSpec++/fasta.h +120 -0
  13. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/fasta_mods.cpp +62 -30
  14. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/fasta_mods.h +7 -0
  15. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/fixedEnvelopes.cpp +610 -223
  16. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/fixedEnvelopes.h +38 -8
  17. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_dispatch.cpp +90 -32
  18. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernel_impl.h +17 -6
  19. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels.h +11 -2
  20. isospecpy-2.6.1/src/IsoSpec++/isoMath.cpp +342 -0
  21. isospecpy-2.6.1/src/IsoSpec++/isoMath.h +183 -0
  22. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/isoSpec++.cpp +192 -117
  23. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/isoSpec++.h +221 -34
  24. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/marginalTrek++.cpp +152 -49
  25. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/marginalTrek++.h +31 -11
  26. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/misc.cpp +2 -7
  27. isospecpy-2.6.1/src/IsoSpec++/operators.h +64 -0
  28. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/platform.h +14 -0
  29. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/pod_vector.h +88 -23
  30. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/unimod.cpp +24 -16
  31. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/unimod.h +4 -5
  32. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/unity-build.cpp +0 -1
  33. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/IsoSpecPy.py +137 -51
  34. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/isoFFI.py +36 -1
  35. isospecpy-2.5.0/VERSION +0 -1
  36. isospecpy-2.5.0/src/IsoSpec++/fasta.h +0 -70
  37. isospecpy-2.5.0/src/IsoSpec++/isoMath.cpp +0 -181
  38. isospecpy-2.5.0/src/IsoSpec++/isoMath.h +0 -121
  39. isospecpy-2.5.0/src/IsoSpec++/operators.cpp +0 -38
  40. isospecpy-2.5.0/src/IsoSpec++/operators.h +0 -150
  41. {isospecpy-2.5.0 → isospecpy-2.6.1}/LICENCE +0 -0
  42. {isospecpy-2.5.0 → isospecpy-2.6.1}/skbuild/CMakeLists.txt +0 -0
  43. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/IsaDispatch.cmake +0 -0
  44. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/allocator.cpp +0 -0
  45. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/btrd.h +0 -0
  46. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/composition.cpp +0 -0
  47. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/conf.h +0 -0
  48. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/dirtyAllocator.cpp +0 -0
  49. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/element_lookup.cpp +0 -0
  50. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/element_lookup.h +0 -0
  51. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/element_tables.cpp +0 -0
  52. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/element_tables.h +0 -0
  53. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels_avx.cpp +0 -0
  54. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels_baseline.cpp +0 -0
  55. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels_v3.cpp +0 -0
  56. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels_v4.cpp +0 -0
  57. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/iso_simd.h +0 -0
  58. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/misc.h +0 -0
  59. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/mman.cpp +0 -0
  60. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/mman.h +0 -0
  61. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/platform_incl.h +0 -0
  62. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/python-build.cpp +0 -0
  63. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/summator.h +0 -0
  64. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/unimod_support.h +0 -0
  65. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpec++/unimod_table_data.h +0 -0
  66. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/Advanced.py +0 -0
  67. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/Distributions.py +0 -0
  68. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/Formulas.py +0 -0
  69. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/IsoSpecPyOld.py +0 -0
  70. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/PeriodicTbl.py +0 -0
  71. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/__init__.py +0 -0
  72. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/__main__.py +0 -0
  73. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/approximations.py +0 -0
  74. {isospecpy-2.5.0 → isospecpy-2.6.1}/src/IsoSpecPy/confs_passthrough.py +0 -0
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IsoSpecPy
3
- Version: 2.5.0
3
+ Version: 2.6.1
4
4
  Summary: IsoSpecPy is a Python library for computing isotopic distributions of molecules.
5
5
  Keywords: isotopic distribution,mass spectrometry,chemistry,isotopic envelope,isotopologues
6
6
  Author: Michał Startek, Mateusz Łącki
7
7
  License-Expression: BSD-2-Clause
8
8
  License-File: LICENCE
9
9
  Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.9
11
10
  Classifier: Programming Language :: Python :: 3.10
12
11
  Classifier: Programming Language :: Python :: 3.11
13
12
  Classifier: Programming Language :: Python :: 3.12
@@ -21,7 +20,7 @@ Classifier: Development Status :: 5 - Production/Stable
21
20
  Classifier: Operating System :: OS Independent
22
21
  Project-URL: Homepage, https://github.com/MatteoLacki/IsoSpec
23
22
  Project-URL: Repository, https://github.com/MatteoLacki/IsoSpec
24
- Requires-Python: >=3.9
23
+ Requires-Python: >=3.10
25
24
  Requires-Dist: cffi
26
25
  Provides-Extra: testing
27
26
  Requires-Dist: pytest; extra == "testing"
@@ -32,7 +31,7 @@ Description-Content-Type: text/markdown
32
31
 
33
32
  IsoSpec is a fine-structure isotopic distribution calculator for chemical formulas — given a molecule, it computes the masses and probabilities of its isotopologues. It is fast enough to handle large molecules (proteins, oligonucleotides) where the full distribution has astronomically many configurations, and it returns provably-optimal subsets that cover a chosen fraction of the total probability mass.
34
33
 
35
- IsoSpec is primarily used as a library by mass spectrometry software. It is implemented in C++ (`src/IsoSpec++/`) and shipped with first-class bindings for Python (`IsoSpecPy`, on PyPI) and R (`IsoSpecR`, on CRAN).
34
+ IsoSpec is primarily used as a library by mass spectrometry software. It is implemented in C++ (`src/IsoSpec++/`) and shipped with first-class bindings for Python (`IsoSpecPy`, on PyPI), R (`IsoSpecR`, on CRAN) and Rust (`isospec`, the crate in `rust/`).
36
35
 
37
36
  ## Installation
38
37
 
@@ -42,7 +41,7 @@ IsoSpec is primarily used as a library by mass spectrometry software. It is impl
42
41
  pip install IsoSpecPy
43
42
  ```
44
43
 
45
- The wheel bundles the C++ library — no separate native install required. Compatible with CPython and PyPy on Linux, macOS, Windows, and Cygwin/MinGW. Python ≥ 3.9.
44
+ The wheel bundles the C++ library — no separate native install required. Compatible with CPython and PyPy on Linux, macOS, Windows, and Cygwin/MinGW. Python ≥ 3.10.
46
45
 
47
46
  To build from source: `pip install .` from a checkout. A C++20 compiler is required.
48
47
 
@@ -54,6 +53,21 @@ install.packages("IsoSpecR")
54
53
 
55
54
  From source: `cd src && R CMD build IsoSpecR && R CMD INSTALL IsoSpecR_*.tar.gz`.
56
55
 
56
+ ### Rust
57
+
58
+ ```bash
59
+ cargo add isospec
60
+ ```
61
+
62
+ or, in `Cargo.toml`:
63
+
64
+ ```toml
65
+ [dependencies]
66
+ isospec = "0.2"
67
+ ```
68
+
69
+ The crate ([crates.io](https://crates.io/crates/isospec), API documentation on [docs.rs](https://docs.rs/isospec)) carries the C++ sources and compiles them in its build script; a C++20 compiler is all that is needed. See `rust/README.md`.
70
+
57
71
  ### C++ library
58
72
 
59
73
  ```bash
@@ -160,6 +174,27 @@ clang++ -std=c++20 water.cpp src/IsoSpec++/unity-build.cpp -o water
160
174
 
161
175
  (`unity-build.cpp` is a single translation unit that `#include`s every source file — see `Examples/C++/COMPILING`.)
162
176
 
177
+ ### Rust
178
+
179
+ ```rust
180
+ use isospec::{Iso, PeptideOptions};
181
+
182
+ fn main() -> Result<(), isospec::Error> {
183
+ let iso = Iso::from_formula("H2O1")?;
184
+ for (mass, prob) in iso.envelope().total_prob(0.999)?.iter() {
185
+ println!("{mass}\t{prob}");
186
+ }
187
+
188
+ // A modified peptide; unlike Python's peptide_sequence=, the default
189
+ // here includes the terminal water.
190
+ let peptide = Iso::from_peptide("PEPTC[UNIMOD:4]DEK", &PeptideOptions::default())?;
191
+ println!("{}", peptide.monoisotopic_mass());
192
+ Ok(())
193
+ }
194
+ ```
195
+
196
+ See `rust/examples/` for binned spectra, generators with configurations, and labelled elements.
197
+
163
198
  ### R
164
199
 
165
200
  ```r
@@ -179,7 +214,19 @@ IsoSpecify(molecule = composition, stopCondition = 0.999)
179
214
 
180
215
  Like the Python `ParsePeptideSequence`, this returns the residue composition without a free peptide's terminal H2O -- add `c(H = 2, O = 1)` to it for the neutral molecule. A modification whose delta removes more atoms of an element than the bare sequence provides gives a negative count (`"G[UNIMOD:11]"` nets `H -1`, `S -1`); `IsoSpecify` rejects such a vector rather than quietly dropping those elements.
181
216
 
182
- See `Examples/R/` for radiolabelling, full-spectrum extraction, and Unimod-annotated sequences.
217
+ The rest of the API takes the same named vector, and treats an envelope as what `IsoSpecify` returns, a matrix with columns `mass` and `prob`:
218
+
219
+ ```r
220
+ insulin <- c(C = 254, H = 377, N = 65, O = 75, S = 6)
221
+ IsoProperties(insulin)$averageMass # also mono/lightest/heaviest/mode peaks, variance
222
+ IsoBinned(insulin, binWidth = 1) # unit-resolution distribution, computed directly
223
+ IsoStochastic(insulin, ionicCurrent = 1e4, seed = 1) # simulated, reproducible measurement
224
+ wassersteinDistance(IsoSpecify(insulin, 0.999), IsoSpecify(c(C = 254, H = 377, N = 65, O = 74, S = 7), 0.999))
225
+ ```
226
+
227
+ with `envelopeConvolve`, `envelopeLinearCombination`, `envelopeBin` and `envelopeResample` for envelope arithmetic, and `orientedWassersteinDistance`, `abyssalWassersteinDistance` and `wassersteinMatch` beside `wassersteinDistance`.
228
+
229
+ See `Examples/R/` for radiolabelling, full-spectrum extraction, Unimod-annotated sequences, and binned, simulated and compared envelopes.
183
230
 
184
231
  ## Advanced features
185
232
 
@@ -189,9 +236,11 @@ See `Examples/R/` for radiolabelling, full-spectrum extraction, and Unimod-annot
189
236
  - `IsoBinned` — histogram-style envelope at a chosen bin width.
190
237
  - **FASTA/peptide-sequence support** — build an `Iso` directly from an amino-acid sequence, with `[UNIMOD:<id>]` modification tags recognized inline; optionally include the N/C-terminal water.
191
238
  - **Custom isotopic tables** — override natural abundances per element (e.g. for radio- or stable-isotope labelling). See `Examples/*/radiolabelling.*`.
192
- - **Fixed-envelope arithmetic** — addition, normalization, convolution, Wasserstein distance.
239
+ - **Fixed-envelope arithmetic** — addition, normalization, convolution, binning, resampling, Wasserstein distances.
240
+ - **Reproducible sampling** — stochastic envelopes and resampling take a seed (`seed=` in Python and R, `seeded_rng()` in C++, `with_seed` in Rust); a seed gives the same counts on every platform and in every binding.
193
241
  - **Nominal-mass mode** — compute distributions over nucleon counts instead of real masses.
194
- - **Streaming generators (for performance)** — the tabulated functions above materialize the full envelope into arrays. For large molecules where that uses too much memory, or for pipelines that consume one configuration at a time (e.g. binning on the fly), use the generator classes instead: `IsoThresholdGenerator`, `IsoLayeredGenerator`, `IsoOrderedGenerator`, `IsoStochasticGenerator`. They yield one isotopologue per call to `advanceToNextConfiguration()` without ever storing the full set. `IsoOrderedGenerator` additionally streams in strict order of decreasing probability.
242
+ - **Thread safety** — the library never parallelises its own work, but independent molecules and envelopes may be used from any number of threads, and a shared molecule or envelope may be read from several at once.
243
+ - **Streaming generators (for performance)** — the tabulated functions above materialize the full envelope into arrays. For large molecules where that uses too much memory, or for pipelines that consume one configuration at a time (e.g. binning on the fly), use the generator classes instead: `IsoThresholdGenerator`, `IsoLayeredGenerator`, `IsoOrderedGenerator`, `IsoStochasticGenerator`. They yield one isotopologue per call to `advanceToNextConfiguration()` without ever storing the full set. `IsoOrderedGenerator` additionally streams in strict order of decreasing probability. Generators are in C++, Python and Rust; R has the materialized functions only.
195
244
 
196
245
  Algorithmic details are in the papers cited below; the Supporting Information of each is the better starting point for implementation specifics.
197
246
 
@@ -201,11 +250,16 @@ Algorithmic details are in the papers cited below; the Supporting Information of
201
250
  src/IsoSpec++/ C++ core library (the algorithms live here)
202
251
  src/IsoSpecPy/ Python binding, loaded via cffi
203
252
  src/IsoSpecR/ R binding, via Rcpp
253
+ rust/ Rust crate (Cargo.toml at the repository root), via cxx
204
254
  Examples/ Working examples in each language
205
- tests/ C, C++, and Python test suites
255
+ tests/ C, C++, and Python test suites (R's are in src/IsoSpecR/tests, Rust's in rust/tests)
206
256
  skbuild/ scikit-build-core entry point used when installing the Python wheel
207
257
  ```
208
258
 
259
+ ## Changes
260
+
261
+ `CHANGELOG` lists the major changes in each release (the Rust crate keeps its own, `rust/CHANGELOG.md`).
262
+
209
263
  ## Citation
210
264
 
211
265
  If IsoSpec is useful in your research, please cite:
@@ -2,7 +2,7 @@
2
2
 
3
3
  IsoSpec is a fine-structure isotopic distribution calculator for chemical formulas — given a molecule, it computes the masses and probabilities of its isotopologues. It is fast enough to handle large molecules (proteins, oligonucleotides) where the full distribution has astronomically many configurations, and it returns provably-optimal subsets that cover a chosen fraction of the total probability mass.
4
4
 
5
- IsoSpec is primarily used as a library by mass spectrometry software. It is implemented in C++ (`src/IsoSpec++/`) and shipped with first-class bindings for Python (`IsoSpecPy`, on PyPI) and R (`IsoSpecR`, on CRAN).
5
+ IsoSpec is primarily used as a library by mass spectrometry software. It is implemented in C++ (`src/IsoSpec++/`) and shipped with first-class bindings for Python (`IsoSpecPy`, on PyPI), R (`IsoSpecR`, on CRAN) and Rust (`isospec`, the crate in `rust/`).
6
6
 
7
7
  ## Installation
8
8
 
@@ -12,7 +12,7 @@ IsoSpec is primarily used as a library by mass spectrometry software. It is impl
12
12
  pip install IsoSpecPy
13
13
  ```
14
14
 
15
- The wheel bundles the C++ library — no separate native install required. Compatible with CPython and PyPy on Linux, macOS, Windows, and Cygwin/MinGW. Python ≥ 3.9.
15
+ The wheel bundles the C++ library — no separate native install required. Compatible with CPython and PyPy on Linux, macOS, Windows, and Cygwin/MinGW. Python ≥ 3.10.
16
16
 
17
17
  To build from source: `pip install .` from a checkout. A C++20 compiler is required.
18
18
 
@@ -24,6 +24,21 @@ install.packages("IsoSpecR")
24
24
 
25
25
  From source: `cd src && R CMD build IsoSpecR && R CMD INSTALL IsoSpecR_*.tar.gz`.
26
26
 
27
+ ### Rust
28
+
29
+ ```bash
30
+ cargo add isospec
31
+ ```
32
+
33
+ or, in `Cargo.toml`:
34
+
35
+ ```toml
36
+ [dependencies]
37
+ isospec = "0.2"
38
+ ```
39
+
40
+ The crate ([crates.io](https://crates.io/crates/isospec), API documentation on [docs.rs](https://docs.rs/isospec)) carries the C++ sources and compiles them in its build script; a C++20 compiler is all that is needed. See `rust/README.md`.
41
+
27
42
  ### C++ library
28
43
 
29
44
  ```bash
@@ -130,6 +145,27 @@ clang++ -std=c++20 water.cpp src/IsoSpec++/unity-build.cpp -o water
130
145
 
131
146
  (`unity-build.cpp` is a single translation unit that `#include`s every source file — see `Examples/C++/COMPILING`.)
132
147
 
148
+ ### Rust
149
+
150
+ ```rust
151
+ use isospec::{Iso, PeptideOptions};
152
+
153
+ fn main() -> Result<(), isospec::Error> {
154
+ let iso = Iso::from_formula("H2O1")?;
155
+ for (mass, prob) in iso.envelope().total_prob(0.999)?.iter() {
156
+ println!("{mass}\t{prob}");
157
+ }
158
+
159
+ // A modified peptide; unlike Python's peptide_sequence=, the default
160
+ // here includes the terminal water.
161
+ let peptide = Iso::from_peptide("PEPTC[UNIMOD:4]DEK", &PeptideOptions::default())?;
162
+ println!("{}", peptide.monoisotopic_mass());
163
+ Ok(())
164
+ }
165
+ ```
166
+
167
+ See `rust/examples/` for binned spectra, generators with configurations, and labelled elements.
168
+
133
169
  ### R
134
170
 
135
171
  ```r
@@ -149,7 +185,19 @@ IsoSpecify(molecule = composition, stopCondition = 0.999)
149
185
 
150
186
  Like the Python `ParsePeptideSequence`, this returns the residue composition without a free peptide's terminal H2O -- add `c(H = 2, O = 1)` to it for the neutral molecule. A modification whose delta removes more atoms of an element than the bare sequence provides gives a negative count (`"G[UNIMOD:11]"` nets `H -1`, `S -1`); `IsoSpecify` rejects such a vector rather than quietly dropping those elements.
151
187
 
152
- See `Examples/R/` for radiolabelling, full-spectrum extraction, and Unimod-annotated sequences.
188
+ The rest of the API takes the same named vector, and treats an envelope as what `IsoSpecify` returns, a matrix with columns `mass` and `prob`:
189
+
190
+ ```r
191
+ insulin <- c(C = 254, H = 377, N = 65, O = 75, S = 6)
192
+ IsoProperties(insulin)$averageMass # also mono/lightest/heaviest/mode peaks, variance
193
+ IsoBinned(insulin, binWidth = 1) # unit-resolution distribution, computed directly
194
+ IsoStochastic(insulin, ionicCurrent = 1e4, seed = 1) # simulated, reproducible measurement
195
+ wassersteinDistance(IsoSpecify(insulin, 0.999), IsoSpecify(c(C = 254, H = 377, N = 65, O = 74, S = 7), 0.999))
196
+ ```
197
+
198
+ with `envelopeConvolve`, `envelopeLinearCombination`, `envelopeBin` and `envelopeResample` for envelope arithmetic, and `orientedWassersteinDistance`, `abyssalWassersteinDistance` and `wassersteinMatch` beside `wassersteinDistance`.
199
+
200
+ See `Examples/R/` for radiolabelling, full-spectrum extraction, Unimod-annotated sequences, and binned, simulated and compared envelopes.
153
201
 
154
202
  ## Advanced features
155
203
 
@@ -159,9 +207,11 @@ See `Examples/R/` for radiolabelling, full-spectrum extraction, and Unimod-annot
159
207
  - `IsoBinned` — histogram-style envelope at a chosen bin width.
160
208
  - **FASTA/peptide-sequence support** — build an `Iso` directly from an amino-acid sequence, with `[UNIMOD:<id>]` modification tags recognized inline; optionally include the N/C-terminal water.
161
209
  - **Custom isotopic tables** — override natural abundances per element (e.g. for radio- or stable-isotope labelling). See `Examples/*/radiolabelling.*`.
162
- - **Fixed-envelope arithmetic** — addition, normalization, convolution, Wasserstein distance.
210
+ - **Fixed-envelope arithmetic** — addition, normalization, convolution, binning, resampling, Wasserstein distances.
211
+ - **Reproducible sampling** — stochastic envelopes and resampling take a seed (`seed=` in Python and R, `seeded_rng()` in C++, `with_seed` in Rust); a seed gives the same counts on every platform and in every binding.
163
212
  - **Nominal-mass mode** — compute distributions over nucleon counts instead of real masses.
164
- - **Streaming generators (for performance)** — the tabulated functions above materialize the full envelope into arrays. For large molecules where that uses too much memory, or for pipelines that consume one configuration at a time (e.g. binning on the fly), use the generator classes instead: `IsoThresholdGenerator`, `IsoLayeredGenerator`, `IsoOrderedGenerator`, `IsoStochasticGenerator`. They yield one isotopologue per call to `advanceToNextConfiguration()` without ever storing the full set. `IsoOrderedGenerator` additionally streams in strict order of decreasing probability.
213
+ - **Thread safety** — the library never parallelises its own work, but independent molecules and envelopes may be used from any number of threads, and a shared molecule or envelope may be read from several at once.
214
+ - **Streaming generators (for performance)** — the tabulated functions above materialize the full envelope into arrays. For large molecules where that uses too much memory, or for pipelines that consume one configuration at a time (e.g. binning on the fly), use the generator classes instead: `IsoThresholdGenerator`, `IsoLayeredGenerator`, `IsoOrderedGenerator`, `IsoStochasticGenerator`. They yield one isotopologue per call to `advanceToNextConfiguration()` without ever storing the full set. `IsoOrderedGenerator` additionally streams in strict order of decreasing probability. Generators are in C++, Python and Rust; R has the materialized functions only.
165
215
 
166
216
  Algorithmic details are in the papers cited below; the Supporting Information of each is the better starting point for implementation specifics.
167
217
 
@@ -171,11 +221,16 @@ Algorithmic details are in the papers cited below; the Supporting Information of
171
221
  src/IsoSpec++/ C++ core library (the algorithms live here)
172
222
  src/IsoSpecPy/ Python binding, loaded via cffi
173
223
  src/IsoSpecR/ R binding, via Rcpp
224
+ rust/ Rust crate (Cargo.toml at the repository root), via cxx
174
225
  Examples/ Working examples in each language
175
- tests/ C, C++, and Python test suites
226
+ tests/ C, C++, and Python test suites (R's are in src/IsoSpecR/tests, Rust's in rust/tests)
176
227
  skbuild/ scikit-build-core entry point used when installing the Python wheel
177
228
  ```
178
229
 
230
+ ## Changes
231
+
232
+ `CHANGELOG` lists the major changes in each release (the Rust crate keeps its own, `rust/CHANGELOG.md`).
233
+
179
234
  ## Citation
180
235
 
181
236
  If IsoSpec is useful in your research, please cite:
@@ -0,0 +1 @@
1
+ 2.6.1
@@ -32,10 +32,9 @@ license = "BSD-2-Clause"
32
32
  license-files = ["LICENCE"]
33
33
  authors = [{name = "Michał Startek"}, {name = "Mateusz Łącki"}]
34
34
  readme = "README.md"
35
- requires-python = ">=3.9"
35
+ requires-python = ">=3.10"
36
36
  classifiers = [
37
37
  "Programming Language :: Python :: 3",
38
- "Programming Language :: Python :: 3.9",
39
38
  "Programming Language :: Python :: 3.10",
40
39
  "Programming Language :: Python :: 3.11",
41
40
  "Programming Language :: Python :: 3.12",
@@ -36,7 +36,9 @@
36
36
  // elsewhere for plain huge-page mmap): that layer has no remap concept at
37
37
  // all on Windows and no equivalent of mremap on Darwin, so each platform's
38
38
  // real growth primitive is used directly instead:
39
- // - Linux / other real-mman UNIX: mmap + mremap(MREMAP_MAYMOVE) + munmap.
39
+ // - Linux (any libc with mremap): mmap + mremap(MREMAP_MAYMOVE) + munmap.
40
+ // - Other UNIX (FreeBSD, ...: no mremap): growth maps a fresh region and
41
+ // copies, shrink unmaps the tail in place.
40
42
  // - Apple: mmap/munmap for create/destroy (Darwin has no mremap); growth
41
43
  // re-wires pages into a fresh mapping via mach_vm_remap instead of
42
44
  // copying, shrink just munmaps the tail in place.
@@ -57,25 +59,41 @@
57
59
 
58
60
  // The small-allocation backend needs an aligned allocator that can also be
59
61
  // deallocated with a plain std::free() (release() promises exactly that).
60
- // glibc/libc++'s std::aligned_alloc satisfies both at once. MSVC's CRT has
62
+ // glibc/libc++'s std::aligned_alloc satisfies both at once. Windows' CRT
63
+ // (MSVC's, and the msvcrt/UCRT that MinGW, i.e. R's Rtools, links against) has
61
64
  // no C11 aligned_alloc at all, only _aligned_malloc/_aligned_free -- and
62
65
  // memory from those two is NOT free()-compatible (the CRT stores its own
63
66
  // offset bookkeeping ahead of the returned pointer, which plain free() does
64
- // not know how to unwind). So on MSVC the small backend uses
67
+ // not know how to unwind). So on Windows the small backend uses
65
68
  // _aligned_malloc/_aligned_free for its own storage (still genuinely
66
69
  // Alignment-aligned, as marginalTrek++.h's use of this class relies on),
67
70
  // and release() falls back to materialising a fresh plain-malloc()'d copy
68
71
  // to keep its free()-compatibility promise -- the same thing it already
69
72
  // does to escape a VM-backed region.
70
- #if defined(_MSC_VER)
73
+ #if defined(_WIN32)
71
74
  #define ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE 0
72
75
  #else
73
76
  #define ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE 1
74
77
  #endif
75
78
 
76
79
  #if ISOSPEC_WE_ARE_ON_WINDOWS
80
+ // Without NOMINMAX, <windows.h> defines min and max as macros, which break
81
+ // every later std::min(...) and numeric_limits<T>::max() in the including
82
+ // translation unit. Defined only around this include, so that the macro
83
+ // itself does not leak into the caller's code (though a later <windows.h>
84
+ // there is then a no-op, without the macros); a caller who included
85
+ // <windows.h> first, macros and all, is covered by the parenthesised
86
+ // (std::min)(...) calls in the public headers.
87
+ #ifndef NOMINMAX
88
+ #define NOMINMAX
89
+ #define ISOSPEC_UNDEF_NOMINMAX
90
+ #endif
77
91
  #include <windows.h>
78
- #if defined(_MSC_VER)
92
+ #ifdef ISOSPEC_UNDEF_NOMINMAX
93
+ #undef NOMINMAX
94
+ #undef ISOSPEC_UNDEF_NOMINMAX
95
+ #endif
96
+ #if defined(_WIN32)
79
97
  #include <malloc.h> // _aligned_malloc / _aligned_free
80
98
  #endif
81
99
  #elif ISOSPEC_WE_ARE_ON_UNIX_YAY
@@ -303,7 +321,26 @@ inline void vm_resize(vm_region& r, std::size_t new_bytes)
303
321
  r.size = new_size;
304
322
  }
305
323
 
306
- #else // Linux / other real-mman UNIX with an actual mremap()
324
+ #else // Other UNIX: Linux with mremap(), the rest (FreeBSD, ...) without
325
+
326
+ // ThreadSanitizer intercepts mmap() and munmap() but not mremap(), so a region
327
+ // that mremap() moves away from is freed behind its back and keeps its old
328
+ // shadow state. When another thread's mremap() later lands on those
329
+ // addresses, TSan reports its writes as racing with the previous owner's --
330
+ // pure address reuse, reproduced with two threads and nothing else. Under
331
+ // TSan, growth therefore goes through mmap + copy + munmap, which it does see.
332
+ #if defined(__SANITIZE_THREAD__)
333
+ #define ISOSPEC_ALIGNED_PTR_UNDER_TSAN 1
334
+ #elif defined(__has_feature)
335
+ #if __has_feature(thread_sanitizer)
336
+ #define ISOSPEC_ALIGNED_PTR_UNDER_TSAN 1
337
+ #endif
338
+ #endif
339
+
340
+ // mremap() is Linux's alone; MREMAP_MAYMOVE, from <sys/mman.h>, is the test.
341
+ #if defined(MREMAP_MAYMOVE) && !defined(ISOSPEC_ALIGNED_PTR_UNDER_TSAN)
342
+ #define ISOSPEC_ALIGNED_PTR_USE_MREMAP 1
343
+ #endif
307
344
 
308
345
  inline void vm_resize(vm_region& r, std::size_t new_bytes)
309
346
  {
@@ -311,11 +348,24 @@ inline void vm_resize(vm_region& r, std::size_t new_bytes)
311
348
  if (new_size == r.size)
312
349
  return;
313
350
 
351
+ #if !defined(ISOSPEC_ALIGNED_PTR_USE_MREMAP)
352
+ if (new_size < r.size) {
353
+ // Shrink in place: base address is untouched, just drop the tail.
354
+ ::munmap(static_cast<char*>(r.base) + new_size, r.size - new_size);
355
+ r.size = new_size;
356
+ return;
357
+ }
358
+ vm_region moved = vm_create(new_bytes);
359
+ std::memcpy(moved.base, r.base, (std::min)(r.size, moved.size));
360
+ vm_destroy(r);
361
+ r = moved;
362
+ #else
314
363
  void* p = ::mremap(r.base, r.size, new_size, MREMAP_MAYMOVE);
315
364
  if (p == MAP_FAILED)
316
365
  throw std::bad_alloc();
317
366
  r.base = p;
318
367
  r.size = new_size;
368
+ #endif
319
369
  }
320
370
 
321
371
  #endif // __APPLE__
@@ -549,7 +599,7 @@ class aligned_unique_ptr {
549
599
  // still has to copy -- there is nothing yet to remap.
550
600
  aligned_ptr_detail::vm_region r = aligned_ptr_detail::vm_create(new_bytes);
551
601
  if (ptr_) {
552
- std::memcpy(r.base, ptr_, std::min(capacity_bytes_, new_bytes));
602
+ std::memcpy(r.base, ptr_, (std::min)(capacity_bytes_, new_bytes));
553
603
  free_small(ptr_);
554
604
  }
555
605
  region_ = r;
@@ -562,7 +612,7 @@ class aligned_unique_ptr {
562
612
 
563
613
  T* new_ptr = allocate_small(n);
564
614
  if (ptr_) {
565
- std::memcpy(new_ptr, ptr_, std::min(capacity_bytes_, new_bytes));
615
+ std::memcpy(new_ptr, ptr_, (std::min)(capacity_bytes_, new_bytes));
566
616
  free_small(ptr_);
567
617
  }
568
618
  ptr_ = new_ptr;
@@ -573,7 +623,7 @@ class aligned_unique_ptr {
573
623
  // bare std::free() / C free(), regardless of which backend produced it.
574
624
  // Whenever the current backend isn't already free()-compatible on its
575
625
  // own -- a VM-backed region always needs this, and so does the small
576
- // backend on MSVC (see ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE
626
+ // backend on Windows (see ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE
577
627
  // above) -- this materialises a fresh plain-malloc()'d copy instead.
578
628
  // That copy is the whole cost of calling this in those cases;
579
629
  // release_with_deleter() below avoids it when the caller can accept a
@@ -23,6 +23,13 @@
23
23
  namespace IsoSpec
24
24
  {
25
25
 
26
+ // Configurations per allocator slab. Small on purpose: with glibc, a slab of
27
+ // up to 1032 bytes (4 isotopes x 64 ints = 1024) is recycled through the
28
+ // per-thread tcache when the next molecule is built, so many small molecules
29
+ // in a row run 16-20% faster than with 1000 (2000 tryptic peptides, threshold
30
+ // generator, x86-64-v3 and v4). Single large calculations do not notice.
31
+ constexpr int default_tab_size = 64;
32
+
26
33
  template <typename T> inline void copyConf(
27
34
  const T* source, T* destination,
28
35
  int dim
@@ -39,7 +46,7 @@ template <typename T> class Allocator
39
46
  pod_vector<T*> prevTabs;
40
47
 
41
48
  public:
42
- explicit Allocator(const int dim, const int tabSize = 10000);
49
+ explicit Allocator(const int dim, const int tabSize = default_tab_size);
43
50
  ~Allocator();
44
51
 
45
52
  Allocator(const Allocator& other) = delete;
@@ -27,6 +27,7 @@
27
27
 
28
28
  #pragma once
29
29
 
30
+ #include <string_view>
30
31
  #include <vector>
31
32
 
32
33
  #include "isoSpec++.h"
@@ -87,6 +88,14 @@ struct ExpandedComposition {
87
88
  }
88
89
  };
89
90
 
91
+ //! A formula string read as a composition: each element once, in the order
92
+ //! written, counts signed ("H-2O-1" is accepted -- a composition may be a
93
+ //! delta; build_iso_from_composition rejects negatives when one becomes a
94
+ //! molecule). Throws std::invalid_argument for a malformed formula, an unknown
95
+ //! element or one written twice, std::out_of_range for a count past int. The
96
+ //! same tokenizer and checks as parse_formula, which goes through it.
97
+ ElementComposition parse_formula_composition(std::string_view formula);
98
+
90
99
  //! Resolves each element of `composition` to its isotopes' masses and
91
100
  //! probabilities from element_tables.h, filling a caller-owned `out`
92
101
  //! (cleared first).