IsoSpecPy 2.3.5__tar.gz → 2.5.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 (76) hide show
  1. isospecpy-2.5.0/PKG-INFO +220 -0
  2. isospecpy-2.5.0/README.md +190 -0
  3. isospecpy-2.5.0/VERSION +1 -0
  4. isospecpy-2.5.0/pyproject.toml +94 -0
  5. isospecpy-2.5.0/skbuild/CMakeLists.txt +71 -0
  6. isospecpy-2.5.0/src/IsoSpec++/IsaDispatch.cmake +78 -0
  7. isospecpy-2.5.0/src/IsoSpec++/aligned_ptr.h +638 -0
  8. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/btrd.h +4 -4
  9. isospecpy-2.5.0/src/IsoSpec++/composition.cpp +85 -0
  10. isospecpy-2.5.0/src/IsoSpec++/composition.h +126 -0
  11. isospecpy-2.5.0/src/IsoSpec++/cwrapper.cpp +739 -0
  12. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/cwrapper.h +86 -1
  13. isospecpy-2.5.0/src/IsoSpec++/element_lookup.cpp +100 -0
  14. isospecpy-2.5.0/src/IsoSpec++/element_lookup.h +36 -0
  15. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/element_tables.cpp +10 -0
  16. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/element_tables.h +1 -1
  17. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/fasta.h +5 -2
  18. isospecpy-2.5.0/src/IsoSpec++/fasta_mods.cpp +244 -0
  19. isospecpy-2.5.0/src/IsoSpec++/fasta_mods.h +70 -0
  20. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/fixedEnvelopes.cpp +487 -162
  21. isospecpy-2.5.0/src/IsoSpec++/fixedEnvelopes.h +418 -0
  22. isospecpy-2.5.0/src/IsoSpec++/isa_dispatch.cpp +294 -0
  23. isospecpy-2.5.0/src/IsoSpec++/isa_kernel_impl.h +192 -0
  24. isospecpy-2.5.0/src/IsoSpec++/isa_kernels.h +172 -0
  25. isospecpy-2.5.0/src/IsoSpec++/isa_kernels_avx.cpp +57 -0
  26. isospecpy-2.5.0/src/IsoSpec++/isa_kernels_baseline.cpp +27 -0
  27. isospecpy-2.5.0/src/IsoSpec++/isa_kernels_v3.cpp +45 -0
  28. isospecpy-2.5.0/src/IsoSpec++/isa_kernels_v4.cpp +46 -0
  29. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/isoMath.cpp +20 -1
  30. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/isoMath.h +39 -5
  31. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/isoSpec++.cpp +154 -57
  32. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/isoSpec++.h +79 -2
  33. isospecpy-2.5.0/src/IsoSpec++/iso_simd.h +48 -0
  34. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/marginalTrek++.cpp +46 -33
  35. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/marginalTrek++.h +32 -19
  36. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/misc.cpp +1 -1
  37. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/misc.h +10 -3
  38. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/platform.h +69 -0
  39. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/pod_vector.h +8 -7
  40. isospecpy-2.5.0/src/IsoSpec++/python-build.cpp +37 -0
  41. isospecpy-2.5.0/src/IsoSpec++/unimod.cpp +171 -0
  42. isospecpy-2.5.0/src/IsoSpec++/unimod.h +80 -0
  43. isospecpy-2.5.0/src/IsoSpec++/unimod_support.h +2169 -0
  44. isospecpy-2.5.0/src/IsoSpec++/unimod_table_data.h +1027 -0
  45. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/unity-build.cpp +11 -0
  46. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/IsoSpecPy.py +263 -38
  47. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/PeriodicTbl.py +17 -0
  48. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/approximations.py +2 -2
  49. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/isoFFI.py +51 -15
  50. isospecpy-2.3.5/PKG-INFO +0 -43
  51. isospecpy-2.3.5/README.md +0 -21
  52. isospecpy-2.3.5/pyproject.toml +0 -58
  53. isospecpy-2.3.5/skbuild/CMakeLists.txt +0 -18
  54. isospecpy-2.3.5/src/IsoSpec++/cwrapper.cpp +0 -489
  55. isospecpy-2.3.5/src/IsoSpec++/fixedEnvelopes.h +0 -243
  56. isospecpy-2.3.5/src/IsoSpec++/python-build.cpp +0 -23
  57. {isospecpy-2.3.5 → isospecpy-2.5.0}/LICENCE +0 -0
  58. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/allocator.cpp +0 -0
  59. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/allocator.h +0 -0
  60. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/conf.h +0 -0
  61. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/dirtyAllocator.cpp +0 -0
  62. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/dirtyAllocator.h +0 -0
  63. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/fasta.cpp +0 -0
  64. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/mman.cpp +0 -0
  65. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/mman.h +0 -0
  66. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/operators.cpp +0 -0
  67. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/operators.h +0 -0
  68. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/platform_incl.h +0 -0
  69. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/summator.h +0 -0
  70. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/Advanced.py +0 -0
  71. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/Distributions.py +0 -0
  72. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/Formulas.py +0 -0
  73. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/IsoSpecPyOld.py +0 -0
  74. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/__init__.py +0 -0
  75. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/__main__.py +0 -0
  76. {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/confs_passthrough.py +0 -0
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: IsoSpecPy
3
+ Version: 2.5.0
4
+ Summary: IsoSpecPy is a Python library for computing isotopic distributions of molecules.
5
+ Keywords: isotopic distribution,mass spectrometry,chemistry,isotopic envelope,isotopologues
6
+ Author: Michał Startek, Mateusz Łącki
7
+ License-Expression: BSD-2-Clause
8
+ License-File: LICENCE
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Programming Language :: Python :: 3.15
17
+ Classifier: Programming Language :: Python :: Implementation :: CPython
18
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Development Status :: 5 - Production/Stable
21
+ Classifier: Operating System :: OS Independent
22
+ Project-URL: Homepage, https://github.com/MatteoLacki/IsoSpec
23
+ Project-URL: Repository, https://github.com/MatteoLacki/IsoSpec
24
+ Requires-Python: >=3.9
25
+ Requires-Dist: cffi
26
+ Provides-Extra: testing
27
+ Requires-Dist: pytest; extra == "testing"
28
+ Requires-Dist: OldIsoSpecPy; extra == "testing"
29
+ Description-Content-Type: text/markdown
30
+
31
+ # IsoSpec
32
+
33
+ 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
+
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).
36
+
37
+ ## Installation
38
+
39
+ ### Python
40
+
41
+ ```bash
42
+ pip install IsoSpecPy
43
+ ```
44
+
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.
46
+
47
+ To build from source: `pip install .` from a checkout. A C++20 compiler is required.
48
+
49
+ ### R
50
+
51
+ ```r
52
+ install.packages("IsoSpecR")
53
+ ```
54
+
55
+ From source: `cd src && R CMD build IsoSpecR && R CMD INSTALL IsoSpecR_*.tar.gz`.
56
+
57
+ ### C++ library
58
+
59
+ ```bash
60
+ cd src/IsoSpec++
61
+ make # produces libIsoSpec++.so
62
+ ```
63
+
64
+ Or via CMake from the repo root:
65
+
66
+ ```bash
67
+ cmake -B build -DCMAKE_BUILD_TYPE=Release
68
+ cmake --build build
69
+ sudo cmake --install build
70
+ ```
71
+
72
+ Requires a C++20 compiler. CMake produces both shared and static libraries and installs headers under `${CMAKE_INSTALL_INCLUDEDIR}/IsoSpec++`.
73
+
74
+ ## Quick start
75
+
76
+ The most common usage is `IsoTotalProb`: ask for the smallest set of isotopologues whose summed probability covers a chosen fraction of the spectrum (typically 0.99–0.9999). The result is a materialized envelope — arrays of masses and probabilities you can read directly.
77
+
78
+ ### Python
79
+
80
+ ```python
81
+ import IsoSpecPy
82
+
83
+ # Isotopologues covering at least 99.9% of the probability mass of water.
84
+ iso = IsoSpecPy.IsoTotalProb(formula="H2O1", prob_to_cover=0.999)
85
+
86
+ for mass, prob in zip(iso.masses, iso.probs):
87
+ print(mass, prob)
88
+
89
+ # From an amino-acid sequence:
90
+ iso = IsoSpecPy.IsoTotalProb(peptide_sequence="AAAPPGQAAC", prob_to_cover=0.999)
91
+ print(list(zip(iso.masses, iso.probs)))
92
+
93
+ # ...optionally carrying [UNIMOD:<id>] modification tags (the notation this
94
+ # monorepo's SAGE search-engine fork writes: [UNIMOD:<id>]-SEQUENCE prefix
95
+ # for an N-terminal mod, X[UNIMOD:<id>] inline for an internal one,
96
+ # SEQUENCE-[UNIMOD:<id>] suffix for a C-terminal one):
97
+ iso = IsoSpecPy.IsoTotalProb(peptide_sequence="PEPTC[UNIMOD:4]DEK", prob_to_cover=0.999)
98
+ print(list(zip(iso.masses, iso.probs)))
99
+
100
+ # Mind the water: peptide_sequence= yields the *residue* composition, without
101
+ # the terminal H2O of a free peptide, so the masses above are 18.0106 Da
102
+ # lighter than the neutral molecule. Add it explicitly for the real peptide
103
+ # (the C++ Iso::FromFASTAWithMods below adds it by default -- the two APIs
104
+ # differ here, and this is the older Python behaviour kept for compatibility):
105
+ iso = IsoSpecPy.IsoTotalProb(peptide_sequence="PEPTC[UNIMOD:4]DEK", formula="H2O1",
106
+ prob_to_cover=0.999)
107
+
108
+ # Or get just the composition (element -> atom count), no envelope:
109
+ composition = IsoSpecPy.ParsePeptideSequence("PEPTC[UNIMOD:4]DEK")
110
+ print(dict(composition)) # {'H': 60, 'C': 39, 'N': 10, 'O': 16, 'S': 1}
111
+ ```
112
+
113
+ Resolves against a table of ~980 Unimod entries embedded in the library (isotope-labelled and glycan/derivatization "brick" modifications are excluded -- they aren't representable as a pure elemental delta). Pass `unimod_db_path="/path/to/table.csv"` to either function to resolve against a different table instead (same `id,name,mono_mass,composition` CSV shape as the embedded default).
114
+
115
+ See `Examples/Python/` for radiolabelling, custom elements, binned spectra, and FASTA/Unimod modifications.
116
+
117
+ ### C++
118
+
119
+ ```cpp
120
+ #include "IsoSpec++/isoSpec++.h"
121
+ #include "IsoSpec++/fixedEnvelopes.h"
122
+
123
+ using namespace IsoSpec;
124
+
125
+ int main() {
126
+ FixedEnvelope iso = FixedEnvelope::FromTotalProb("H2O1", 0.999, true, true);
127
+
128
+ for (size_t i = 0; i < iso.confs_no(); ++i) {
129
+ std::cout << iso.mass(i) << '\t' << iso.prob(i) << '\n';
130
+ }
131
+ }
132
+ ```
133
+
134
+ From a peptide sequence, optionally carrying `[UNIMOD:<id>]` modification tags (`fasta_mods.h`; same notation as the Python/R examples above):
135
+
136
+ ```cpp
137
+ #include "IsoSpec++/isoSpec++.h"
138
+ #include "IsoSpec++/fasta_mods.h"
139
+ #include "IsoSpec++/fixedEnvelopes.h"
140
+
141
+ using namespace IsoSpec;
142
+
143
+ int main() {
144
+ FixedEnvelope iso = FixedEnvelope::FromTotalProb(
145
+ Iso::FromFASTAWithMods("PEPTC[UNIMOD:4]DEK"), 0.999, true, true);
146
+
147
+ for (size_t i = 0; i < iso.confs_no(); ++i) {
148
+ std::cout << iso.mass(i) << '\t' << iso.prob(i) << '\n';
149
+ }
150
+ }
151
+ ```
152
+
153
+ `Iso::FromFASTAWithMods` takes an optional trailing `unimod_db_path` argument to resolve against a different Unimod table instead of the one embedded at compile time. For just the composition, no envelope, call `parse_fasta_with_mods`/`parse_fasta_with_mods_full` directly (or the allocation-free `_into` forms, for a hot loop over many sequences) — see `fasta_mods.h`.
154
+
155
+ Quickest way to build:
156
+
157
+ ```bash
158
+ clang++ -std=c++20 water.cpp src/IsoSpec++/unity-build.cpp -o water
159
+ ```
160
+
161
+ (`unity-build.cpp` is a single translation unit that `#include`s every source file — see `Examples/C++/COMPILING`.)
162
+
163
+ ### R
164
+
165
+ ```r
166
+ library(IsoSpecR)
167
+ water <- c(H = 2, O = 1)
168
+ IsoSpecify(molecule = water, stopCondition = 0.999)
169
+ ```
170
+
171
+ `IsoSpecify` only ever takes a raw named-integer-vector `molecule` -- it has no sequence-string parameter -- so a `[UNIMOD:<id>]`-annotated peptide sequence takes two calls instead of one: `RParsePeptideSequence()` to resolve the sequence into a composition, then `IsoSpecify()` as above.
172
+
173
+ ```r
174
+ composition <- RParsePeptideSequence("PEPTC[UNIMOD:4]DEK")
175
+ print(composition) # H C N O S
176
+ # 60 39 10 16 1
177
+ IsoSpecify(molecule = composition, stopCondition = 0.999)
178
+ ```
179
+
180
+ 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
+
182
+ See `Examples/R/` for radiolabelling, full-spectrum extraction, and Unimod-annotated sequences.
183
+
184
+ ## Advanced features
185
+
186
+ - **Alternative algorithms** — for cases where `IsoTotalProb` isn't the right shape:
187
+ - `IsoThreshold` — all isotopologues with probability above a fixed threshold.
188
+ - `IsoStochastic` — simulate a measured spectrum by sampling integer ion counts.
189
+ - `IsoBinned` — histogram-style envelope at a chosen bin width.
190
+ - **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
+ - **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.
193
+ - **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.
195
+
196
+ Algorithmic details are in the papers cited below; the Supporting Information of each is the better starting point for implementation specifics.
197
+
198
+ ## Repository layout
199
+
200
+ ```
201
+ src/IsoSpec++/ C++ core library (the algorithms live here)
202
+ src/IsoSpecPy/ Python binding, loaded via cffi
203
+ src/IsoSpecR/ R binding, via Rcpp
204
+ Examples/ Working examples in each language
205
+ tests/ C, C++, and Python test suites
206
+ skbuild/ scikit-build-core entry point used when installing the Python wheel
207
+ ```
208
+
209
+ ## Citation
210
+
211
+ If IsoSpec is useful in your research, please cite:
212
+
213
+ - Łącki, M. K.; Valkenborg, D.; Startek, M. P. *IsoSpec2: Ultrafast Fine Structure Calculator.* Analytical Chemistry **2020**, *92* (14), 9472–9475. <https://doi.org/10.1021/acs.analchem.0c00959>
214
+ - Łącki, M. K.; Startek, M.; Valkenborg, D.; Gambin, A. *IsoSpec: Hyperfast Fine Structure Calculator.* Analytical Chemistry **2017**, *89* (6), 3272–3277. <https://doi.org/10.1021/acs.analchem.6b01459>
215
+
216
+ The Supporting Information of each paper has the algorithmic detail.
217
+
218
+ ## License
219
+
220
+ 2-clause BSD (see `LICENCE`). If you need different licensing terms, contact the authors. The authors appreciate (but do not require) being told when IsoSpec is used in other software.
@@ -0,0 +1,190 @@
1
+ # IsoSpec
2
+
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
+
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).
6
+
7
+ ## Installation
8
+
9
+ ### Python
10
+
11
+ ```bash
12
+ pip install IsoSpecPy
13
+ ```
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.
16
+
17
+ To build from source: `pip install .` from a checkout. A C++20 compiler is required.
18
+
19
+ ### R
20
+
21
+ ```r
22
+ install.packages("IsoSpecR")
23
+ ```
24
+
25
+ From source: `cd src && R CMD build IsoSpecR && R CMD INSTALL IsoSpecR_*.tar.gz`.
26
+
27
+ ### C++ library
28
+
29
+ ```bash
30
+ cd src/IsoSpec++
31
+ make # produces libIsoSpec++.so
32
+ ```
33
+
34
+ Or via CMake from the repo root:
35
+
36
+ ```bash
37
+ cmake -B build -DCMAKE_BUILD_TYPE=Release
38
+ cmake --build build
39
+ sudo cmake --install build
40
+ ```
41
+
42
+ Requires a C++20 compiler. CMake produces both shared and static libraries and installs headers under `${CMAKE_INSTALL_INCLUDEDIR}/IsoSpec++`.
43
+
44
+ ## Quick start
45
+
46
+ The most common usage is `IsoTotalProb`: ask for the smallest set of isotopologues whose summed probability covers a chosen fraction of the spectrum (typically 0.99–0.9999). The result is a materialized envelope — arrays of masses and probabilities you can read directly.
47
+
48
+ ### Python
49
+
50
+ ```python
51
+ import IsoSpecPy
52
+
53
+ # Isotopologues covering at least 99.9% of the probability mass of water.
54
+ iso = IsoSpecPy.IsoTotalProb(formula="H2O1", prob_to_cover=0.999)
55
+
56
+ for mass, prob in zip(iso.masses, iso.probs):
57
+ print(mass, prob)
58
+
59
+ # From an amino-acid sequence:
60
+ iso = IsoSpecPy.IsoTotalProb(peptide_sequence="AAAPPGQAAC", prob_to_cover=0.999)
61
+ print(list(zip(iso.masses, iso.probs)))
62
+
63
+ # ...optionally carrying [UNIMOD:<id>] modification tags (the notation this
64
+ # monorepo's SAGE search-engine fork writes: [UNIMOD:<id>]-SEQUENCE prefix
65
+ # for an N-terminal mod, X[UNIMOD:<id>] inline for an internal one,
66
+ # SEQUENCE-[UNIMOD:<id>] suffix for a C-terminal one):
67
+ iso = IsoSpecPy.IsoTotalProb(peptide_sequence="PEPTC[UNIMOD:4]DEK", prob_to_cover=0.999)
68
+ print(list(zip(iso.masses, iso.probs)))
69
+
70
+ # Mind the water: peptide_sequence= yields the *residue* composition, without
71
+ # the terminal H2O of a free peptide, so the masses above are 18.0106 Da
72
+ # lighter than the neutral molecule. Add it explicitly for the real peptide
73
+ # (the C++ Iso::FromFASTAWithMods below adds it by default -- the two APIs
74
+ # differ here, and this is the older Python behaviour kept for compatibility):
75
+ iso = IsoSpecPy.IsoTotalProb(peptide_sequence="PEPTC[UNIMOD:4]DEK", formula="H2O1",
76
+ prob_to_cover=0.999)
77
+
78
+ # Or get just the composition (element -> atom count), no envelope:
79
+ composition = IsoSpecPy.ParsePeptideSequence("PEPTC[UNIMOD:4]DEK")
80
+ print(dict(composition)) # {'H': 60, 'C': 39, 'N': 10, 'O': 16, 'S': 1}
81
+ ```
82
+
83
+ Resolves against a table of ~980 Unimod entries embedded in the library (isotope-labelled and glycan/derivatization "brick" modifications are excluded -- they aren't representable as a pure elemental delta). Pass `unimod_db_path="/path/to/table.csv"` to either function to resolve against a different table instead (same `id,name,mono_mass,composition` CSV shape as the embedded default).
84
+
85
+ See `Examples/Python/` for radiolabelling, custom elements, binned spectra, and FASTA/Unimod modifications.
86
+
87
+ ### C++
88
+
89
+ ```cpp
90
+ #include "IsoSpec++/isoSpec++.h"
91
+ #include "IsoSpec++/fixedEnvelopes.h"
92
+
93
+ using namespace IsoSpec;
94
+
95
+ int main() {
96
+ FixedEnvelope iso = FixedEnvelope::FromTotalProb("H2O1", 0.999, true, true);
97
+
98
+ for (size_t i = 0; i < iso.confs_no(); ++i) {
99
+ std::cout << iso.mass(i) << '\t' << iso.prob(i) << '\n';
100
+ }
101
+ }
102
+ ```
103
+
104
+ From a peptide sequence, optionally carrying `[UNIMOD:<id>]` modification tags (`fasta_mods.h`; same notation as the Python/R examples above):
105
+
106
+ ```cpp
107
+ #include "IsoSpec++/isoSpec++.h"
108
+ #include "IsoSpec++/fasta_mods.h"
109
+ #include "IsoSpec++/fixedEnvelopes.h"
110
+
111
+ using namespace IsoSpec;
112
+
113
+ int main() {
114
+ FixedEnvelope iso = FixedEnvelope::FromTotalProb(
115
+ Iso::FromFASTAWithMods("PEPTC[UNIMOD:4]DEK"), 0.999, true, true);
116
+
117
+ for (size_t i = 0; i < iso.confs_no(); ++i) {
118
+ std::cout << iso.mass(i) << '\t' << iso.prob(i) << '\n';
119
+ }
120
+ }
121
+ ```
122
+
123
+ `Iso::FromFASTAWithMods` takes an optional trailing `unimod_db_path` argument to resolve against a different Unimod table instead of the one embedded at compile time. For just the composition, no envelope, call `parse_fasta_with_mods`/`parse_fasta_with_mods_full` directly (or the allocation-free `_into` forms, for a hot loop over many sequences) — see `fasta_mods.h`.
124
+
125
+ Quickest way to build:
126
+
127
+ ```bash
128
+ clang++ -std=c++20 water.cpp src/IsoSpec++/unity-build.cpp -o water
129
+ ```
130
+
131
+ (`unity-build.cpp` is a single translation unit that `#include`s every source file — see `Examples/C++/COMPILING`.)
132
+
133
+ ### R
134
+
135
+ ```r
136
+ library(IsoSpecR)
137
+ water <- c(H = 2, O = 1)
138
+ IsoSpecify(molecule = water, stopCondition = 0.999)
139
+ ```
140
+
141
+ `IsoSpecify` only ever takes a raw named-integer-vector `molecule` -- it has no sequence-string parameter -- so a `[UNIMOD:<id>]`-annotated peptide sequence takes two calls instead of one: `RParsePeptideSequence()` to resolve the sequence into a composition, then `IsoSpecify()` as above.
142
+
143
+ ```r
144
+ composition <- RParsePeptideSequence("PEPTC[UNIMOD:4]DEK")
145
+ print(composition) # H C N O S
146
+ # 60 39 10 16 1
147
+ IsoSpecify(molecule = composition, stopCondition = 0.999)
148
+ ```
149
+
150
+ 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
+
152
+ See `Examples/R/` for radiolabelling, full-spectrum extraction, and Unimod-annotated sequences.
153
+
154
+ ## Advanced features
155
+
156
+ - **Alternative algorithms** — for cases where `IsoTotalProb` isn't the right shape:
157
+ - `IsoThreshold` — all isotopologues with probability above a fixed threshold.
158
+ - `IsoStochastic` — simulate a measured spectrum by sampling integer ion counts.
159
+ - `IsoBinned` — histogram-style envelope at a chosen bin width.
160
+ - **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
+ - **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.
163
+ - **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.
165
+
166
+ Algorithmic details are in the papers cited below; the Supporting Information of each is the better starting point for implementation specifics.
167
+
168
+ ## Repository layout
169
+
170
+ ```
171
+ src/IsoSpec++/ C++ core library (the algorithms live here)
172
+ src/IsoSpecPy/ Python binding, loaded via cffi
173
+ src/IsoSpecR/ R binding, via Rcpp
174
+ Examples/ Working examples in each language
175
+ tests/ C, C++, and Python test suites
176
+ skbuild/ scikit-build-core entry point used when installing the Python wheel
177
+ ```
178
+
179
+ ## Citation
180
+
181
+ If IsoSpec is useful in your research, please cite:
182
+
183
+ - Łącki, M. K.; Valkenborg, D.; Startek, M. P. *IsoSpec2: Ultrafast Fine Structure Calculator.* Analytical Chemistry **2020**, *92* (14), 9472–9475. <https://doi.org/10.1021/acs.analchem.0c00959>
184
+ - Łącki, M. K.; Startek, M.; Valkenborg, D.; Gambin, A. *IsoSpec: Hyperfast Fine Structure Calculator.* Analytical Chemistry **2017**, *89* (6), 3272–3277. <https://doi.org/10.1021/acs.analchem.6b01459>
185
+
186
+ The Supporting Information of each paper has the algorithmic detail.
187
+
188
+ ## License
189
+
190
+ 2-clause BSD (see `LICENCE`). If you need different licensing terms, contact the authors. The authors appreciate (but do not require) being told when IsoSpec is used in other software.
@@ -0,0 +1 @@
1
+ 2.5.0
@@ -0,0 +1,94 @@
1
+ [build-system]
2
+ requires = ["setuptools", "scikit-build-core"]
3
+ build-backend = "scikit_build_core.build"
4
+
5
+ # Single source of truth for the version: the VERSION file at the top of the
6
+ # tree, which CMakeLists.txt reads too (see the note there). Keep that file to
7
+ # one bare version number on one line.
8
+ [[tool.dynamic-metadata]]
9
+ field = "version"
10
+ provider = "scikit_build_core.metadata.regex"
11
+ input = "VERSION"
12
+ regex = "^\\s*(?P<value>[0-9]+(?:\\.[0-9]+)*)"
13
+
14
+ [tool.scikit-build]
15
+ cmake.source-dir = "skbuild"
16
+ # The compiled part is not a Python extension module: it is a plain C-ABI
17
+ # shared library that isoFFI.py dlopen()s through cffi's ABI mode, and it never
18
+ # includes Python.h or links libpython. So the wheel is tagged py3-none-<plat>
19
+ # rather than per-interpreter (cp3XX) or Stable ABI (abi3): one binary per
20
+ # platform serves every CPython -- free-threaded builds included, which abi3
21
+ # does not cover -- and PyPy too. cibuildwheel builds it once per platform and
22
+ # only *tests* it under each remaining interpreter. Should the library ever
23
+ # start using the CPython API, this must go back to per-interpreter tags.
24
+ wheel.py-api = "py3"
25
+
26
+ [project]
27
+ name = "IsoSpecPy"
28
+ dependencies = ["cffi"]
29
+ description = "IsoSpecPy is a Python library for computing isotopic distributions of molecules."
30
+ dynamic = ["version"]
31
+ license = "BSD-2-Clause"
32
+ license-files = ["LICENCE"]
33
+ authors = [{name = "Michał Startek"}, {name = "Mateusz Łącki"}]
34
+ readme = "README.md"
35
+ requires-python = ">=3.9"
36
+ classifiers = [
37
+ "Programming Language :: Python :: 3",
38
+ "Programming Language :: Python :: 3.9",
39
+ "Programming Language :: Python :: 3.10",
40
+ "Programming Language :: Python :: 3.11",
41
+ "Programming Language :: Python :: 3.12",
42
+ "Programming Language :: Python :: 3.13",
43
+ "Programming Language :: Python :: 3.14",
44
+ "Programming Language :: Python :: 3.15",
45
+ "Programming Language :: Python :: Implementation :: CPython",
46
+ "Programming Language :: Python :: Implementation :: PyPy",
47
+ "Topic :: Software Development :: Libraries :: Python Modules",
48
+ "Development Status :: 5 - Production/Stable",
49
+ "Operating System :: OS Independent",
50
+ ]
51
+ keywords = ["isotopic distribution", "mass spectrometry", "chemistry", "isotopic envelope", "isotopologues"]
52
+
53
+ [project.urls]
54
+ Homepage = "https://github.com/MatteoLacki/IsoSpec"
55
+ Repository = "https://github.com/MatteoLacki/IsoSpec"
56
+
57
+
58
+
59
+ [project.optional-dependencies]
60
+ testing = ["pytest", "OldIsoSpecPy"]
61
+
62
+ [tool.pytest.ini_options]
63
+ testpaths = ["tests/Python"]
64
+
65
+ [tool.cibuildwheel]
66
+ enable = ["pypy"]
67
+ test-command = "python -m pytest -o faulthandler_timeout=600 {project}/tests/Python"
68
+ #test-sources = ["tests/Python"]
69
+ test-extras = ["testing"]
70
+
71
+ # Apple Clang's libc++ has no <experimental/simd>; build with Homebrew GCC instead.
72
+ [tool.cibuildwheel.macos]
73
+ before-all = "brew install gcc"
74
+ # NB: cibuildwheel evaluates these with bashlex, which supports command
75
+ # substitution but *not* pipelines - hence the `sh -c` wrapper.
76
+ environment = { CC = "$(sh -c 'ls $(brew --prefix gcc)/bin/gcc-* | sort -V | tail -1')", CXX = "$(sh -c 'ls $(brew --prefix gcc)/bin/g++-* | sort -V | tail -1')" }
77
+
78
+ [tool.scikit-build.sdist]
79
+ exclude = ["**/*"]
80
+ include = [
81
+ "/src/IsoSpecPy/*.py",
82
+ "/src/IsoSpec++/*.c",
83
+ "/src/IsoSpec++/*.h",
84
+ "/src/IsoSpec++/*.hpp",
85
+ "/src/IsoSpec++/*.cpp",
86
+ # skbuild/CMakeLists.txt include()s IsaDispatch.cmake; without this the
87
+ # sdist configures no further than that line.
88
+ "/src/IsoSpec++/*.cmake",
89
+ "/VERSION",
90
+ "/LICENCE",
91
+ "/README.md",
92
+ "/pyproject.toml",
93
+ "/skbuild/CMakeLists.txt"
94
+ ]
@@ -0,0 +1,71 @@
1
+ cmake_minimum_required(VERSION 3.15)
2
+
3
+ project(IsoSpecPy CXX)
4
+
5
+ set(CMAKE_CXX_STANDARD 20)
6
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
7
+ set(CMAKE_CXX_EXTENSIONS OFF)
8
+
9
+ #find_package(PythonExtensions REQUIRED)
10
+ #find_package(Python COMPONENTS Interpreter Development REQUIRED)
11
+
12
+ # Runtime ISA dispatch. This matters most here: the wheel is built with no
13
+ # -march at all, so without it every user gets the x86-64 baseline -- 2-double
14
+ # vectors -- no matter what their CPU can do.
15
+ include(${CMAKE_CURRENT_LIST_DIR}/../src/IsoSpec++/IsaDispatch.cmake)
16
+
17
+ add_library(IsoSpecCppPy SHARED ../src/IsoSpec++/unity-build.cpp ${isospec_ISA_SRCS})
18
+ target_compile_features(IsoSpecCppPy PRIVATE cxx_std_20)
19
+
20
+ # macOS wheels are built with Homebrew GCC (Apple's libc++ has no
21
+ # <experimental/simd>), and Homebrew bottles its libstdc++.6.dylib for the
22
+ # *current* macOS -- minos 26.0 today. delocate refuses to bundle a dylib whose
23
+ # minimum target is newer than the wheel's (11.0 on arm64), and hard-fails the
24
+ # build. Linking the C++ runtime statically leaves libSystem as the only
25
+ # dependency, so there is nothing for delocate to reject. The
26
+ # "built for newer macOS version" warnings this draws from ld are the archive
27
+ # members' own load commands and are harmless.
28
+ #
29
+ # Plain -static-libstdc++ is not enough, though: the archive members keep
30
+ # default visibility, so the dylib would re-export ~2000 libstdc++ symbols.
31
+ # Mach-O coalesces weak definitions (templates, inline functions, vtables)
32
+ # across *all* loaded images regardless of two-level namespace, so a process
33
+ # that also loads anything built against Homebrew's libstdc++.6.dylib ends up
34
+ # with two copies of that runtime spliced together and aborts inside dlopen --
35
+ # silently, with no dyld message and nothing Python can catch. A seven-line
36
+ # extension whose static initialiser touches std::locale is enough to trigger
37
+ # it; OldIsoSpecPy, which the wheel test suite installs, is one such consumer.
38
+ # -load_hidden links the archive with all of its symbols hidden, so our private
39
+ # copy of the runtime stays private. It replaces -static-libstdc++ (which would
40
+ # otherwise pull the same archive in again, visibly).
41
+ if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
42
+ execute_process(COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=libstdc++.a
43
+ OUTPUT_VARIABLE ISOSPEC_LIBSTDCXX_A
44
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
45
+ if(NOT IS_ABSOLUTE "${ISOSPEC_LIBSTDCXX_A}" OR NOT EXISTS "${ISOSPEC_LIBSTDCXX_A}")
46
+ message(FATAL_ERROR
47
+ "${CMAKE_CXX_COMPILER} could not locate a static libstdc++.a "
48
+ "(-print-file-name returned '${ISOSPEC_LIBSTDCXX_A}'). The macOS wheel "
49
+ "needs one: linking libstdc++ dynamically makes delocate reject the "
50
+ "Homebrew bottle, and linking it visibly breaks other libstdc++ users "
51
+ "in the same process.")
52
+ endif()
53
+ # -load_hidden needs a reasonably modern ld64; an older one rejects the
54
+ # option outright, which is a loud failure rather than a silent regression.
55
+ #
56
+ # The archive is only half of it: our own translation units instantiate std::
57
+ # templates (std::to_string, sorts, the Mersenne twister), and those weak
58
+ # definitions coalesce across images just as readily. std:: is not part of
59
+ # this library's ABI -- callers reach it through cwrapper's extern "C"
60
+ # interface -- so none of it has any business being exported.
61
+ # tests/Python/test_cxx_runtime.py fails if any of this comes undone.
62
+ target_link_options(IsoSpecCppPy PRIVATE
63
+ -static-libgcc "-Wl,-load_hidden,${ISOSPEC_LIBSTDCXX_A}"
64
+ "-Wl,-unexported_symbol,__ZNSt*" "-Wl,-unexported_symbol,__ZNKSt*"
65
+ "-Wl,-unexported_symbol,__ZSt*")
66
+ endif()
67
+ #python_extension_module(IsoSpecCppPy)
68
+ install(TARGETS IsoSpecCppPy LIBRARY DESTINATION IsoSpecPy)
69
+ install(DIRECTORY ../src/IsoSpec++/ DESTINATION IsoSpecPy/IsoSpec++
70
+ FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" PATTERN "*.hxx"
71
+ )
@@ -0,0 +1,78 @@
1
+ # Runtime ISA dispatch, for the CMake builds.
2
+ #
3
+ # The two batched fill kernels are compiled once per instruction-set level, each
4
+ # into its own translation unit with its own -march, and the one to run is chosen
5
+ # from the CPU at startup. They must never share a compilation with the rest of
6
+ # the library: a raised -march would otherwise reach every inline function in the
7
+ # headers, and the linker could then pick an AVX-512 copy of one for a code path
8
+ # reached on a CPU without it. See isa_kernels.h for the full reasoning.
9
+ #
10
+ # Include this, then add ${isospec_ISA_SRCS} to your target's sources. It sets
11
+ # the per-source flags and defines ISOSPEC_ISA_DISPATCH=1 for the directory.
12
+ #
13
+ # The kernel sources deliberately get no -march of their own beyond the level
14
+ # they implement: the baseline level has to stay runnable on any CPU of the
15
+ # target architecture, since it is what dispatch falls back to.
16
+
17
+ include(CheckCXXCompilerFlag)
18
+ include(CheckCXXSourceCompiles)
19
+
20
+ set(_isa_dir ${CMAKE_CURRENT_LIST_DIR})
21
+
22
+ # Worth turning off when building exclusively for the machine that will run the
23
+ # result (-march=native, say): dispatching costs an indirect call per marginal-0
24
+ # run -- measured at 0.5-3% on the binned fill and 2-7% on the threshold fill --
25
+ # which a single-level build compiled for the actual CPU does not pay.
26
+ option(ISOSPEC_ISA_DISPATCH "Build several instruction-set levels and select at run time" ON)
27
+
28
+ if(NOT ISOSPEC_ISA_DISPATCH)
29
+ set(isospec_ISA_SRCS "")
30
+ message(STATUS "IsoSpec: runtime ISA dispatch disabled; single-level build")
31
+ return()
32
+ endif()
33
+
34
+ set(isospec_ISA_SRCS
35
+ ${_isa_dir}/isa_kernels_baseline.cpp
36
+ ${_isa_dir}/isa_kernels_avx.cpp
37
+ ${_isa_dir}/isa_kernels_v3.cpp
38
+ ${_isa_dir}/isa_kernels_v4.cpp
39
+ )
40
+
41
+ set(_isa_avx_flags -mavx)
42
+ set(_isa_v3_flags -mavx2 -mfma)
43
+ set(_isa_v4_flags -mavx512f -mavx512bw -mavx512dq -mavx512vl)
44
+
45
+ # Only offer a level its flags where the compiler will take them; they are
46
+ # meaningless off x86, and older toolchains may not know the AVX-512 ones. The
47
+ # .cpp files then self-check the predefined macros those flags produce, so a
48
+ # level whose flags did not arrive publishes an empty table and dispatch simply
49
+ # never selects it -- rather than it being quietly built at the wrong width.
50
+ check_cxx_compiler_flag("-mavx" ISOSPEC_CXX_ACCEPTS_AVX)
51
+ check_cxx_compiler_flag("-mavx2 -mfma" ISOSPEC_CXX_ACCEPTS_V3)
52
+ check_cxx_compiler_flag("-mavx512f -mavx512bw -mavx512dq -mavx512vl" ISOSPEC_CXX_ACCEPTS_V4)
53
+
54
+ if(ISOSPEC_CXX_ACCEPTS_AVX)
55
+ set_source_files_properties(${_isa_dir}/isa_kernels_avx.cpp
56
+ PROPERTIES COMPILE_OPTIONS "${_isa_avx_flags}")
57
+ message(STATUS "IsoSpec: building ISA level avx (AVX, no FMA)")
58
+ else()
59
+ message(STATUS "IsoSpec: ISA level avx not available with this compiler/target")
60
+ endif()
61
+
62
+ if(ISOSPEC_CXX_ACCEPTS_V3)
63
+ set_source_files_properties(${_isa_dir}/isa_kernels_v3.cpp
64
+ PROPERTIES COMPILE_OPTIONS "${_isa_v3_flags}")
65
+ message(STATUS "IsoSpec: building ISA level v3 (AVX2+FMA)")
66
+ else()
67
+ message(STATUS "IsoSpec: ISA level v3 not available with this compiler/target")
68
+ endif()
69
+
70
+ if(ISOSPEC_CXX_ACCEPTS_V4)
71
+ set_source_files_properties(${_isa_dir}/isa_kernels_v4.cpp
72
+ PROPERTIES COMPILE_OPTIONS "${_isa_v4_flags}")
73
+ message(STATUS "IsoSpec: building ISA level v4 (AVX-512)")
74
+ else()
75
+ message(STATUS "IsoSpec: ISA level v4 not available with this compiler/target")
76
+ endif()
77
+
78
+ add_definitions(-DISOSPEC_ISA_DISPATCH=1)