IsoSpecPy 2.6.0__tar.gz → 2.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. {isospecpy-2.6.0 → isospecpy-2.6.1}/PKG-INFO +11 -6
  2. {isospecpy-2.6.0 → isospecpy-2.6.1}/README.md +9 -3
  3. isospecpy-2.6.1/VERSION +1 -0
  4. {isospecpy-2.6.0 → isospecpy-2.6.1}/pyproject.toml +1 -2
  5. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/cwrapper.cpp +96 -61
  6. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/cwrapper.h +28 -0
  7. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/fixedEnvelopes.cpp +49 -19
  8. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/fixedEnvelopes.h +14 -0
  9. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isoMath.cpp +135 -33
  10. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isoMath.h +5 -11
  11. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/marginalTrek++.cpp +13 -4
  12. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/marginalTrek++.h +6 -0
  13. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/pod_vector.h +88 -23
  14. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/IsoSpecPy.py +11 -5
  15. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/isoFFI.py +23 -0
  16. isospecpy-2.6.0/VERSION +0 -1
  17. {isospecpy-2.6.0 → isospecpy-2.6.1}/LICENCE +0 -0
  18. {isospecpy-2.6.0 → isospecpy-2.6.1}/skbuild/CMakeLists.txt +0 -0
  19. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/IsaDispatch.cmake +0 -0
  20. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/aligned_ptr.h +0 -0
  21. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/allocator.cpp +0 -0
  22. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/allocator.h +0 -0
  23. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/btrd.h +0 -0
  24. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/composition.cpp +0 -0
  25. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/composition.h +0 -0
  26. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/conf.h +0 -0
  27. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/dirtyAllocator.cpp +0 -0
  28. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/dirtyAllocator.h +0 -0
  29. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/element_lookup.cpp +0 -0
  30. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/element_lookup.h +0 -0
  31. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/element_tables.cpp +0 -0
  32. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/element_tables.h +0 -0
  33. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/fasta.cpp +0 -0
  34. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/fasta.h +0 -0
  35. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/fasta_mods.cpp +0 -0
  36. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/fasta_mods.h +0 -0
  37. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_dispatch.cpp +0 -0
  38. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernel_impl.h +0 -0
  39. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels.h +0 -0
  40. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels_avx.cpp +0 -0
  41. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels_baseline.cpp +0 -0
  42. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels_v3.cpp +0 -0
  43. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isa_kernels_v4.cpp +0 -0
  44. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isoSpec++.cpp +0 -0
  45. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/isoSpec++.h +0 -0
  46. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/iso_simd.h +0 -0
  47. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/misc.cpp +0 -0
  48. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/misc.h +0 -0
  49. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/mman.cpp +0 -0
  50. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/mman.h +0 -0
  51. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/operators.h +0 -0
  52. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/platform.h +0 -0
  53. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/platform_incl.h +0 -0
  54. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/python-build.cpp +0 -0
  55. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/summator.h +0 -0
  56. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/unimod.cpp +0 -0
  57. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/unimod.h +0 -0
  58. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/unimod_support.h +0 -0
  59. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/unimod_table_data.h +0 -0
  60. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpec++/unity-build.cpp +0 -0
  61. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/Advanced.py +0 -0
  62. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/Distributions.py +0 -0
  63. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/Formulas.py +0 -0
  64. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/IsoSpecPyOld.py +0 -0
  65. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/PeriodicTbl.py +0 -0
  66. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/__init__.py +0 -0
  67. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/__main__.py +0 -0
  68. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/approximations.py +0 -0
  69. {isospecpy-2.6.0 → isospecpy-2.6.1}/src/IsoSpecPy/confs_passthrough.py +0 -0
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IsoSpecPy
3
- Version: 2.6.0
3
+ Version: 2.6.1
4
4
  Summary: IsoSpecPy is a Python library for computing isotopic distributions of molecules.
5
5
  Keywords: isotopic distribution,mass spectrometry,chemistry,isotopic envelope,isotopologues
6
6
  Author: Michał Startek, Mateusz Łącki
7
7
  License-Expression: BSD-2-Clause
8
8
  License-File: LICENCE
9
9
  Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.9
11
10
  Classifier: Programming Language :: Python :: 3.10
12
11
  Classifier: Programming Language :: Python :: 3.11
13
12
  Classifier: Programming Language :: Python :: 3.12
@@ -21,7 +20,7 @@ Classifier: Development Status :: 5 - Production/Stable
21
20
  Classifier: Operating System :: OS Independent
22
21
  Project-URL: Homepage, https://github.com/MatteoLacki/IsoSpec
23
22
  Project-URL: Repository, https://github.com/MatteoLacki/IsoSpec
24
- Requires-Python: >=3.9
23
+ Requires-Python: >=3.10
25
24
  Requires-Dist: cffi
26
25
  Provides-Extra: testing
27
26
  Requires-Dist: pytest; extra == "testing"
@@ -42,7 +41,7 @@ IsoSpec is primarily used as a library by mass spectrometry software. It is impl
42
41
  pip install IsoSpecPy
43
42
  ```
44
43
 
45
- The wheel bundles the C++ library — no separate native install required. Compatible with CPython and PyPy on Linux, macOS, Windows, and Cygwin/MinGW. Python ≥ 3.9.
44
+ The wheel bundles the C++ library — no separate native install required. Compatible with CPython and PyPy on Linux, macOS, Windows, and Cygwin/MinGW. Python ≥ 3.10.
46
45
 
47
46
  To build from source: `pip install .` from a checkout. A C++20 compiler is required.
48
47
 
@@ -56,12 +55,18 @@ From source: `cd src && R CMD build IsoSpecR && R CMD INSTALL IsoSpecR_*.tar.gz`
56
55
 
57
56
  ### Rust
58
57
 
58
+ ```bash
59
+ cargo add isospec
60
+ ```
61
+
62
+ or, in `Cargo.toml`:
63
+
59
64
  ```toml
60
65
  [dependencies]
61
- isospec = "0.1"
66
+ isospec = "0.2"
62
67
  ```
63
68
 
64
- The build script compiles the bundled C++ library; a C++20 compiler is all that is needed. See `rust/README.md`.
69
+ The crate ([crates.io](https://crates.io/crates/isospec), API documentation on [docs.rs](https://docs.rs/isospec)) carries the C++ sources and compiles them in its build script; a C++20 compiler is all that is needed. See `rust/README.md`.
65
70
 
66
71
  ### C++ library
67
72
 
@@ -12,7 +12,7 @@ IsoSpec is primarily used as a library by mass spectrometry software. It is impl
12
12
  pip install IsoSpecPy
13
13
  ```
14
14
 
15
- The wheel bundles the C++ library — no separate native install required. Compatible with CPython and PyPy on Linux, macOS, Windows, and Cygwin/MinGW. Python ≥ 3.9.
15
+ The wheel bundles the C++ library — no separate native install required. Compatible with CPython and PyPy on Linux, macOS, Windows, and Cygwin/MinGW. Python ≥ 3.10.
16
16
 
