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.
- isospecpy-2.5.0/PKG-INFO +220 -0
- isospecpy-2.5.0/README.md +190 -0
- isospecpy-2.5.0/VERSION +1 -0
- isospecpy-2.5.0/pyproject.toml +94 -0
- isospecpy-2.5.0/skbuild/CMakeLists.txt +71 -0
- isospecpy-2.5.0/src/IsoSpec++/IsaDispatch.cmake +78 -0
- isospecpy-2.5.0/src/IsoSpec++/aligned_ptr.h +638 -0
- {isospecpy-2.3.5 → 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.5.0/src/IsoSpec++/cwrapper.cpp +739 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/cwrapper.h +86 -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.3.5 → isospecpy-2.5.0}/src/IsoSpec++/element_tables.cpp +10 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/element_tables.h +1 -1
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/fasta.h +5 -2
- isospecpy-2.5.0/src/IsoSpec++/fasta_mods.cpp +244 -0
- isospecpy-2.5.0/src/IsoSpec++/fasta_mods.h +70 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/fixedEnvelopes.cpp +487 -162
- isospecpy-2.5.0/src/IsoSpec++/fixedEnvelopes.h +418 -0
- isospecpy-2.5.0/src/IsoSpec++/isa_dispatch.cpp +294 -0
- isospecpy-2.5.0/src/IsoSpec++/isa_kernel_impl.h +192 -0
- isospecpy-2.5.0/src/IsoSpec++/isa_kernels.h +172 -0
- isospecpy-2.5.0/src/IsoSpec++/isa_kernels_avx.cpp +57 -0
- isospecpy-2.5.0/src/IsoSpec++/isa_kernels_baseline.cpp +27 -0
- isospecpy-2.5.0/src/IsoSpec++/isa_kernels_v3.cpp +45 -0
- isospecpy-2.5.0/src/IsoSpec++/isa_kernels_v4.cpp +46 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/isoMath.cpp +20 -1
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/isoMath.h +39 -5
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/isoSpec++.cpp +154 -57
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/isoSpec++.h +79 -2
- isospecpy-2.5.0/src/IsoSpec++/iso_simd.h +48 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/marginalTrek++.cpp +46 -33
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/marginalTrek++.h +32 -19
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/misc.cpp +1 -1
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/misc.h +10 -3
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/platform.h +69 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/pod_vector.h +8 -7
- 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.3.5 → isospecpy-2.5.0}/src/IsoSpec++/unity-build.cpp +11 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/IsoSpecPy.py +263 -38
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/PeriodicTbl.py +17 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/approximations.py +2 -2
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/isoFFI.py +51 -15
- isospecpy-2.3.5/PKG-INFO +0 -43
- isospecpy-2.3.5/README.md +0 -21
- isospecpy-2.3.5/pyproject.toml +0 -58
- isospecpy-2.3.5/skbuild/CMakeLists.txt +0 -18
- isospecpy-2.3.5/src/IsoSpec++/cwrapper.cpp +0 -489
- isospecpy-2.3.5/src/IsoSpec++/fixedEnvelopes.h +0 -243
- isospecpy-2.3.5/src/IsoSpec++/python-build.cpp +0 -23
- {isospecpy-2.3.5 → isospecpy-2.5.0}/LICENCE +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/allocator.cpp +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/allocator.h +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/conf.h +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/dirtyAllocator.cpp +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/dirtyAllocator.h +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/fasta.cpp +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/mman.cpp +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/mman.h +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/operators.cpp +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/operators.h +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/platform_incl.h +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpec++/summator.h +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/Advanced.py +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/Distributions.py +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/Formulas.py +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/IsoSpecPyOld.py +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/__init__.py +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/__main__.py +0 -0
- {isospecpy-2.3.5 → isospecpy-2.5.0}/src/IsoSpecPy/confs_passthrough.py +0 -0
isospecpy-2.5.0/PKG-INFO
ADDED
|
@@ -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.
|
isospecpy-2.5.0/VERSION
ADDED
|
@@ -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)
|