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.
- {isospecpy-2.4.0 → isospecpy-2.5.0}/PKG-INFO +70 -9
- {isospecpy-2.4.0 → isospecpy-2.5.0}/README.md +59 -6
- isospecpy-2.5.0/VERSION +1 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/pyproject.toml +19 -2
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/aligned_ptr.h +18 -2
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/btrd.h +4 -4
- isospecpy-2.5.0/src/IsoSpec++/composition.cpp +85 -0
- isospecpy-2.5.0/src/IsoSpec++/composition.h +126 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/cwrapper.cpp +157 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/cwrapper.h +49 -1
- isospecpy-2.5.0/src/IsoSpec++/element_lookup.cpp +100 -0
- isospecpy-2.5.0/src/IsoSpec++/element_lookup.h +36 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/element_tables.cpp +10 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/element_tables.h +1 -1
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/fasta.h +1 -1
- isospecpy-2.5.0/src/IsoSpec++/fasta_mods.cpp +244 -0
- isospecpy-2.5.0/src/IsoSpec++/fasta_mods.h +70 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/fixedEnvelopes.cpp +65 -23
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/fixedEnvelopes.h +1 -1
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isoMath.cpp +0 -1
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isoMath.h +16 -2
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isoSpec++.cpp +134 -51
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isoSpec++.h +29 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/marginalTrek++.cpp +3 -9
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/marginalTrek++.h +12 -15
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/misc.h +4 -1
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/platform.h +1 -1
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/pod_vector.h +3 -4
- isospecpy-2.5.0/src/IsoSpec++/python-build.cpp +37 -0
- isospecpy-2.5.0/src/IsoSpec++/unimod.cpp +171 -0
- isospecpy-2.5.0/src/IsoSpec++/unimod.h +80 -0
- isospecpy-2.5.0/src/IsoSpec++/unimod_support.h +2169 -0
- isospecpy-2.5.0/src/IsoSpec++/unimod_table_data.h +1027 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/unity-build.cpp +4 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/IsoSpecPy.py +189 -25
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/PeriodicTbl.py +17 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/approximations.py +2 -2
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/isoFFI.py +42 -15
- isospecpy-2.4.0/VERSION +0 -1
- isospecpy-2.4.0/src/IsoSpec++/python-build.cpp +0 -23
- {isospecpy-2.4.0 → isospecpy-2.5.0}/LICENCE +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/skbuild/CMakeLists.txt +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/IsaDispatch.cmake +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/allocator.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/allocator.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/conf.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/dirtyAllocator.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/dirtyAllocator.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/fasta.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_dispatch.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernel_impl.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels_avx.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels_baseline.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels_v3.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/isa_kernels_v4.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/iso_simd.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/misc.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/mman.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/mman.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/operators.cpp +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/operators.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/platform_incl.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpec++/summator.h +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/Advanced.py +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/Distributions.py +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/Formulas.py +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/IsoSpecPyOld.py +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/__init__.py +0 -0
- {isospecpy-2.4.0 → isospecpy-2.5.0}/src/IsoSpecPy/__main__.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
82
|
-
iso = IsoSpecPy.IsoTotalProb(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
60
|
-
iso = IsoSpecPy.IsoTotalProb(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
isospecpy-2.5.0/VERSION
ADDED
|
@@ -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.
|
|
35
|
+
requires-python = ">=3.9"
|
|
27
36
|
classifiers = [
|
|
28
37
|
"Programming Language :: Python :: 3",
|
|
29
|
-
"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",
|
|
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);
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|