17
17
  To build from source: `pip install .` from a checkout. A C++20 compiler is required.
18
18
 
@@ -26,12 +26,18 @@ From source: `cd src && R CMD build IsoSpecR && R CMD INSTALL IsoSpecR_*.tar.gz`
26
26
 
27
27
  ### Rust
28
28
 
29
+ ```bash
30
+ cargo add isospec
31
+ ```
32
+
33
+ or, in `Cargo.toml`:
34
+
29
35
  ```toml
30
36
  [dependencies]
31
- isospec = "0.1"
37
+ isospec = "0.2"
32
38
  ```
33
39
 
34
- The build script compiles the bundled C++ library; a C++20 compiler is all that is needed. See `rust/README.md`.
40
+ The crate ([crates.io](https://crates.io/crates/isospec), API documentation on [docs.rs](https://docs.rs/isospec)) carries the C++ sources and compiles them in its build script; a C++20 compiler is all that is needed. See `rust/README.md`.
35
41
 
36
42
  ### C++ library
37
43
 
@@ -0,0 +1 @@
1
+ 2.6.1
@@ -32,10 +32,9 @@ license = "BSD-2-Clause"
32
32
  license-files = ["LICENCE"]
33
33
  authors = [{name = "Michał Startek"}, {name = "Mateusz Łącki"}]
34
34
  readme = "README.md"
35
- requires-python = ">=3.9"
35
+ requires-python = ">=3.10"
36
36
  classifiers = [
37
37
  "Programming Language :: Python :: 3",
38
- "Programming Language :: Python :: 3.9",
39
38
  "Programming Language :: Python :: 3.10",
40
39
  "Programming Language :: Python :: 3.11",
41
40
  "Programming Language :: Python :: 3.12",
@@ -341,87 +341,122 @@ ISOSPEC_C_FN_CODES(IsoStochasticGenerator)
341
341
 
342
342
  // ______________________________________________________ FixedEnvelopes
343
343
 
344
- void* setupThresholdFixedEnvelope(void* iso,
345
- double threshold,
346
- bool absolute,
347
- bool get_confs)
344
+ // Each envelope constructor comes in two forms. The plain one leaves the
345
+ // caller's Iso alone, so it fully copies it (marginal tables and all) for the
346
+ // generator inside to own. The *Consuming one takes the Iso over: it moves it
347
+ // into the generator and deletes the emptied handle before returning, whether
348
+ // or not it succeeded. Skipping the copy saves 0.65 us of the 4.5 us a
349
+ // 30-residue peptide takes in Binned at 1 Da and target 0.99 (x86-64-v3).
350
+ // The helpers are templates and lambdas, so they sit outside extern "C".
351
+ } // extern "C"
352
+
353
+ namespace
354
+ {
355
+ template<typename F>
356
+ void* envelope_from_iso(void* iso, bool consume, F&& make) noexcept
348
357
  {
358
+ // Owned before c_guard runs anything, so that the handle goes on every path.
359
+ const std::unique_ptr<Iso> owned(consume ? reinterpret_cast<Iso*>(iso) : nullptr);
349
360
  return c_guard([&]() -> void*
350
361
  { // Use copy elision to allocate on heap with named constructor
351
- return new FixedEnvelope(
352
- FixedEnvelope::FromThreshold(Iso(*reinterpret_cast<const Iso*>(iso), true),
353
- threshold,
354
- absolute,
355
- get_confs));
362
+ if(consume)
363
+ return new FixedEnvelope(make(std::move(*owned)));
364
+ return new FixedEnvelope(make(Iso(*reinterpret_cast<const Iso*>(iso), true)));
356
365
  });
357
366
  }
358
367
 
359
- void* setupTotalProbFixedEnvelope(void* iso,
360
- double target_coverage,
361
- bool optimize,
362
- bool get_confs)
368
+ void* threshold_envelope(void* iso, bool consume, double threshold, bool absolute, bool get_confs) noexcept
363
369
  {
364
- return c_guard([&]() -> void*
365
- { // Use copy elision to allocate on heap with named constructor
366
- return new FixedEnvelope(
367
- FixedEnvelope::FromTotalProb(Iso(*reinterpret_cast<const Iso*>(iso), true),
368
- target_coverage,
369
- optimize,
370
- get_confs));
371
- });
370
+ return envelope_from_iso(iso, consume, [&](Iso&& i)
371
+ { return FixedEnvelope::FromThreshold(std::move(i), threshold, absolute, get_confs); });
372
372
  }
373
373
 
374
- void* setupStochasticFixedEnvelope(void* iso,
375
- size_t no_molecules,
376
- double precision,
377
- double beta_bias,
378
- bool get_confs)
374
+ void* total_prob_envelope(void* iso, bool consume, double target_coverage, bool optimize, bool get_confs) noexcept
379
375
  {
380
- return c_guard([&]() -> void*
381
- { // Use copy elision to allocate on heap with named constructor
382
- return new FixedEnvelope(
383
- FixedEnvelope::FromStochastic(Iso(*reinterpret_cast<const Iso*>(iso), true),
384
- no_molecules,
385
- precision,
386
- beta_bias,
387
- get_confs));
388
- });
376
+ return envelope_from_iso(iso, consume, [&](Iso&& i)
377
+ { return FixedEnvelope::FromTotalProb(std::move(i), target_coverage, optimize, get_confs); });
389
378
  }
390
379
 
391
- void* setupStochasticFixedEnvelopeSeeded(void* iso,
392
- size_t no_molecules,
393
- double precision,
394
- double beta_bias,
395
- bool get_confs,
396
- uint64_t seed)
380
+ void* stochastic_envelope(void* iso, bool consume, size_t no_molecules, double precision, double beta_bias,
381
+ bool get_confs) noexcept
397
382
  {
398
- return c_guard([&]() -> void*
383
+ return envelope_from_iso(iso, consume, [&](Iso&& i)
384
+ { return FixedEnvelope::FromStochastic(std::move(i), no_molecules, precision, beta_bias, get_confs); });
385
+ }
386
+
387
+ void* stochastic_envelope_seeded(void* iso, bool consume, size_t no_molecules, double precision,
388
+ double beta_bias, bool get_confs, uint64_t seed) noexcept
389
+ {
390
+ return envelope_from_iso(iso, consume, [&](Iso&& i)
399
391
  {
400
392
  std::mt19937 rng = seeded_rng(seed);
401
- return new FixedEnvelope(
402
- FixedEnvelope::FromStochastic(Iso(*reinterpret_cast<const Iso*>(iso), true),
403
- no_molecules,
404
- precision,
405
- beta_bias,
406
- get_confs,
407
- rng));
393
+ return FixedEnvelope::FromStochastic(std::move(i), no_molecules, precision, beta_bias, get_confs, rng);
408
394
  });
409
395
  }
410
396
 
397
+ void* binned_envelope(void* iso, bool consume, double target_total_prob, double bin_width,
398
+ double bin_middle) noexcept
399
+ {
400
+ return envelope_from_iso(iso, consume, [&](Iso&& i)
401
+ { return FixedEnvelope::Binned(std::move(i), target_total_prob, bin_width, bin_middle); });
402
+ }
403
+ } // namespace
411
404
 
412
- void* setupBinnedFixedEnvelope(void* iso,
413
- double target_total_prob,
414
- double bin_width,
415
- double bin_middle)
405
+ extern "C"
416
406
  {
417
- return c_guard([&]() -> void*
418
- { // Use copy elision to allocate on heap with named constructor
419
- return new FixedEnvelope(
420
- FixedEnvelope::Binned(Iso(*reinterpret_cast<const Iso*>(iso), true),
421
- target_total_prob,
422
- bin_width,
423
- bin_middle));
424
- });
407
+
408
+ void* setupThresholdFixedEnvelope(void* iso, double threshold, bool absolute, bool get_confs)
409
+ {
410
+ return threshold_envelope(iso, false, threshold, absolute, get_confs);
411
+ }
412
+
413
+ void* setupThresholdFixedEnvelopeConsuming(void* iso, double threshold, bool absolute, bool get_confs)
414
+ {
415
+ return threshold_envelope(iso, true, threshold, absolute, get_confs);
416
+ }
417
+
418
+ void* setupTotalProbFixedEnvelope(void* iso, double target_coverage, bool optimize, bool get_confs)
419
+ {
420
+ return total_prob_envelope(iso, false, target_coverage, optimize, get_confs);
421
+ }
422
+
423
+ void* setupTotalProbFixedEnvelopeConsuming(void* iso, double target_coverage, bool optimize, bool get_confs)
424
+ {
425
+ return total_prob_envelope(iso, true, target_coverage, optimize, get_confs);
426
+ }
427
+
428
+ void* setupStochasticFixedEnvelope(void* iso, size_t no_molecules, double precision, double beta_bias,
429
+ bool get_confs)
430
+ {
431
+ return stochastic_envelope(iso, false, no_molecules, precision, beta_bias, get_confs);
432
+ }
433
+
434
+ void* setupStochasticFixedEnvelopeConsuming(void* iso, size_t no_molecules, double precision, double beta_bias,
435
+ bool get_confs)
436
+ {
437
+ return stochastic_envelope(iso, true, no_molecules, precision, beta_bias, get_confs);
438
+ }
439
+
440
+ void* setupStochasticFixedEnvelopeSeeded(void* iso, size_t no_molecules, double precision, double beta_bias,
441
+ bool get_confs, uint64_t seed)
442
+ {
443
+ return stochastic_envelope_seeded(iso, false, no_molecules, precision, beta_bias, get_confs, seed);
444
+ }
445
+
446
+ void* setupStochasticFixedEnvelopeSeededConsuming(void* iso, size_t no_molecules, double precision,
447
+ double beta_bias, bool get_confs, uint64_t seed)
448
+ {
449
+ return stochastic_envelope_seeded(iso, true, no_molecules, precision, beta_bias, get_confs, seed);
450
+ }
451
+
452
+ void* setupBinnedFixedEnvelope(void* iso, double target_total_prob, double bin_width, double bin_middle)
453
+ {
454
+ return binned_envelope(iso, false, target_total_prob, bin_width, bin_middle);
455
+ }
456
+
457
+ void* setupBinnedFixedEnvelopeConsuming(void* iso, double target_total_prob, double bin_width, double bin_middle)
458
+ {
459
+ return binned_envelope(iso, true, target_total_prob, bin_width, bin_middle);
425
460
  }
426
461
 
427
462
  void* setupFixedEnvelope(double* masses, double* probs, size_t size, bool mass_sorted, bool prob_sorted, double total_prob)
@@ -130,21 +130,39 @@ ISOSPEC_C_API void* setupIsoStochasticGeneratorSeeded(void* iso,
130
130
  double beta_bias,
131
131
  uint64_t seed);
132
132
 
133
+ /* Envelopes from an Iso. Each comes in two forms: the plain one leaves `iso`
134
+ untouched (it copies it, marginal tables and all), the *Consuming one takes
135
+ `iso` over and saves that copy (0.65 us of a 30-residue peptide's 4.5 us
136
+ binned at 1 Da). A *Consuming call deletes `iso` before returning, whether
137
+ or not it succeeds: never use or deleteIso() it afterwards. */
133
138
  ISOSPEC_C_API void* setupThresholdFixedEnvelope(void* iso,
134
139
  double threshold,
135
140
  bool absolute,
136
141
  bool get_confs);
142
+ ISOSPEC_C_API void* setupThresholdFixedEnvelopeConsuming(void* iso,
143
+ double threshold,
144
+ bool absolute,
145
+ bool get_confs);
137
146
 
138
147
  ISOSPEC_C_API void* setupTotalProbFixedEnvelope(void* iso,
139
148
  double taget_coverage,
140
149
  bool optimize,
141
150
  bool get_confs);
151
+ ISOSPEC_C_API void* setupTotalProbFixedEnvelopeConsuming(void* iso,
152
+ double taget_coverage,
153
+ bool optimize,
154
+ bool get_confs);
142
155
 
143
156
  ISOSPEC_C_API void* setupStochasticFixedEnvelope(void* iso,
144
157
  size_t no_molecules,
145
158
  double precision,
146
159
  double beta_bias,
147
160
  bool get_confs);
161
+ ISOSPEC_C_API void* setupStochasticFixedEnvelopeConsuming(void* iso,
162
+ size_t no_molecules,
163
+ double precision,
164
+ double beta_bias,
165
+ bool get_confs);
148
166
 
149
167
  ISOSPEC_C_API void* setupStochasticFixedEnvelopeSeeded(void* iso,
150
168
  size_t no_molecules,
@@ -152,11 +170,21 @@ ISOSPEC_C_API void* setupStochasticFixedEnvelopeSeeded(void* iso,
152
170
  double beta_bias,
153
171
  bool get_confs,
154
172
  uint64_t seed);
173
+ ISOSPEC_C_API void* setupStochasticFixedEnvelopeSeededConsuming(void* iso,
174
+ size_t no_molecules,
175
+ double precision,
176
+ double beta_bias,
177
+ bool get_confs,
178
+ uint64_t seed);
155
179
 
156
180
  ISOSPEC_C_API void* setupBinnedFixedEnvelope(void* iso,
157
181
  double target_total_prob,
158
182
  double bin_width,
159
183
  double bin_middle);
184
+ ISOSPEC_C_API void* setupBinnedFixedEnvelopeConsuming(void* iso,
185
+ double target_total_prob,
186
+ double bin_width,
187
+ double bin_middle);
160
188
 
161
189
  ISOSPEC_C_API void freeReleasedArray(void* array);
162
190
 
@@ -1303,8 +1303,15 @@ template<bool tgetConfs> void FixedEnvelope::total_prob_init(Iso&& iso, double t
1303
1303
  // recomputes land inside the smaller buffer rather than past its end.
1304
1304
  this->_confs_no = end;
1305
1305
 
1306
- if(end <= current_size/2)
1307
- // Overhead in memory of 2x or more, shrink to fit
1306
+ // Overhead in memory of 2x or more: shrink to fit, but only buffers big
1307
+ // enough for the waste to matter. Below the VM backend's threshold the
1308
+ // shrink is a fresh aligned_alloc plus a copy of every survivor, paid on
1309
+ // every call for at most a few hundred KiB of slack (C254H377N65O75S6 to
1310
+ // p = 0.9999: 25 KiB copied out of 64 KiB buffers, each call).
1311
+ size_t bytes_per_conf = 2 * sizeof(double);
1312
+ constexpr_if(tgetConfs)
1313
+ bytes_per_conf += this->allDimSizeofInt;
1314
+ if(end <= current_size/2 && current_size * bytes_per_conf >= aligned_ptr_detail::vm_backend_threshold())
1308
1315
  this->template reallocate_memory<tgetConfs>(end);
1309
1316
  }
1310
1317
 
@@ -1587,26 +1594,49 @@ FixedEnvelope FixedEnvelope::Binned(Iso&& iso, double target_total_prob, double
1587
1594
  throw std::logic_error("Binned: the shifted bin grid starts below its first slot");
1588
1595
  const size_t no_bins = static_cast<size_t>(last_slot) + 2;
1589
1596
 
1590
- double* acc;
1591
- # if ISOSPEC_GOT_MMAN
1592
- acc = reinterpret_cast<double*>(mmap(nullptr, sizeof(double)*no_bins, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
1593
- if(acc == MAP_FAILED)
1594
- throw std::bad_alloc();
1597
+ // The accumulator spans the whole mass range, of which a large molecule
1598
+ // populates only a small part: mapped anonymously, the rest costs address
1599
+ // space only. A small one's range is mostly populated, and below
1600
+ // ISOSPEC_BINNED_MMAP_MIN_BYTES (see fixedEnvelopes.h) calloc's zeroing is
1601
+ // cheaper than mmap's syscalls and page faults. Above it calloc is no
1602
+ // substitute: glibc serves blocks up to its adaptive mmap threshold (as much
1603
+ // as 32 MiB) from the heap and memsets all of them.
1604
+ const size_t acc_bytes = sizeof(double)*no_bins;
1605
+ #if ISOSPEC_GOT_MMAN
1606
+ const bool mapped = acc_bytes >= ISOSPEC_BINNED_MMAP_MIN_BYTES;
1595
1607
  #else
1596
- // Platforms without mmap use a zero-initialized heap accumulator.
1597
- acc = reinterpret_cast<double*>(calloc(no_bins, sizeof(double)));
1598
- if(acc == nullptr)
1599
- throw std::bad_alloc();
1608
+ constexpr bool mapped = false;
1609
+ #endif
1610
+
1611
+ double* acc;
1612
+ if(mapped)
1613
+ {
1614
+ #if ISOSPEC_GOT_MMAN
1615
+ acc = reinterpret_cast<double*>(mmap(nullptr, acc_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
1616
+ if(acc == MAP_FAILED)
1617
+ throw std::bad_alloc();
1600
1618
  #endif
1601
- // Keep the original mapping alive through generator construction and output
1602
- // growth, both of which can throw. acc is offset below for absolute indexing;
1603
- // the owner must always retain the actual base address for cleanup.
1604
- const auto release_acc = [no_bins](double* base) noexcept {
1619
+ }
1620
+ else
1621
+ {
1622
+ acc = reinterpret_cast<double*>(calloc(no_bins, sizeof(double)));
1623
+ if(acc == nullptr)
1624
+ throw std::bad_alloc();
1625
+ }
1626
+ // Keep the accumulator alive through generator construction and output
1627
+ // growth, both of which can throw.
1628
+ const auto release_acc = [acc_bytes, mapped](double* base) noexcept {
1605
1629
  #if ISOSPEC_GOT_MMAN
1606
- munmap(base, sizeof(double)*no_bins);
1630
+ if(mapped)
1631
+ {
1632
+ munmap(base, acc_bytes);
1633
+ return;
1634
+ }
1607
1635
  #else
1608
- free(base);
1636
+ (void)acc_bytes;
1637
+ (void)mapped;
1609
1638
  #endif
1639
+ free(base);
1610
1640
  };
1611
1641
  std::unique_ptr<double, decltype(release_acc)> acc_owner(acc, release_acc);
1612
1642
 
@@ -1622,8 +1652,8 @@ FixedEnvelope FixedEnvelope::Binned(Iso&& iso, double target_total_prob, double
1622
1652
  # ifndef ISOSPEC_BINNED_HUGEPAGE_MIN_BYTES
1623
1653
  # define ISOSPEC_BINNED_HUGEPAGE_MIN_BYTES (size_t(64) << 20) // 64 MiB
1624
1654
  # endif
1625
- if(sizeof(double)*no_bins >= ISOSPEC_BINNED_HUGEPAGE_MIN_BYTES)
1626
- madvise(acc, sizeof(double)*no_bins, MADV_HUGEPAGE);
1655
+ if(mapped && acc_bytes >= ISOSPEC_BINNED_HUGEPAGE_MIN_BYTES)
1656
+ madvise(acc, acc_bytes, MADV_HUGEPAGE);
1627
1657
  #endif
1628
1658
 
1629
1659
  std::ptrdiff_t nonzero_slot = 0;
@@ -43,6 +43,20 @@
43
43
  # define ISOSPEC_BINNED_LAYER_MAXSTEP (-3.0)
44
44
  #endif
45
45
 
46
+ // Binned() takes its bin accumulator from calloc below this size, and maps it
47
+ // anonymously at and above it. The mmap path costs a fixed 2-25 us (map, page
48
+ // faults, unmap: 2.5 on Alder Lake, 2 on M1 macOS, 8 under WSL2, 25 in a KVM
49
+ // guest), which calloc saves; calloc in turn zeroes the whole range, which the
50
+ // mmap path never touches beyond the populated bins. Measured as calloc/mmap
51
+ // time: glucose at 0.001 Da (241 KB) 0.43 / 0.21 / 0.63 / 0.43 on those four,
52
+ // a 1 kDa peptide at 0.01 Da (157 KB) 0.69 / 0.30 / 0.80 / 0.53. Where calloc
53
+ // starts to lose depends on how much of the range is populated: on Alder Lake,
54
+ // insulin breaks even at ~300 KB, a 1 kDa peptide at ~1 MB; the other hosts
55
+ // tolerate 1-2 MB. 256 KiB never loses on the primary target.
56
+ #ifndef ISOSPEC_BINNED_MMAP_MIN_BYTES
57
+ # define ISOSPEC_BINNED_MMAP_MIN_BYTES (std::size_t(256) << 10)
58
+ #endif
59
+
46
60
  // The same knob for total_prob_init, which can afford a wider step than Binned:
47
61
  // it trims afterwards, so overshooting the target costs only the work of
48
62
  // generating what gets thrown away.
@@ -10,7 +10,10 @@
10
10
  // NOLINT(legal/copyright)
11
11
 
12
12
 
13
+ #include <algorithm>
13
14
  #include <cmath>
15
+ #include <cstdint>
16
+ #include <limits>
14
17
  #include <cstdlib>
15
18
  #include <new>
16
19
  #include "isoMath.h"
@@ -180,51 +183,150 @@ double NormalPDF(double x, double mean, double stdev)
180
183
 
181
184
  const double sqrt_pi = 1.772453850905516027298167483341145182798;
182
185
 
183
- double LowerIncompleteGamma2(int a, double x)
186
+ namespace
184
187
  {
185
- double base;
186
- double exp_minus_x = exp(-x);
187
- double current_s;
188
- if(a % 2 == 0)
189
- {
190
- base = 1 - exp_minus_x;
191
- current_s = 1.0;
192
- a--;
193
- }
194
- else
188
+ // The regularised incomplete gamma functions at a = k/2, k >= 1: their values
189
+ // P(a, s) and Q(a, s) = 1 - P(a, s), each computed directly so that neither
190
+ // loses its relative precision to cancellation in its tail, and the density
191
+ // dP/ds = s^(a-1) e^(-s) / Gamma(a), which every Halley step needs anyway.
192
+ // `lgamma_a` is lnGamma(a). Region by region, as Boost's gamma_p does it:
193
+ // s < a+1: P = s^a e^-s / Gamma(a+1) * sum_n s^n / ((a+1)...(a+n)),
194
+ // whose prefix is density * s / a and whose terms fall at once;
195
+ // s >= a+1: Q's finite sum, for k = 2m and k = 2m+1
196
+ // Q(m, s) = e^-s * sum_{j<m} s^j / j!
197
+ // Q(m+1/2, s) = erfc(sqrt(s)) + e^-s * sum_{j<m} s^(j+1/2) / Gamma(j+3/2),
198
+ // summed downward from its last term, which is the density:
199
+ // each term is the next one times (j+a-m)/s < 1, so nothing
200
+ // overflows whatever k, and the sum stops once the terms are
201
+ // below rounding.
202
+ struct GammaHalf
203
+ {
204
+ double P, Q, density;
205
+ };
206
+
207
+ GammaHalf gamma_half(int k, double s, double lgamma_a)
208
+ {
209
+ const double a = 0.5 * k;
210
+ const double density = exp((a - 1.0) * log(s) - s - lgamma_a);
211
+
212
+ if(s < a + 1.0)
195
213
  {
196
- base = sqrt_pi * erf(sqrt(x));
197
- current_s = 0.5;
214
+ double t = 1.0, series = 1.0;
215
+ for(double d = a + 1.0; t > series * 0x1p-54; d += 1.0)
216
+ {
217
+ t *= s / d;
218
+ series += t;
219
+ }
220
+ const double P = density * (s / a) * series;
221
+ return {P, 1.0 - P, density};
198
222
  }
199
223
 
200
- a = a/2;
201
- for(; a; a--)
224
+ double sum = 0.0, t = density;
225
+ for(int i = 0; i < k / 2 && t > sum * 0x1p-54; i++)
202
226
  {
203
- base = base * current_s - pow(x, current_s) * exp_minus_x;
204
- current_s += 1.0;
227
+ sum += t;
228
+ t *= (a - 1.0 - i) / s;
205
229
  }
206
-
207
- return base;
230
+ const double Q = (k % 2 == 0) ? sum : erfc(sqrt(s)) + sum;
231
+ return {1.0 - Q, Q, density};
208
232
  }
233
+ } // namespace
209
234
 
210
- double InverseLowerIncompleteGamma2(int a, double x)
235
+ double InverseChiSquareCDF2(int k, double x)
211
236
  {
212
- double l = 0.0;
213
- double p = tgamma(a);
214
- double s;
215
-
216
- do {
217
- s = (l+p) / 2.0;
218
- double v = LowerIncompleteGamma2(a, s);
219
- if (x < v)
220
- p = s;
237
+ // q = 2s with P(k/2, s) = x. The initial guess is the larger of
238
+ // * Wilson and Hilferty (1931): (q/k)^(1/3) is close to normal with mean
239
+ // 1 - 2/(9k) and variance 2/(9k), so q ~ k (1 - 2/(9k) + z sqrt(2/(9k)))^3
240
+ // with z the normal quantile of x -- within a fraction of a percent
241
+ // away from the far tails;
242
+ // * the leading term of P's series, P(a, s) <= s^a / Gamma(a+1), whose
243
+ // inverse s = (x Gamma(a+1))^(1/a) is a lower bound on the root
244
+ // everywhere and accurate in the lower tail, where Wilson-Hilferty's
245
+ // cube can go negative.
246
+ // Then Halley's iteration (as in DiDonato and Morris 1986, and Boost's
247
+ // gamma_p_inv): f'' / f' = (a-1)/s - 1 for f = P(a, s) - x, so each step
248
+ // costs one evaluation of gamma_half, and from this guess two (rarely
249
+ // more; at most 10 in the far tails of k < 10) reach full precision. The callers need about 0.1% (the quantile
250
+ // only orders marginals by estimated size), but the steps past that cost
251
+ // little and make the function testable against reference values. No
252
+ // tgamma or pow: one exp and one log per step, lnGamma from lgamma_half.
253
+ //
254
+ // Defined everywhere, so that the sort keys built from it are never NaN:
255
+ // 0 for k <= 0 (no freedom) and for x <= 0 or NaN; x >= 1, whose quantile
256
+ // is infinite, is answered at the largest double below 1.
257
+ if(k <= 0 || !(x > 0.0))
258
+ return 0.0;
259
+ x = (std::min)(x, 1.0 - 0x1p-53);
260
+ if(k == 2)
261
+ return -2.0 * log1p(-x); // exponential with mean 2: exact
262
+
263
+ const double a = 0.5 * k;
264
+ // lnGamma(1/2) = ln(sqrt(pi)) is below lgamma_half's domain.
265
+ const double lgamma_a = k == 1 ? 0.5723649429247001 : lgamma_half(k);
266
+
267
+ double s_wh;
268
+ if(k == 1 && x >= 0.5)
269
+ {
270
+ // The square of a standard normal: q = z^2 for z the normal quantile
271
+ // at (1+x)/2, taken from its tail (1-x)/2, which keeps its precision;
272
+ // NormalCDFInverse's 4.5e-4 makes it a guess. Below the median the
273
+ // series bound is the better one.
274
+ const double z = NormalCDFInverse(0.5 * (1.0 - x));
275
+ s_wh = 0.5 * z * z;
276
+ }
277
+ else
278
+ {
279
+ const double v = 2.0 / (9.0 * k);
280
+ const double h = 1.0 - v + NormalCDFInverse(x) * sqrt(v);
281
+ s_wh = h > 0.0 ? a * h * h * h : 0.0;
282
+ }
283
+ const double lower_bound = exp((log(x) + lgamma_half(static_cast<std::int64_t>(k) + 2)) / a);
284
+ double s = (std::max)(s_wh, lower_bound);
285
+ if(!(s > 0.0))
286
+ return 0.0; // below the smallest double
287
+
288
+ // Solved for P below the median and for Q above it, in whichever tail
289
+ // keeps its relative precision; both are increasing in s.
290
+ const bool upper = x > 0.5;
291
+ const double target = upper ? 1.0 - x : x;
292
+ double lo = lower_bound, hi = std::numeric_limits<double>::infinity();
293
+ for(int iteration = 0; iteration < 100; iteration++)
294
+ {
295
+ const GammaHalf g = gamma_half(k, s, lgamma_a);
296
+ const double f = upper ? target - g.Q : g.P - target;
297
+ if(f == 0.0 || !(g.density > 0.0))
298
+ break;
299
+ if(f < 0.0)
300
+ lo = s;
221
301
  else
222
- l = s;
223
- } while((p-l)*1000.0 > p);
224
-
225
- return s;
302
+ hi = s;
303
+ const double newton = f / g.density;
304
+ const double curvature = 1.0 - 0.5 * newton * ((a - 1.0) / s - 1.0);
305
+ const double halley = curvature > 0.5 ? newton / curvature : newton;
306
+ // Converged once the step is below 2^-20 relative: Halley's error
307
+ // after a step of d is of order d^3, so taking that step puts s
308
+ // within rounding of the root (checked over k = 1..1000 and x from
309
+ // 1e-15 to 1 - 1e-15 against SciPy: worst 4e-14, one evaluation
310
+ // fewer than a 2^-40 test, the same answers). Checked before the
311
+ // bracket, which a step below one ulp of s cannot enter.
312
+ if(fabs(halley) <= s * 0x1p-20)
313
+ {
314
+ s -= halley;
315
+ break;
316
+ }
317
+ // A step leaving the bracket falls back to Newton's, then to
318
+ // bisection (or doubling, while the bracket has no upper end).
319
+ double next = s - halley;
320
+ if(!(next > lo && next < hi))
321
+ next = s - newton;
322
+ if(!(next > lo && next < hi))
323
+ next = std::isfinite(hi) ? 0.5 * (lo + hi) : 2.0 * (std::max)(s, lo);
324
+ s = next;
325
+ }
326
+ return 2.0 * s;
226
327
  }
227
328
 
329
+
228
330
  thread_local std::mt19937 random_gen(std::random_device{}());
229
331
 
230
332
  size_t rdvariate_binom(size_t tries, double succ_prob, std::mt19937& rgen)
@@ -110,17 +110,11 @@ double NormalCDFInverse(double p, double mean, double stdev);
110
110
  double NormalCDF(double x, double mean, double stdev);
111
111
  double NormalPDF(double x, double mean = 0.0, double stdev = 1.0);
112
112
 
113
- // Returns lower incomplete gamma function of a/2, x, where a is int and > 0.
114
- double LowerIncompleteGamma2(int a, double x);
115
-
116
- // Returns y such that LowerIncompleteGamma2(a, y) == x. Approximately.
117
- double InverseLowerIncompleteGamma2(int a, double x);
118
-
119
- // Computes the inverse Cumulative Distribution Funcion of the Chi-Square distribution with k degrees of freedom
120
- inline double InverseChiSquareCDF2(int k, double x)
121
- {
122
- return InverseLowerIncompleteGamma2(k, x*tgamma(static_cast<double>(k)/2.0)) * 2.0;
123
- }
113
+ // The quantile function (inverse CDF) of the chi-square distribution with k
114
+ // degrees of freedom at x: q with P(k/2, q/2) = x, to about 1e-12 relative
115
+ // (tested against SciPy). 0 for k <= 0 and for x <= 0 or NaN; finite for
116
+ // x >= 1. Thread-safe: no lgamma.
117
+ double InverseChiSquareCDF2(int k, double x);
124
118
 
125
119
  extern thread_local std::mt19937 random_gen;
126
120
 
@@ -736,8 +736,12 @@ bool LayeredMarginal::extend(double new_threshold, bool do_sort)
736
736
  for(size_t ii = 0; ii < ISOSPEC_LPROB_GUARDIANS; ii++)
737
737
  lProbs.pop_back(); // Remove the trailing -inf guardians; restored at the end
738
738
 
739
- pod_vector<Conf> new_fringe;
740
- pod_vector<double> new_fringe_unn_lprobs;
739
+ // Empty already (swapped with the drained fringe at the end of the last
740
+ // call), unless that call threw partway.
741
+ pod_vector<Conf>& new_fringe = next_fringe;
742
+ pod_vector<double>& new_fringe_unn_lprobs = next_fringe_unn_lprobs;
743
+ new_fringe.clear_keep_capacity();
744
+ new_fringe_unn_lprobs.clear_keep_capacity();
741
745
 
742
746
  while(!fringe.empty())
743
747
  {
@@ -809,10 +813,15 @@ bool LayeredMarginal::extend(double new_threshold, bool do_sort)
809
813
  size_t to_sort_size = configurations.size() - probs.size();
810
814
  if(to_sort_size > 0)
811
815
  {
812
- std::unique_ptr<size_t[]> order_arr(get_inverse_order(lProbs.data()+1+probs.size(), to_sort_size));
816
+ // get_inverse_order(), into the reused sort_order.
813
817
  double* P = lProbs.data()+1+probs.size();
814
818
  Conf* C = configurations.data()+probs.size();
815
- size_t* O = order_arr.get();
819
+ sort_order.clear_keep_capacity();
820
+ sort_order.resize(to_sort_size);
821
+ size_t* O = sort_order.data();
822
+ for(size_t ii = 0; ii < to_sort_size; ii++)
823
+ O[ii] = ii;
824
+ std::sort(O, O + to_sort_size, [P](size_t i, size_t j) { return P[i] > P[j]; });
816
825
  impose_order(O, to_sort_size, P, C);
817
826
  }
818
827
  }
@@ -402,6 +402,12 @@ class LayeredMarginal : public Marginal
402
402
  pod_vector<Conf> configurations;
403
403
  pod_vector<Conf> fringe;
404
404
  pod_vector<double> fringe_unn_lprobs;
405
+ // extend()'s scratch, kept between calls so that a layer allocates
406
+ // nothing once they have grown: the fringe below the new threshold (then
407
+ // swapped with fringe), and the sort order of the new configurations.
408
+ pod_vector<Conf> next_fringe;
409
+ pod_vector<double> next_fringe_unn_lprobs;
410
+ pod_vector<size_t> sort_order;
405
411
  Allocator<int> allocator;
406
412
  pod_vector<double> lProbs;
407
413
  pod_vector<double> probs;
@@ -19,6 +19,7 @@
19
19
  #include <type_traits>
20
20
  #include <cstdlib>
21
21
  #include <cstddef>
22
+ #include <cstring>
22
23
  #include <utility>
23
24
  #include <new>
24
25
  #include <algorithm>
@@ -29,19 +30,66 @@
29
30
 
30
31
  template<typename T> class unsafe_pod_vector;
31
32
 
33
+ // The first inline_capacity elements live inside the object itself; the
34
+ // store moves to the heap only when the vector outgrows them, and stays there.
35
+ // Most vectors in the library (a marginal's configurations, probabilities and
36
+ // masses, a layer's fringe) never get past a handful of elements, so they
37
+ // cost no allocation at all. The flip side: unlike a plain heap vector, a move
38
+ // or swap of a vector that is still inline copies its elements, so pointers
39
+ // into data() do not survive either. (unsafe_pod_vector, below, has no inline
40
+ // storage: it must stay trivially copyable, and a self-pointer would not.)
32
41
  template<typename T> class pod_vector
33
42
  {
43
+ public:
44
+ static constexpr size_t inline_capacity = 16;
45
+
46
+ private:
34
47
  T* backend_past_end;
35
48
  T* first_free;
36
49
  T* store;
50
+ alignas(T) unsigned char inline_buf[inline_capacity * sizeof(T)];
51
+
52
+ ISOSPEC_FORCE_INLINE T* inline_store() noexcept { return reinterpret_cast<T*>(inline_buf); }
53
+ ISOSPEC_FORCE_INLINE bool is_inline() const noexcept { return store == reinterpret_cast<const T*>(inline_buf); }
54
+
55
+ void reset_to_inline() noexcept
56
+ {
57
+ store = first_free = inline_store();
58
+ backend_past_end = store + inline_capacity;
59
+ }
60
+
61
+ // Takes other's contents; *this must hold no heap store. other is left
62
+ // empty and inline.
63
+ void take(pod_vector<T>& other) noexcept
64
+ {
65
+ if(other.is_inline())
66
+ {
67
+ const size_t n = other.size();
68
+ reset_to_inline();
69
+ memcpy(static_cast<void*>(store), other.store, n * sizeof(T));
70
+ first_free = store + n;
71
+ }
72
+ else
73
+ {
74
+ backend_past_end = other.backend_past_end;
75
+ first_free = other.first_free;
76
+ store = other.store;
77
+ }
78
+ other.reset_to_inline();
79
+ }
37
80
 
38
81
  public:
39
- explicit pod_vector(size_t initial_size = 16)
82
+ explicit pod_vector(size_t initial_size = inline_capacity)
40
83
  {
41
84
  #if !ISOSPEC_BUILDING_R
42
85
  static_assert(std::is_trivially_copyable<T>::value, "Cannot use a pod_vector with a non-Plain Old Data type.");
43
86
  #endif
44
87
 
88
+ if(initial_size <= inline_capacity)
89
+ {
90
+ reset_to_inline();
91
+ return;
92
+ }
45
93
  store = reinterpret_cast<T*>(malloc(sizeof(T) * initial_size));
46
94
  if(store == NULL)
47
95
  throw std::bad_alloc();
@@ -51,25 +99,20 @@ template<typename T> class pod_vector
51
99
 
52
100
  pod_vector(const pod_vector<T>& other) = delete;
53
101
  pod_vector& operator=(const pod_vector<T>& other) = delete;
54
- pod_vector& operator=(pod_vector<T>&& other)
102
+ pod_vector& operator=(pod_vector<T>&& other) noexcept
55
103
  {
56
- free(store);
57
- backend_past_end = other.backend_past_end;
58
- first_free = other.first_free;
59
- store = other.store;
60
- other.backend_past_end = other.first_free = other.store = NULL;
104
+ if(this != &other)
105
+ {
106
+ if(!is_inline())
107
+ free(store);
108
+ take(other);
109
+ }
61
110
  return *this;
62
111
  }
63
112
 
64
- pod_vector(pod_vector<T>&& other)
65
- {
66
- backend_past_end = other.backend_past_end;
67
- first_free = other.first_free;
68
- store = other.store;
69
- other.backend_past_end = other.first_free = other.store = NULL;
70
- }
113
+ pod_vector(pod_vector<T>&& other) noexcept { take(other); }
71
114
 
72
- ~pod_vector() { free(store); backend_past_end = first_free = store = NULL; }
115
+ ~pod_vector() { if(!is_inline()) free(store); }
73
116
 
74
117
  explicit pod_vector(unsafe_pod_vector<T>&& other)
75
118
  {
@@ -83,9 +126,20 @@ template<typename T> class pod_vector
83
126
  {
84
127
  ISOSPEC_IMPOSSIBLE(n < static_cast<size_t>(backend_past_end - store));
85
128
  const std::ptrdiff_t store_used_size = first_free - store;
86
- T* new_store = reinterpret_cast<T*>(realloc(store, n * sizeof(T)));
87
- if(new_store == NULL)
88
- throw std::bad_alloc();
129
+ T* new_store;
130
+ if(is_inline())
131
+ {
132
+ new_store = reinterpret_cast<T*>(malloc(n * sizeof(T)));
133
+ if(new_store == NULL)
134
+ throw std::bad_alloc();
135
+ memcpy(static_cast<void*>(new_store), store, store_used_size * sizeof(T));
136
+ }
137
+ else
138
+ {
139
+ new_store = reinterpret_cast<T*>(realloc(store, n * sizeof(T)));
140
+ if(new_store == NULL)
141
+ throw std::bad_alloc();
142
+ }
89
143
  first_free = new_store + store_used_size;
90
144
  backend_past_end = new_store + n;
91
145
  store = new_store;
@@ -187,9 +241,16 @@ template<typename T> class pod_vector
187
241
 
188
242
  void swap(pod_vector<T>& other) noexcept
189
243
  {
190
- std::swap(backend_past_end, other.backend_past_end);
191
- std::swap(first_free, other.first_free);
192
- std::swap(store, other.store);
244
+ if(!is_inline() && !other.is_inline())
245
+ {
246
+ std::swap(backend_past_end, other.backend_past_end);
247
+ std::swap(first_free, other.first_free);
248
+ std::swap(store, other.store);
249
+ return;
250
+ }
251
+ pod_vector<T> tmp(std::move(other));
252
+ other.take(*this);
253
+ take(tmp);
193
254
  }
194
255
 
195
256
  typedef T* iterator;
@@ -214,10 +275,14 @@ template<typename T> class pod_vector
214
275
 
215
276
  void clear()
216
277
  {
217
- free(store);
218
- first_free = store = backend_past_end = NULL;
278
+ if(!is_inline())
279
+ free(store);
280
+ reset_to_inline();
219
281
  }
220
282
 
283
+ // Unlike clear(), keeps the storage for reuse.
284
+ void clear_keep_capacity() noexcept { first_free = store; }
285
+
221
286
  friend class unsafe_pod_vector<T>;
222
287
  };
223
288
 
@@ -506,6 +506,12 @@ class Iso(object):
506
506
  def parse_conf(self, cptr, starting_with = 0):
507
507
  return tuple(tuple(cptr[i+starting_with] for i in o) for o in self.offsets)
508
508
 
509
+ def _hand_over(self):
510
+ """Gives the C Iso away to a *Consuming setup call, which deletes it,
511
+ and leaves this object without one. Saves that call copying it."""
512
+ c_iso, self.iso = self.iso, None
513
+ return c_iso
514
+
509
515
  def _get_parse_conf_fun(self):
510
516
  # Can't just use the above function as lambda, as the entire class instance will be in closure
511
517
  offsets = self.offsets
@@ -894,7 +900,7 @@ def IsoThreshold(threshold,
894
900
  **kwds: named arguments to IsoSpectrum.
895
901
  """
896
902
  iso = Iso(formula = formula, get_confs = get_confs, **kwargs)
897
- tabulator = _check_pointer(isoFFI.clib.setupThresholdFixedEnvelope(iso.iso, threshold, absolute, get_confs),
903
+ tabulator = _check_pointer(isoFFI.clib.setupThresholdFixedEnvelopeConsuming(iso._hand_over(), threshold, absolute, get_confs),
898
904
  "setupThresholdFixedEnvelope")
899
905
  ido = IsoDistribution(cobject = tabulator, get_confs = get_confs, iso = iso)
900
906
  isoFFI.clib.deleteFixedEnvelope(tabulator, False)
@@ -916,7 +922,7 @@ def IsoTotalProb(prob_to_cover,
916
922
  **kwargs: named arguments to the superclass.
917
923
  """
918
924
  iso = Iso(formula=formula, get_confs=get_confs, **kwargs)
919
- tabulator = _check_pointer(isoFFI.clib.setupTotalProbFixedEnvelope(iso.iso, prob_to_cover, get_minimal_pset, get_confs),
925
+ tabulator = _check_pointer(isoFFI.clib.setupTotalProbFixedEnvelopeConsuming(iso._hand_over(), prob_to_cover, get_minimal_pset, get_confs),
920
926
  "setupTotalProbFixedEnvelope")
921
927
  ido = IsoDistribution(cobject = tabulator, get_confs = get_confs, iso = iso)
922
928
  isoFFI.clib.deleteFixedEnvelope(tabulator, False)
@@ -947,9 +953,9 @@ def IsoStochastic(no_molecules,
947
953
  """
948
954
  iso = Iso(formula=formula, get_confs=get_confs, **kwargs)
949
955
  if seed is None:
950
- tabulator = isoFFI.clib.setupStochasticFixedEnvelope(iso.iso, no_molecules, precision, beta_bias, get_confs)
956
+ tabulator = isoFFI.clib.setupStochasticFixedEnvelopeConsuming(iso._hand_over(), no_molecules, precision, beta_bias, get_confs)
951
957
  else:
952
- tabulator = isoFFI.clib.setupStochasticFixedEnvelopeSeeded(iso.iso, no_molecules, precision, beta_bias,
958
+ tabulator = isoFFI.clib.setupStochasticFixedEnvelopeSeededConsuming(iso._hand_over(), no_molecules, precision, beta_bias,
953
959
  get_confs, _check_seed(seed))
954
960
  _check_pointer(tabulator, "setupStochasticFixedEnvelope")
955
961
  ido = IsoDistribution(cobject = tabulator, get_confs = get_confs, iso = iso)
@@ -973,7 +979,7 @@ def IsoBinned(bin_width,
973
979
  **kwargs: named arguments to the superclass.
974
980
  """
975
981
  iso = Iso(formula=formula, get_confs=False, **kwargs)
976
- tabulator = _check_pointer(isoFFI.clib.setupBinnedFixedEnvelope(iso.iso, target_total_prob, bin_width, bin_middle),
982
+ tabulator = _check_pointer(isoFFI.clib.setupBinnedFixedEnvelopeConsuming(iso._hand_over(), target_total_prob, bin_width, bin_middle),
977
983
  "setupBinnedFixedEnvelope")
978
984
  ido = IsoDistribution(cobject = tabulator, get_confs = False, iso = iso)
979
985
  isoFFI.clib.deleteFixedEnvelope(tabulator, False)
@@ -92,17 +92,30 @@ class IsoFFI:
92
92
  double threshold,
93
93
  bool absolute,
94
94
  bool get_confs);
95
+ void* setupThresholdFixedEnvelopeConsuming(void* iso,
96
+ double threshold,
97
+ bool absolute,
98
+ bool get_confs);
95
99
 
96
100
  void* setupTotalProbFixedEnvelope(void* iso,
97
101
  double taget_coverage,
98
102
  bool optimize,
99
103
  bool get_confs);
104
+ void* setupTotalProbFixedEnvelopeConsuming(void* iso,
105
+ double taget_coverage,
106
+ bool optimize,
107
+ bool get_confs);
100
108
 
101
109
  void* setupStochasticFixedEnvelope(void* iso,
102
110
  size_t no_molecules,
103
111
  double precision,
104
112
  double beta_bias,
105
113
  bool get_confs);
114
+ void* setupStochasticFixedEnvelopeConsuming(void* iso,
115
+ size_t no_molecules,
116
+ double precision,
117
+ double beta_bias,
118
+ bool get_confs);
106
119
 
107
120
  void* setupStochasticFixedEnvelopeSeeded(void* iso,
108
121
  size_t no_molecules,
@@ -110,11 +123,21 @@ class IsoFFI:
110
123
  double beta_bias,
111
124
  bool get_confs,
112
125
  uint64_t seed);
126
+ void* setupStochasticFixedEnvelopeSeededConsuming(void* iso,
127
+ size_t no_molecules,
128
+ double precision,
129
+ double beta_bias,
130
+ bool get_confs,
131
+ uint64_t seed);
113
132
 
114
133
  void* setupBinnedFixedEnvelope(void* iso,
115
134
  double target_total_prob,
116
135
  double bin_width,
117
136
  double bin_middle);
137
+ void* setupBinnedFixedEnvelopeConsuming(void* iso,
138
+ double target_total_prob,
139
+ double bin_width,
140
+ double bin_middle);
118
141
 
119
142
  void* setupFixedEnvelope(double* masses, double* probs, size_t size, bool mass_sorted, bool prob_sorted, double total_prob);
120
143
  void* setupFixedEnvelopeWithConfs(double* masses, double* probs, int* confs, size_t size, int allDim, bool mass_sorted, bool prob_sorted, double total_prob);
isospecpy-2.6.0/VERSION DELETED
@@ -1 +0,0 @@
1
- 2.6.0
File without changes