IsoSpecPy 2.4.0__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 (71) hide show
  1. {isospecpy-2.4.0 → isospecpy-2.5.0}/PKG-INFO +70 -9
  2. {isospecpy-2.4.0 → isospecpy-2.5.0}/README.md +59 -6
  3. isospecpy-2.5.0/VERSION +1 -0
  4. {isospecpy-2.4.0 → isospecpy-2.5.0}/pyproject.toml +19 -2
  5. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/aligned_ptr.h +18 -2
  6. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/btrd.h +4 -4
  7. isospecpy-2.5.0/src/IsoSpec++/composition.cpp +85 -0
  8. isospecpy-2.5.0/src/IsoSpec++/composition.h +126 -0
  9. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/cwrapper.cpp +157 -0
  10. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/cwrapper.h +49 -1
  11. isospecpy-2.5.0/src/IsoSpec++/element_lookup.cpp +100 -0
  12. isospecpy-2.5.0/src/IsoSpec++/element_lookup.h +36 -0
  13. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/element_tables.cpp +10 -0
  14. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/element_tables.h +1 -1
  15. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/fasta.h +1 -1
  16. isospecpy-2.5.0/src/IsoSpec++/fasta_mods.cpp +244 -0
  17. isospecpy-2.5.0/src/IsoSpec++/fasta_mods.h +70 -0
  18. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/fixedEnvelopes.cpp +65 -23
  19. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/fixedEnvelopes.h +1 -1
  20. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isoMath.cpp +0 -1
  21. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isoMath.h +16 -2
  22. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isoSpec++.cpp +134 -51
  23. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isoSpec++.h +29 -0
  24. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/marginalTrek++.cpp +3 -9
  25. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/marginalTrek++.h +12 -15
  26. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/misc.h +4 -1
  27. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/platform.h +1 -1
  28. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/pod_vector.h +3 -4
  29. isospecpy-2.5.0/src/IsoSpec++/python-build.cpp +37 -0
  30. isospecpy-2.5.0/src/IsoSpec++/unimod.cpp +171 -0
  31. isospecpy-2.5.0/src/IsoSpec++/unimod.h +80 -0
  32. isospecpy-2.5.0/src/IsoSpec++/unimod_support.h +2169 -0
  33. isospecpy-2.5.0/src/IsoSpec++/unimod_table_data.h +1027 -0
  34. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/unity-build.cpp +4 -0
  35. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/IsoSpecPy.py +189 -25
  36. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/PeriodicTbl.py +17 -0
  37. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/approximations.py +2 -2
  38. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/isoFFI.py +42 -15
  39. isospecpy-2.4.0/VERSION +0 -1
  40. isospecpy-2.4.0/src/IsoSpec++/python-build.cpp +0 -23
  41. {isospecpy-2.4.0 → isospecpy-2.5.0}/LICENCE +0 -0
  42. {isospecpy-2.4.0 → isospecpy-2.5.0}/skbuild/CMakeLists.txt +0 -0
  43. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/IsaDispatch.cmake +0 -0
  44. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/allocator.cpp +0 -0
  45. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/allocator.h +0 -0
  46. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/conf.h +0 -0
  47. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/dirtyAllocator.cpp +0 -0
  48. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/dirtyAllocator.h +0 -0
  49. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/fasta.cpp +0 -0
  50. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_dispatch.cpp +0 -0
  51. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernel_impl.h +0 -0
  52. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels.h +0 -0
  53. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels_avx.cpp +0 -0
  54. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels_baseline.cpp +0 -0
  55. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels_v3.cpp +0 -0
  56. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels_v4.cpp +0 -0
  57. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/iso_simd.h +0 -0
  58. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/misc.cpp +0 -0
  59. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/mman.cpp +0 -0
  60. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/mman.h +0 -0
  61. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/operators.cpp +0 -0
  62. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/operators.h +0 -0
  63. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/platform_incl.h +0 -0
  64. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/summator.h +0 -0
  65. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/Advanced.py +0 -0
  66. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/Distributions.py +0 -0
  67. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/Formulas.py +0 -0
  68. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/IsoSpecPyOld.py +0 -0
  69. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/__init__.py +0 -0
  70. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/__main__.py +0 -0
  71. {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/confs_passthrough.py +0 -0
@@ -1,19 +1,27 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IsoSpecPy
3
- Version: 2.4.0
3
+ Version: 2.5.0
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.6
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
11
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
20
  Classifier: Development Status :: 5 - Production/Stable
13
21
  Classifier: Operating System :: OS Independent
14
22
  Project-URL: Homepage, https://github.com/MatteoLacki/IsoSpec
15
23
  Project-URL: Repository, https://github.com/MatteoLacki/IsoSpec
16
- Requires-Python: >=3.6
24
+ Requires-Python: >=3.9
17
25
  Requires-Dist: cffi
18
26
  Provides-Extra: testing
19
27
  Requires-Dist: pytest; extra == "testing"
@@ -34,7 +42,7 @@ IsoSpec is primarily used as a library by mass spectrometry software. It is impl
34
42
  pip install IsoSpecPy
35
43
  ```
36
44
 
37
- 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.6.
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.
38
46
 
39
47
  To build from source: `pip install .` from a checkout. A C++20 compiler is required.
40
48
 
@@ -78,12 +86,33 @@ iso = IsoSpecPy.IsoTotalProb(formula="H2O1", prob_to_cover=0.999)
78
86
  for mass, prob in zip(iso.masses, iso.probs):
79
87
  print(mass, prob)
80
88
 
81
- # From an amino-acid FASTA sequence:
82
- iso = IsoSpecPy.IsoTotalProb(fasta="AAAPPGQAAC", prob_to_cover=0.999)
89
+ # From an amino-acid sequence:
90
+ iso = IsoSpecPy.IsoTotalProb(peptide_sequence="AAAPPGQAAC", prob_to_cover=0.999)
83
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}
84
111
  ```
85
112
 
86
- See `Examples/Python/` for radiolabelling, custom elements, binned spectra, and FASTA modifications.
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.
87
116
 
88
117
  ### C++
89
118
 
@@ -102,6 +131,27 @@ int main() {
102
131
  }
103
132
  ```
104
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
+
105
155
  Quickest way to build:
106
156
 
107
157
  ```bash
@@ -118,7 +168,18 @@ water <- c(H = 2, O = 1)
118
168
  IsoSpecify(molecule = water, stopCondition = 0.999)
119
169
  ```
120
170
 
121
- See `Examples/R/` for radiolabelling and full-spectrum extraction.
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.
122
183
 
123
184
  ## Advanced features
124
185
 
@@ -126,7 +187,7 @@ See `Examples/R/` for radiolabelling and full-spectrum extraction.
126
187
  - `IsoThreshold` — all isotopologues with probability above a fixed threshold.
127
188
  - `IsoStochastic` — simulate a measured spectrum by sampling integer ion counts.
128
189
  - `IsoBinned` — histogram-style envelope at a chosen bin width.
129
- - **FASTA support** — build an `Iso` directly from an amino-acid sequence; optionally include the N/C-terminal water.
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.
130
191
  - **Custom isotopic tables** — override natural abundances per element (e.g. for radio- or stable-isotope labelling). See `Examples/*/radiolabelling.*`.
131
192
  - **Fixed-envelope arithmetic** — addition, normalization, convolution, Wasserstein distance.
132
193
  - **Nominal-mass mode** — compute distributions over nucleon counts instead of real masses.
@@ -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.6.
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
16
 
17
17
  To build from source: `pip install .` from a checkout. A C++20 compiler is required.
18
18
 
@@ -56,12 +56,33 @@ iso = IsoSpecPy.IsoTotalProb(formula="H2O1", prob_to_cover=0.999)
56
56
  for mass, prob in zip(iso.masses, iso.probs):
57
57
  print(mass, prob)
58
58
 
59
- # From an amino-acid FASTA sequence:
60
- iso = IsoSpecPy.IsoTotalProb(fasta="AAAPPGQAAC", prob_to_cover=0.999)
59
+ # From an amino-acid sequence:
60
+ iso = IsoSpecPy.IsoTotalProb(peptide_sequence="AAAPPGQAAC", prob_to_cover=0.999)
61
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}
62
81
  ```
63
82
 
64
- See `Examples/Python/` for radiolabelling, custom elements, binned spectra, and FASTA modifications.
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.
65
86
 
66
87
  ### C++
67
88
 
@@ -80,6 +101,27 @@ int main() {
80
101
  }
81
102
  ```
82
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
+
83
125
  Quickest way to build:
84
126
 
85
127
  ```bash
@@ -96,7 +138,18 @@ water <- c(H = 2, O = 1)
96
138
  IsoSpecify(molecule = water, stopCondition = 0.999)
97
139
  ```
98
140
 
99
- See `Examples/R/` for radiolabelling and full-spectrum extraction.
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.
100
153
 
101
154
  ## Advanced features
102
155
 
@@ -104,7 +157,7 @@ See `Examples/R/` for radiolabelling and full-spectrum extraction.
104
157
  - `IsoThreshold` — all isotopologues with probability above a fixed threshold.
105
158
  - `IsoStochastic` — simulate a measured spectrum by sampling integer ion counts.
106
159
  - `IsoBinned` — histogram-style envelope at a chosen bin width.
107
- - **FASTA support** — build an `Iso` directly from an amino-acid sequence; optionally include the N/C-terminal water.
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.
108
161
  - **Custom isotopic tables** — override natural abundances per element (e.g. for radio- or stable-isotope labelling). See `Examples/*/radiolabelling.*`.
109
162
  - **Fixed-envelope arithmetic** — addition, normalization, convolution, Wasserstein distance.
110
163
  - **Nominal-mass mode** — compute distributions over nucleon counts instead of real masses.
@@ -0,0 +1 @@
1
+ 2.5.0
@@ -13,6 +13,15 @@ regex = "^\\s*(?P<value>[0-9]+(?:\\.[0-9]+)*)"
13
13
 
14
14
  [tool.scikit-build]
15
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"
16
25
 
17
26
  [project]
18
27
  name = "IsoSpecPy"
@@ -23,10 +32,18 @@ license = "BSD-2-Clause"
23
32
  license-files = ["LICENCE"]
24
33
  authors = [{name = "Michał Startek"}, {name = "Mateusz Łącki"}]
25
34
  readme = "README.md"
26
- requires-python = ">=3.6"
35
+ requires-python = ">=3.9"
27
36
  classifiers = [
28
37
  "Programming Language :: Python :: 3",
29
- "Programming Language :: Python :: 3.6",
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",
30
47
  "Topic :: Software Development :: Libraries :: Python Modules",
31
48
  "Development Status :: 5 - Production/Stable",
32
49
  "Operating System :: OS Independent",
@@ -1,3 +1,19 @@
1
+ /*
2
+ * Copyright (C) 2026 Mateusz Łącki and Michał Startek.
3
+ *
4
+ * This file is part of IsoSpec.
5
+ *
6
+ * IsoSpec is free software: you can redistribute it and/or modify
7
+ * it under the terms of the Simplified ("2-clause") BSD licence.
8
+ *
9
+ * IsoSpec is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+ *
13
+ * You should have received a copy of the Simplified BSD Licence
14
+ * along with IsoSpec. If not, see <https://opensource.org/licenses/BSD-2-Clause>.
15
+ */
16
+
1
17
  #pragma once
2
18
 
3
19
  #include <cstddef> // std::size_t, std::nullptr_t
@@ -311,7 +327,7 @@ inline void vm_resize(vm_region& r, std::size_t new_bytes)
311
327
 
312
328
  template<class T, std::size_t Alignment>
313
329
  class aligned_unique_ptr {
314
- static_assert((Alignment & (Alignment - 1)) == 0); // meaning: Alignment is a power of two
330
+ static_assert((Alignment & (Alignment - 1)) == 0); // meaning: Alignment is a power of two
315
331
  static_assert(Alignment >= alignof(T));
316
332
  static_assert(Alignment <= 4096, "Alignment must not exceed the smallest page size assumed across supported platforms");
317
333
  // Storage is acquired/released as raw bytes (aligned_alloc/free, or
@@ -396,7 +412,7 @@ class aligned_unique_ptr {
396
412
  free_small(p);
397
413
  }
398
414
 
399
- public:
415
+ public:
400
416
  static constexpr std::size_t alignment = Alignment;
401
417
 
402
418
  // Returned by release_with_deleter(): the raw pointer plus everything
@@ -93,7 +93,7 @@ IntType btrd(IntType _t, RealType p, IntType m, std::mt19937& urng = random_gen)
93
93
 
94
94
  while(true) {
95
95
  RealType u;
96
- RealType v = stdunif(urng);
96
+ RealType v = rdvariate_unif01(urng);
97
97
  if(v <= btrd_u_rv_r) {
98
98
  u = v/btrd_v_r - 0.43;
99
99
  return static_cast<IntType>(floor(
@@ -101,11 +101,11 @@ IntType btrd(IntType _t, RealType p, IntType m, std::mt19937& urng = random_gen)
101
101
  }
102
102
 
103
103
  if(v >= btrd_v_r) {
104
- u = stdunif(urng) - 0.5;
104
+ u = rdvariate_unif01(urng) - 0.5;
105
105
  } else {
106
106
  u = v/btrd_v_r - 0.93;
107
107
  u = ((u < 0)? -0.5 : 0.5) - u;
108
- v = stdunif(urng) * btrd_v_r;
108
+ v = rdvariate_unif01(urng) * btrd_v_r;
109
109
  }
110
110
 
111
111
  RealType us = 0.5 - abs(u);
@@ -163,7 +163,7 @@ IntType invert(IntType t, RealType p, std::mt19937& urng = random_gen)
163
163
  RealType s = p / q;
164
164
  RealType a = (t + 1) * s;
165
165
  RealType r = pow((1 - p), static_cast<RealType>(t));
166
- RealType u = stdunif(urng);
166
+ RealType u = rdvariate_unif01(urng);
167
167
  IntType x = 0;
168
168
  while(u > r) {
169
169
  u = u - r;
@@ -0,0 +1,85 @@
1
+ /*
2
+ * Copyright (C) 2015-2026 Mateusz Łącki and Michał Startek.
3
+ *
4
+ * This file is part of IsoSpec.
5
+ *
6
+ * IsoSpec is free software: you can redistribute it and/or modify
7
+ * it under the terms of the Simplified ("2-clause") BSD licence.
8
+ *
9
+ * IsoSpec is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+ *
13
+ * You should have received a copy of the Simplified BSD Licence
14
+ * along with IsoSpec. If not, see <https://opensource.org/licenses/BSD-2-Clause>.
15
+ */
16
+
17
+ #include "composition.h"
18
+
19
+ #include <stdexcept>
20
+ #include <string>
21
+ #include <vector>
22
+
23
+ #include "element_lookup.h"
24
+ #include "element_tables.h"
25
+
26
+ namespace IsoSpec {
27
+
28
+ void expand_composition_into(const ElementComposition& composition, ExpandedComposition& out,
29
+ bool use_nominal_masses) {
30
+ out.clear();
31
+
32
+ const size_t dimNumber = composition.element_first_index.size();
33
+ const double* masses_table = use_nominal_masses ? elem_table_massNo : elem_table_mass;
34
+
35
+ out.isotopeNumbers.reserve(dimNumber);
36
+ out.atomCounts.reserve(dimNumber);
37
+
38
+ for (size_t i = 0; i < dimNumber; i++) {
39
+ const int first_idx = composition.element_first_index[i];
40
+ const int num_isotopes = element_isotope_count(first_idx);
41
+ out.isotopeNumbers.push_back(num_isotopes);
42
+ out.atomCounts.push_back(composition.count[i]);
43
+ for (int k = 0; k < num_isotopes; k++) {
44
+ out.isotope_masses.push_back(masses_table[first_idx + k]);
45
+ out.isotope_probabilities.push_back(elem_table_probability[first_idx + k]);
46
+ }
47
+ }
48
+ }
49
+
50
+ void reject_negative_counts(const ElementComposition& composition) {
51
+ std::string offenders;
52
+ for (size_t i = 0; i < composition.count.size(); i++) {
53
+ if (composition.count[i] < 0) {
54
+ if (!offenders.empty())
55
+ offenders += ", ";
56
+ offenders += elem_table_symbol[composition.element_first_index[i]];
57
+ }
58
+ }
59
+
60
+ if (offenders.empty())
61
+ return;
62
+
63
+ throw std::invalid_argument(
64
+ "Negative atom count for element(s): " + offenders +
65
+ " -- a molecule can't contain a negative number of atoms. Signed counts are only "
66
+ "meaningful for a modification's composition delta (where a mod may remove more of an "
67
+ "element than the rest of the molecule provides), never for a molecule itself.");
68
+ }
69
+
70
+ Iso build_iso_from_composition(const ElementComposition& composition, bool use_nominal_masses) {
71
+ // Checked here rather than in expand_composition_into: a negative count is
72
+ // a perfectly good intermediate in a composition (a modification's delta
73
+ // can outweigh the bare sequence), and only becomes an error where the
74
+ // composition becomes a molecule -- which is here.
75
+ reject_negative_counts(composition);
76
+
77
+ ExpandedComposition expanded;
78
+ expand_composition_into(composition, expanded, use_nominal_masses);
79
+
80
+ return Iso(static_cast<int>(expanded.atomCounts.size()), expanded.isotopeNumbers.data(),
81
+ expanded.atomCounts.data(), expanded.isotope_masses.data(),
82
+ expanded.isotope_probabilities.data());
83
+ }
84
+
85
+ } // namespace IsoSpec
@@ -0,0 +1,126 @@
1
+ /*
2
+ * Copyright (C) 2015-2026 Mateusz Łącki and Michał Startek.
3
+ *
4
+ * This file is part of IsoSpec.
5
+ *
6
+ * IsoSpec is free software: you can redistribute it and/or modify
7
+ * it under the terms of the Simplified ("2-clause") BSD licence.
8
+ *
9
+ * IsoSpec is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+ *
13
+ * You should have received a copy of the Simplified BSD Licence
14
+ * along with IsoSpec. If not, see <https://opensource.org/licenses/BSD-2-Clause>.
15
+ */
16
+
17
+ // The library's vocabulary for "a molecule as element counts", and the one
18
+ // implementation of turning that into the isotope masses and probabilities an
19
+ // Iso is built from.
20
+ //
21
+ // This lives in its own header because three unrelated things need it: the
22
+ // formula parser (isoSpec++.cpp), the peptide-sequence parsers (fasta_mods.h),
23
+ // and the C ABI (cwrapper.cpp). It used to sit inside fasta_mods.h, which
24
+ // meant the core formula path could only share the resolution by including a
25
+ // peptide-modification header -- backwards, so it didn't, and kept its own
26
+ // copy instead. See docs/ai/composition_expansion.md.
27
+
28
+ #pragma once
29
+
30
+ #include <vector>
31
+
32
+ #include "isoSpec++.h"
33
+
34
+ namespace IsoSpec {
35
+
36
+ //! A resolved elemental composition: parallel arrays of
37
+ //! (element_table_first_index, count) pairs, one per element actually
38
+ //! present (non-zero net count).
39
+ //!
40
+ //! Counts are *signed*, and parse_fasta_with_mods/_full really can return a
41
+ //! negative one: a modification's composition delta may remove more atoms of
42
+ //! some element than the bare sequence provides ("G[UNIMOD:11]" nets H-1
43
+ //! S-1). That is a legitimate intermediate -- adding a second modification
44
+ //! could bring it back up -- so the parser does not reject it. The
45
+ //! non-negativity check belongs at the point a composition becomes a
46
+ //! molecule, and lives in build_iso_from_composition; anything else
47
+ //! consuming an ElementComposition directly (the C ABI's
48
+ //! parseFastaWithModsC, R's RParsePeptideSequence) must do its own check
49
+ //! before handing the counts to an Iso.
50
+ struct ElementComposition {
51
+ std::vector<int> element_first_index;
52
+ std::vector<int> count;
53
+
54
+ //! Empties both vectors without releasing their capacity -- a caller
55
+ //! that reuses one ElementComposition across many parse_fasta_with_mods_into
56
+ //! calls (e.g. building a cache over millions of peptides) pays for the
57
+ //! underlying heap allocation at most a handful of times total (until
58
+ //! capacity reaches this feature's real ceiling, ~30 elements), not once
59
+ //! per call the way the value-returning parse_fasta_with_mods does.
60
+ void clear() {
61
+ element_first_index.clear();
62
+ count.clear();
63
+ }
64
+ };
65
+
66
+ //! A composition expanded to the flat, per-isotope form the generic
67
+ //! Iso(dimNumber, isotopeNumbers, atomCounts, masses, probs) constructor
68
+ //! takes: one isotopeNumbers/atomCounts entry per element of the
69
+ //! composition, and one isotope_masses/isotope_probabilities entry per
70
+ //! isotope, element-major, in the composition's own order. Exactly the
71
+ //! layout the C ABI's setupIso expects, since that constructor is what it
72
+ //! forwards to.
73
+ struct ExpandedComposition {
74
+ std::vector<int> isotopeNumbers;
75
+ std::vector<int> atomCounts;
76
+ std::vector<double> isotope_masses;
77
+ std::vector<double> isotope_probabilities;
78
+
79
+ //! Empties all four vectors without releasing their capacity -- same
80
+ //! reuse contract, for the same reason, as ElementComposition::clear
81
+ //! above.
82
+ void clear() {
83
+ isotopeNumbers.clear();
84
+ atomCounts.clear();
85
+ isotope_masses.clear();
86
+ isotope_probabilities.clear();
87
+ }
88
+ };
89
+
90
+ //! Resolves each element of `composition` to its isotopes' masses and
91
+ //! probabilities from element_tables.h, filling a caller-owned `out`
92
+ //! (cleared first).
93
+ //!
94
+ //! The single implementation of that resolution. Everything that turns
95
+ //! element counts into an Iso goes through here: parse_formula (the formula
96
+ //! string path), build_iso_from_composition (the peptide-sequence path), and
97
+ //! cwrapper.h's expandCompositionC -- which exists so that a binding
98
+ //! language need not carry its own copy of the periodic table either.
99
+ //! IsoSpecPy did carry one (IsoParamsFromDict, walking PeriodicTbl's
100
+ //! symbol->isotopes dicts) until that entry point existed.
101
+ //!
102
+ //! Counts are copied through unchanged, negatives included: expanding a
103
+ //! composition is not the same as making a molecule out of one, and the
104
+ //! non-negativity check belongs at the latter -- see ElementComposition's
105
+ //! note above, and build_iso_from_composition, which does check.
106
+ void expand_composition_into(const ElementComposition& composition, ExpandedComposition& out,
107
+ bool use_nominal_masses = false);
108
+
109
+ //! Throws std::invalid_argument if any of `composition`'s counts is
110
+ //! negative, naming the offending elements. The check every path from a
111
+ //! composition to an Iso has to make: a negative atom count reaching Iso's
112
+ //! constructor is not merely a wrong answer but undefined behaviour --
113
+ //! Marginal::computeModeConf() sizes its configuration buffer from the count
114
+ //! and writeInitialConfiguration then walks off the end of it (ASan catches
115
+ //! it as a heap-buffer-overflow).
116
+ void reject_negative_counts(const ElementComposition& composition);
117
+
118
+ //! Resolves a composition (as returned by parse_fasta_with_mods[_full]) into
119
+ //! an Iso via the same generic Iso(dimNumber, isotopeNumbers, atomCounts,
120
+ //! masses, probs) constructor parse_formula uses. Throws
121
+ //! std::invalid_argument if any accumulated element count is negative (a
122
+ //! modification set that removes more atoms of some element than the base
123
+ //! sequence plus other modifications provide).
124
+ Iso build_iso_from_composition(const ElementComposition& composition, bool use_nominal_masses = false);
125
+
126
+ } // namespace IsoSpec