IsoSpecPy 2.3.4__tar.gz → 2.3.5__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 (48) hide show
  1. {isospecpy-2.3.4 → isospecpy-2.3.5}/PKG-INFO +1 -1
  2. {isospecpy-2.3.4 → isospecpy-2.3.5}/pyproject.toml +1 -1
  3. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/cwrapper.cpp +4 -10
  4. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/fixedEnvelopes.cpp +86 -257
  5. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/fixedEnvelopes.h +3 -5
  6. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/Distributions.py +0 -1
  7. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/IsoSpecPy.py +27 -29
  8. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/PeriodicTbl.py +1 -1
  9. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/approximations.py +0 -9
  10. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/isoFFI.py +2 -2
  11. {isospecpy-2.3.4 → isospecpy-2.3.5}/LICENCE +0 -0
  12. {isospecpy-2.3.4 → isospecpy-2.3.5}/README.md +0 -0
  13. {isospecpy-2.3.4 → isospecpy-2.3.5}/skbuild/CMakeLists.txt +0 -0
  14. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/allocator.cpp +0 -0
  15. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/allocator.h +0 -0
  16. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/btrd.h +0 -0
  17. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/conf.h +0 -0
  18. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/cwrapper.h +0 -0
  19. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/dirtyAllocator.cpp +0 -0
  20. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/dirtyAllocator.h +0 -0
  21. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/element_tables.cpp +0 -0
  22. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/element_tables.h +0 -0
  23. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/fasta.cpp +0 -0
  24. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/fasta.h +0 -0
  25. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/isoMath.cpp +0 -0
  26. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/isoMath.h +0 -0
  27. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/isoSpec++.cpp +0 -0
  28. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/isoSpec++.h +0 -0
  29. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/marginalTrek++.cpp +0 -0
  30. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/marginalTrek++.h +0 -0
  31. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/misc.cpp +0 -0
  32. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/misc.h +0 -0
  33. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/mman.cpp +0 -0
  34. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/mman.h +0 -0
  35. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/operators.cpp +0 -0
  36. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/operators.h +0 -0
  37. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/platform.h +0 -0
  38. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/platform_incl.h +0 -0
  39. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/pod_vector.h +0 -0
  40. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/python-build.cpp +0 -0
  41. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/summator.h +0 -0
  42. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpec++/unity-build.cpp +0 -0
  43. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/Advanced.py +0 -0
  44. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/Formulas.py +0 -0
  45. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/IsoSpecPyOld.py +0 -0
  46. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/__init__.py +0 -0
  47. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/__main__.py +0 -0
  48. {isospecpy-2.3.4 → isospecpy-2.3.5}/src/IsoSpecPy/confs_passthrough.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IsoSpecPy
3
- Version: 2.3.4
3
+ Version: 2.3.5
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
@@ -9,7 +9,7 @@ cmake.source-dir = "skbuild"
9
9
  name = "IsoSpecPy"
10
10
  dependencies = ["cffi"]
11
11
  description = "IsoSpecPy is a Python library for computing isotopic distributions of molecules."
12
- version = "2.3.4"
12
+ version = "2.3.5"
13
13
  license = "BSD-2-Clause"
14
14
  license-files = ["LICENCE"]
15
15
  authors = [{name = "Michał Startek"}, {name = "Mateusz Łącki"}]
@@ -129,8 +129,9 @@ double* getMarginalLogSizeEstimates(void* iso, double target_total_prob)
129
129
  {
130
130
  Iso* i = reinterpret_cast<Iso*>(iso);
131
131
  double* ret = reinterpret_cast<double*>(malloc(sizeof(double)*i->getDimNumber()));
132
- if(ret != nullptr)
133
- i->saveMarginalLogSizeEstimates(ret, target_total_prob);
132
+ if(ret == nullptr)
133
+ throw std::bad_alloc();
134
+ i->saveMarginalLogSizeEstimates(ret, target_total_prob);
134
135
  return ret;
135
136
  }
136
137
 
@@ -383,13 +384,6 @@ double abyssalWassersteinDistance(void* tabulator1, void* tabulator2, double aby
383
384
  return reinterpret_cast<FixedEnvelope*>(tabulator1)->AbyssalWassersteinDistance(*reinterpret_cast<FixedEnvelope*>(tabulator2), abyss_depth, other_scale);
384
385
  }
385
386
 
386
- #if 0
387
- double abyssalWassersteinDistanceGrad(void* const* envelopes, const double* scales, double* ret_gradient, size_t N, double abyss_depth_exp, double abyss_depth_the)
388
- {
389
- return AbyssalWassersteinDistanceGrad(reinterpret_cast<FixedEnvelope* const*>(envelopes), scales, ret_gradient, N, abyss_depth_exp, abyss_depth_the);
390
- }
391
- #endif
392
-
393
387
  struct ws_match_res wassersteinMatch(void* tabulator1, void* tabulator2, double flow_dist, double other_scale)
394
388
  {
395
389
  struct ws_match_res res;
@@ -483,7 +477,7 @@ void array_fma(double* array, size_t N, double mul, double add)
483
477
  #if defined(FP_FAST_FMA)
484
478
  array[ii] = std::fma(array[ii], mul, add);
485
479
  #else
486
- array[ii] += (array[ii] * mul) + add;
480
+ array[ii] = (array[ii] * mul) + add;
487
481
  #endif
488
482
  }
489
483
 
@@ -28,9 +28,11 @@ _probs(array_copy_malloc<double>(other._probs, other._confs_no)),
28
28
  _confs(array_copy_nptr_malloc<int>(other._confs, other._confs_no*other.allDim)),
29
29
  _confs_no(other._confs_no),
30
30
  allDim(other.allDim),
31
+ allDimSizeofInt(other.allDimSizeofInt),
31
32
  sorted_by_mass(other.sorted_by_mass),
32
33
  sorted_by_prob(other.sorted_by_prob),
33
- total_prob(other.total_prob)
34
+ total_prob(other.total_prob),
35
+ current_size(other._confs_no)
34
36
  {}
35
37
 
36
38
  FixedEnvelope::FixedEnvelope(FixedEnvelope&& other) :
@@ -39,15 +41,60 @@ _probs(other._probs),
39
41
  _confs(other._confs),
40
42
  _confs_no(other._confs_no),
41
43
  allDim(other.allDim),
44
+ allDimSizeofInt(other.allDimSizeofInt),
42
45
  sorted_by_mass(other.sorted_by_mass),
43
46
  sorted_by_prob(other.sorted_by_prob),
44
- total_prob(other.total_prob)
47
+ total_prob(other.total_prob),
48
+ current_size(other.current_size)
45
49
  {
46
50
  other._masses = nullptr;
47
51
  other._probs = nullptr;
48
52
  other._confs = nullptr;
49
53
  other._confs_no = 0;
50
54
  other.total_prob = 0.0;
55
+ other.current_size = 0;
56
+ }
57
+
58
+ FixedEnvelope& FixedEnvelope::operator=(const FixedEnvelope& other)
59
+ {
60
+ if(this == &other)
61
+ return *this;
62
+ // Copy-and-swap: construct the copy first so that if any allocation inside
63
+ // the copy constructor throws, *this is left untouched. Only after tmp is
64
+ // fully built do we pilfer its pointers; tmp then destructs holding the old
65
+ // ones, which frees them without any possibility of failure.
66
+ FixedEnvelope tmp(other);
67
+ std::swap(_masses, tmp._masses);
68
+ std::swap(_probs, tmp._probs);
69
+ std::swap(_confs, tmp._confs);
70
+ _confs_no = tmp._confs_no;
71
+ allDim = tmp.allDim;
72
+ allDimSizeofInt = tmp.allDimSizeofInt;
73
+ sorted_by_mass = tmp.sorted_by_mass;
74
+ sorted_by_prob = tmp.sorted_by_prob;
75
+ total_prob = tmp.total_prob;
76
+ current_size = tmp.current_size;
77
+ return *this;
78
+ }
79
+
80
+ FixedEnvelope& FixedEnvelope::operator=(FixedEnvelope&& other)
81
+ {
82
+ if(this == &other)
83
+ return *this;
84
+ free(_masses);
85
+ free(_probs);
86
+ free(_confs);
87
+ _masses = other._masses; other._masses = nullptr;
88
+ _probs = other._probs; other._probs = nullptr;
89
+ _confs = other._confs; other._confs = nullptr;
90
+ _confs_no = other._confs_no; other._confs_no = 0;
91
+ allDim = other.allDim;
92
+ allDimSizeofInt = other.allDimSizeofInt;
93
+ sorted_by_mass = other.sorted_by_mass;
94
+ sorted_by_prob = other.sorted_by_prob;
95
+ total_prob = other.total_prob; other.total_prob = 0.0;
96
+ current_size = other.current_size; other.current_size = 0;
97
+ return *this;
51
98
  }
52
99
 
53
100
  FixedEnvelope::FixedEnvelope(double* in_masses, double* in_probs, size_t in_confs_no, bool masses_sorted, bool probs_sorted, double _total_prob) :
@@ -246,6 +293,12 @@ void FixedEnvelope::resample(size_t samples, double beta_bias)
246
293
  double cprob = 0.0;
247
294
  size_t pidx = -1; // Overflows - but it doesn't matter.
248
295
 
296
+ // Sentinel: prevents the inner while(pprob < cprob) from walking off the end
297
+ // if floating-point rounding leaves the accumulated probability sum slightly
298
+ // below 1.0. Safe to overwrite in-place: rdvariate_binom guards succ_prob>=1.0
299
+ // so infinity is handled correctly, and the sentinel is always cleaned up before
300
+ // return — either by the loop zeroing the slot when it advances pidx to here,
301
+ // or by the memset below when the loop terminates earlier.
249
302
  _probs[_confs_no-1] = (std::numeric_limits<double>::max)();
250
303
 
251
304
  while(samples > 0)
@@ -502,244 +555,6 @@ double FixedEnvelope::AbyssalWassersteinDistance(FixedEnvelope& other, double ab
502
555
  return accd + condemned * abyss_depth * 0.5;
503
556
  }
504
557
 
505
- #if 0
506
- double FixedEnvelope::ScaledAbyssalWassersteinDistance(FixedEnvelope * const * others, double abyss_depth, const double* other_scales, const size_t N)
507
- {
508
- sort_by_mass();
509
-
510
- std::priority_queue<std::pair<double, size_t>> PQ;
511
- std::unique_ptr<size_t[]> indices = std::make_unique<size_t[]>(N);
512
- memset(indices.get(), 0, sizeof(size_t)*N);
513
-
514
- for(size_t ii=0; ii<N; ii++)
515
- {
516
- others[ii]->sort_by_mass();
517
- if(others[ii]->_confs_no > 0)
518
- PQ.emplace_back({others._probs[0], ii});
519
- }
520
-
521
-
522
-
523
-
524
- std::vector<std::pair<double, double>> carried;
525
-
526
- size_t idx_this = 0;
527
- size_t idx_other = 0;
528
-
529
- //std::cout << "AAA" << std::endl;
530
-
531
- auto finished = [&]() -> bool { return idx_this >= _confs_no && PQ.empty(); };
532
- auto next = [&]() -> std::tuple<double, double, size_t> {
533
- if(idx_this >= _confs_no || (idx_other < other._confs_no && _masses[idx_this] > other._masses[idx_other]))
534
- {
535
- std::pair<double, double> res = std::pair<double, double>(other._masses[idx_other], other._probs[idx_other]*other_scale);
536
- idx_other++;
537
- return res;
538
- }
539
- else
540
- {
541
- std::pair<double, double> res = std::pair<double, double>(_masses[idx_this], -_probs[idx_this]);
542
- idx_this++;
543
- return res;
544
- }
545
- };
546
- double accd = 0.0;
547
- double condemned = 0.0;
548
-
549
- while(!finished())
550
- {
551
- auto [m, p] = next();
552
- if(!carried.empty() && carried[0].second * p > 0.0)
553
- {
554
- carried.emplace_back(m, p);
555
- continue;
556
- }
557
-
558
- while(!carried.empty())
559
- {
560
- auto& [cm, cp] = carried.back();
561
- if(m - cm >= abyss_depth)
562
- {
563
- for(auto it = carried.cbegin(); it != carried.cend(); it++)
564
- condemned += fabs(it->second);
565
- carried.clear();
566
- break;
567
- }
568
- if((cp+p)*p > 0.0)
569
- {
570
- accd += fabs((m-cm)*cp);
571
- p += cp;
572
- carried.pop_back();
573
- }
574
- else
575
- {
576
- accd += fabs((m-cm)*p);
577
- cp += p;
578
- p = 0.0;
579
- break;
580
- }
581
- }
582
- if(p != 0.0)
583
- carried.emplace_back(m, p);
584
- //std::cout << m << " " << p << std::endl;
585
- }
586
-
587
- for(auto it = carried.cbegin(); it != carried.cend(); it++)
588
- condemned += fabs(it->second);
589
-
590
- return accd + condemned * abyss_depth * 0.5;
591
- }
592
-
593
- #endif
594
-
595
- #if 0
596
- double AbyssalWassersteinDistanceGrad(FixedEnvelope* const* envelopes, const double* scales, double* ret_gradient, size_t N, double abyss_depth_exp, double abyss_depth_the)
597
- {
598
- return 0.0;
599
- std::unique_ptr<size_t[]> env_idx = std::make_unique<size_t[]>(N+1);
600
- memset(env_idx.get(), 0, (N+1)*sizeof(size_t));
601
- memset(ret_gradient, 0, (N+1)*sizeof(double));
602
-
603
- auto pq_cmp = [](std::pair<double, size_t>& p1, std::pair<double, size_t>& p2) { return p1.first > p2.first; };
604
- std::priority_queue<std::pair<double, size_t>, std::vector<std::pair<double, size_t>>, decltype(pq_cmp)> PQ(pq_cmp);
605
-
606
- for(size_t ii=0; ii<=N; ii++)
607
- {
608
- envelopes[ii]->sort_by_mass();
609
- if(envelopes[ii]->_confs_no > 0)
610
- {
611
- std::cout << "Initial push: " << envelopes[ii]->_masses[0] << " " << ii << '\n';
612
- PQ.push({envelopes[ii]->_masses[0], ii});
613
- }
614
- }
615
-
616
- std::vector<std::tuple<double, double, size_t>> carried;
617
-
618
- auto next = [&]() -> std::tuple<double, double, size_t> {
619
- auto [mass, eidx] = PQ.top();
620
- double prob = envelopes[eidx]->_probs[env_idx[eidx]];
621
- PQ.pop();
622
- if(eidx == 0)
623
- prob = -prob;
624
- else
625
- prob = prob * scales[eidx];
626
- std::cout << "Next: " << mass << " " << prob << " " << eidx << '\n';
627
- env_idx[eidx]++;
628
- if(env_idx[eidx] < envelopes[eidx]->_confs_no)
629
- PQ.push({envelopes[eidx]->_masses[env_idx[eidx]], eidx});
630
-
631
- return {mass, prob, eidx};
632
- };
633
- double accd = 0.0;
634
- double condemned = 0.0;
635
- //double flow;
636
- const double max_flow_dist = abyss_depth_exp + abyss_depth_the;
637
- max_flow_dist *= 2.0;
638
-
639
- while(!PQ.empty())
640
- {
641
- auto [m, p, eidx] = next();
642
- if(!carried.empty() && std::get<1>(carried[0]) * p > 0.0)
643
- {
644
- carried.emplace_back(m, p, eidx);
645
- continue;
646
- }
647
-
648
- while(!carried.empty())
649
- {
650
- auto& [cm, cp, ceidx] = carried.back();
651
- if(m - cm >= max_flow_dist)
652
- {
653
- for(auto it = carried.cbegin(); it != carried.cend(); it++)
654
- condemned += fabs(std::get<1>(*it));
655
- carried.clear();
656
- break;
657
- }
658
- std::cout << "accing\n";
659
- if((cp+p)*p > 0.0)
660
- {
661
- accd += fabs((m-cm)*cp);
662
- p += cp;
663
- carried.pop_back();
664
- std::cout << "cprob:" << cp << '\n';
665
- }
666
- else
667
- {
668
- accd += fabs((m-cm)*p);
669
- cp += p;
670
- std::cout << "prob:" << p << '\n';
671
- p = 0.0;
672
- break;
673
- }
674
- }
675
- if(p != 0.0)
676
- carried.emplace_back(m, p, eidx);
677
- //std::cout << m << " " << p << std::endl;
678
- }
679
-
680
- for(auto it = carried.cbegin(); it != carried.cend(); it++)
681
- condemned += fabs(std::get<1>(*it));
682
-
683
- std::cout << "ISO:" << accd << " " << condemned << '\n';
684
- return accd + condemned * max_flow_dist * 0.5;
685
- while(!PQ.empty())
686
- {
687
- auto [m, p, eidx] = next();
688
- if(!carried.empty() && (std::get<1>(carried[0]) * p > 0.0))
689
- {
690
- carried.emplace_back(m, p, eidx);
691
- continue;
692
- }
693
-
694
- while(!carried.empty())
695
- {
696
- auto& [cm, cp, ceidx] = carried.back();
697
- if(m - cm >= max_flow_dist)
698
- {
699
- for(auto it = carried.cbegin(); it != carried.cend(); it++)
700
- {
701
- flow = fabs(std::get<1>(*it));
702
- const size_t target_idx = std::get<2>(*it);
703
- flow *= target_idx == 0 ? abyss_depth_exp : abyss_depth_the;
704
- ret_gradient[target_idx] += flow;
705
- condemned += flow;
706
- std::cout << "condemnin': " << m << " " << p << " " << eidx << " | " << cm << " " << cp << " " << ceidx << '\n';
707
- }
708
- carried.clear();
709
- break;
710
- }
711
- if((cp+p)*p > 0.0)
712
- {
713
- flow = fabs((m-cm)*cp);
714
- accd += flow;
715
- p += cp;
716
- ret_gradient[ceidx] += flow;
717
- carried.pop_back();
718
- }
719
- else
720
- {
721
- flow = fabs((m-cm)*p);
722
- accd += flow;
723
- cp += p;
724
- ret_gradient[eidx] += flow;
725
- p = 0.0;
726
- break;
727
- }
728
- }
729
- if(p != 0.0)
730
- carried.emplace_back(m, p, eidx);
731
- //std::cout << m << " " << p << std::endl;
732
- }
733
-
734
- for(auto it = carried.cbegin(); it != carried.cend(); it++)
735
- condemned += fabs(std::get<1>(*it));
736
-
737
-
738
- return accd + condemned * (abyss_depth_exp + abyss_depth_the) * 0.5;
739
- }
740
- #endif
741
-
742
-
743
558
  std::tuple<double, double, double> FixedEnvelope::WassersteinMatch(FixedEnvelope& other, double flow_distance, double other_scale)
744
559
  {
745
560
  if(_confs_no == 0)
@@ -867,48 +682,60 @@ FixedEnvelope FixedEnvelope::bin(double bin_width, double middle)
867
682
 
868
683
  template<bool tgetConfs> void FixedEnvelope::reallocate_memory(size_t new_size)
869
684
  {
870
- current_size = new_size;
871
- // FIXME: Handle overflow gracefully here. It definitely could happen for people still stuck on 32 bits...
872
- _masses = reinterpret_cast<double*>(realloc(_masses, new_size * sizeof(double)));
873
- if(_masses == nullptr)
685
+ if(new_size > SIZE_MAX / sizeof(double))
874
686
  throw std::bad_alloc();
687
+ double* tmp_masses = reinterpret_cast<double*>(realloc(_masses, new_size * sizeof(double)));
688
+ if(tmp_masses == nullptr)
689
+ throw std::bad_alloc();
690
+ _masses = tmp_masses;
875
691
  tmasses = _masses + _confs_no;
876
692
 
877
- _probs = reinterpret_cast<double*>(realloc(_probs, new_size * sizeof(double)));
878
- if(_probs == nullptr)
693
+ double* tmp_probs = reinterpret_cast<double*>(realloc(_probs, new_size * sizeof(double)));
694
+ if(tmp_probs == nullptr)
879
695
  throw std::bad_alloc();
696
+ _probs = tmp_probs;
880
697
  tprobs = _probs + _confs_no;
881
698
 
882
699
  constexpr_if(tgetConfs)
883
700
  {
884
- _confs = reinterpret_cast<int*>(realloc(_confs, new_size * allDimSizeofInt));
885
- if(_confs == nullptr)
701
+ if(allDimSizeofInt > 0 && new_size > SIZE_MAX / static_cast<size_t>(allDimSizeofInt))
702
+ throw std::bad_alloc();
703
+ int* tmp_confs = reinterpret_cast<int*>(realloc(_confs, new_size * allDimSizeofInt));
704
+ if(tmp_confs == nullptr)
886
705
  throw std::bad_alloc();
706
+ _confs = tmp_confs;
887
707
  tconfs = _confs + (allDim * _confs_no);
888
708
  }
709
+ current_size = new_size;
889
710
  }
890
711
 
891
712
  void FixedEnvelope::slow_reallocate_memory(size_t new_size)
892
713
  {
893
- current_size = new_size;
894
- // FIXME: Handle overflow gracefully here. It definitely could happen for people still stuck on 32 bits...
895
- _masses = reinterpret_cast<double*>(realloc(_masses, new_size * sizeof(double)));
896
- if(_masses == nullptr)
714
+ if(new_size > SIZE_MAX / sizeof(double))
715
+ throw std::bad_alloc();
716
+ double* tmp_masses = reinterpret_cast<double*>(realloc(_masses, new_size * sizeof(double)));
717
+ if(tmp_masses == nullptr)
897
718
  throw std::bad_alloc();
719
+ _masses = tmp_masses;
898
720
  tmasses = _masses + _confs_no;
899
721
 
900
- _probs = reinterpret_cast<double*>(realloc(_probs, new_size * sizeof(double)));
901
- if(_probs == nullptr)
722
+ double* tmp_probs = reinterpret_cast<double*>(realloc(_probs, new_size * sizeof(double)));
723
+ if(tmp_probs == nullptr)
902
724
  throw std::bad_alloc();
725
+ _probs = tmp_probs;
903
726
  tprobs = _probs + _confs_no;
904
727
 
905
728
  if(_confs != nullptr)
906
729
  {
907
- _confs = reinterpret_cast<int*>(realloc(_confs, new_size * allDimSizeofInt));
908
- if(_confs == nullptr)
730
+ if(allDimSizeofInt > 0 && new_size > SIZE_MAX / static_cast<size_t>(allDimSizeofInt))
909
731
  throw std::bad_alloc();
732
+ int* tmp_confs = reinterpret_cast<int*>(realloc(_confs, new_size * allDimSizeofInt));
733
+ if(tmp_confs == nullptr)
734
+ throw std::bad_alloc();
735
+ _confs = tmp_confs;
910
736
  tconfs = _confs + (allDim * _confs_no);
911
737
  }
738
+ current_size = new_size;
912
739
  }
913
740
 
914
741
  template<bool tgetConfs> void FixedEnvelope::threshold_init(Iso&& iso, double threshold, bool absolute)
@@ -1155,7 +982,7 @@ FixedEnvelope FixedEnvelope::Binned(Iso&& iso, double target_total_prob, double
1155
982
 
1156
983
  ret.reallocate_memory<false>(ISOSPEC_INIT_TABLE_SIZE);
1157
984
 
1158
- for(size_t ii = nonzero_idx; ii >= idx_min && empty_steps < distance_10da; ii--)
985
+ for(size_t ii = nonzero_idx; empty_steps < distance_10da; )
1159
986
  {
1160
987
  if(acc[ii] > 0.0)
1161
988
  {
@@ -1164,6 +991,8 @@ FixedEnvelope FixedEnvelope::Binned(Iso&& iso, double target_total_prob, double
1164
991
  }
1165
992
  else
1166
993
  empty_steps++;
994
+ if(ii == idx_min) break;
995
+ ii--;
1167
996
  }
1168
997
 
1169
998
  empty_steps = 0;
@@ -34,9 +34,6 @@ namespace IsoSpec
34
34
 
35
35
  class FixedEnvelope;
36
36
 
37
- double AbyssalWassersteinDistanceGrad(FixedEnvelope* const* envelopes, const double* scales, double* ret_gradient, size_t N, double abyss_depth_exp, double abyss_depth_the);
38
-
39
-
40
37
  class ISOSPEC_EXPORT_SYMBOL FixedEnvelope {
41
38
  protected:
42
39
  double* _masses;
@@ -61,7 +58,7 @@ class ISOSPEC_EXPORT_SYMBOL FixedEnvelope {
61
58
  allDim(0),
62
59
  sorted_by_mass(false),
63
60
  sorted_by_prob(false),
64
- total_prob(0.0),
61
+ total_prob(NAN),
65
62
  current_size(0),
66
63
  allDimSizeofInt(0)
67
64
  // Deliberately not initializing tmasses, tprobs, tconfs
@@ -69,6 +66,8 @@ class ISOSPEC_EXPORT_SYMBOL FixedEnvelope {
69
66
 
70
67
  FixedEnvelope(const FixedEnvelope& other);
71
68
  FixedEnvelope(FixedEnvelope&& other);
69
+ FixedEnvelope& operator=(const FixedEnvelope& other);
70
+ FixedEnvelope& operator=(FixedEnvelope&& other);
72
71
 
73
72
  FixedEnvelope(double* masses, double* probs, size_t confs_no, bool masses_sorted = false, bool probs_sorted = false, double _total_prob = NAN);
74
73
  FixedEnvelope(double* masses, double* probs, int* confs, size_t confs_no, int _allDim, bool masses_sorted = false, bool probs_sorted = false, double _total_prob = NAN);
@@ -239,7 +238,6 @@ class ISOSPEC_EXPORT_SYMBOL FixedEnvelope {
239
238
  return Binned(Iso(iso, false), target_total_prob, bin_width, bin_middle);
240
239
  }
241
240
 
242
- friend double AbyssalWassersteinDistanceGrad(FixedEnvelope* const* envelopes, const double* scales, double* ret_gradient, size_t N, double abyss_depth_exp, double abyss_depth_the);
243
241
  };
244
242
 
245
243
  } // namespace IsoSpec
@@ -16,7 +16,6 @@ def simple_inverse(fun, prec = 0.01):
16
16
  end *= 2.0
17
17
 
18
18
  while (end - start) > prec:
19
- print(start, end)
20
19
  mid = (end + start) * 0.5
21
20
  if fun(mid) < x:
22
21
  start = mid
@@ -62,11 +62,10 @@ def ParseFormula(formula):
62
62
 
63
63
  return ret
64
64
 
65
- fasta_parsing_space = isoFFI.ffi.new("int[6]")
66
-
67
65
  def ParseFASTA(fasta):
68
66
  if isinstance(fasta, str):
69
67
  fasta = fasta.encode("ascii")
68
+ fasta_parsing_space = isoFFI.ffi.new("int[6]")
70
69
  isoFFI.clib.parse_fasta_c(fasta, fasta_parsing_space)
71
70
  elements = list("CHNOS")
72
71
  if fasta_parsing_space[5] > 0:
@@ -183,8 +182,10 @@ class Iso(object):
183
182
  raise ValueError("All isotope probabilities p must fulfill: 0.0 < p <= 1.0")
184
183
 
185
184
  self.isotopeNumbers = tuple(map(len, self.isotopeMasses))
186
- assert self.isotopeNumbers == tuple(map(len, self.isotopeProbabilities))
187
- assert len(self.atomCounts) == len(self.isotopeNumbers) == len(self.isotopeProbabilities)
185
+ if self.isotopeNumbers != tuple(map(len, self.isotopeProbabilities)):
186
+ raise ValueError("isotopeMasses and isotopeProbabilities must have the same number of isotopes per element")
187
+ if len(self.atomCounts) != len(self.isotopeNumbers) or len(self.isotopeNumbers) != len(self.isotopeProbabilities):
188
+ raise ValueError("atomCounts, isotopeMasses, and isotopeProbabilities must all have the same length")
188
189
 
189
190
  self.dimNumber = len(self.isotopeNumbers)
190
191
 
@@ -299,7 +300,7 @@ class IsoDistribution(object):
299
300
  try:
300
301
  import numpy as np
301
302
  except ImportError as e:
302
- raise Exception(e.msg + "\nThis requires numpy to be installed.")
303
+ raise Exception(str(e) + "\nThis requires numpy to be installed.")
303
304
  return np.frombuffer(isoFFI.ffi.buffer(self.masses))
304
305
 
305
306
  def np_probs(self):
@@ -307,7 +308,7 @@ class IsoDistribution(object):
307
308
  try:
308
309
  import numpy as np
309
310
  except ImportError as e:
310
- raise Exception(e.msg + "\nThis requires numpy to be installed.")
311
+ raise Exception(str(e) + "\nThis requires numpy to be installed.")
311
312
  return np.frombuffer(isoFFI.ffi.buffer(self.probs))
312
313
 
313
314
  def __iter__(self):
@@ -358,22 +359,16 @@ class IsoDistribution(object):
358
359
  self.parse_conf = iso._get_parse_conf_fun()
359
360
 
360
361
  elif probs is not None or masses is not None:
361
- assert probs is not None and masses is not None
362
- assert len(probs) == len(masses)
362
+ if probs is None or masses is None:
363
+ raise ValueError("masses and probs must both be provided together")
364
+ if len(probs) != len(masses):
365
+ raise ValueError("masses and probs must have the same length")
363
366
  self.size = len(probs)
364
367
  type_str = "double["+str(self.size)+"]"
365
368
  self.probs = isoFFI.ffi.new(type_str, probs)
366
369
  self.masses = isoFFI.ffi.new(type_str, masses)
367
- elif cobject == probs == masses == get_confs == iso == None:
368
- self.size = 0
369
- type_str = "double["+str(self.size)+"]"
370
- self.probs = isoFFI.ffi.new(type_str, [])
371
- self.masses = isoFFI.ffi.new(type_str, [])
372
- self._total_prob = 0.0
373
- self.mass_sorted = True
374
- self.prob_sorted = True
375
370
  else:
376
- raise RuntimeError("Invalid arguments for IsoDistribution constructor")
371
+ raise ValueError("IsoDistribution requires at least one of: cobject, or masses+probs")
377
372
 
378
373
  def _get_cobject(self):
379
374
  if hasattr(self, 'raw_confs') and self.raw_confs is not None:
@@ -666,13 +661,15 @@ def IsoStochastic(no_molecules,
666
661
  beta_bias=5.0,
667
662
  get_confs=False,
668
663
  **kwargs):
669
- """Initialize the IsoDistribution isotopic distribution by total probability.
664
+ """Initialize the isotopic distribution by stochastic sampling, simulating a measured mass spectrum
665
+ with a given number of ions. Each configuration is drawn independently according to its isotopic
666
+ probability, producing integer ion counts rather than exact probabilities.
670
667
 
671
668
  Args:
672
- no_molecules (uint): ionic current in instrument
669
+ no_molecules (uint): number of ions to simulate (ionic current).
673
670
  formula (str): a chemical formula, e.g. "C2H6O1" or "C2H6O".
674
- precision (float): passed to IsoTotalProbGenerator. Between 0.0 and 1.0.
675
- beta_bias (float, nonnegative): fiddling with this parameter does not change the result, but might make computations slightly faster (or likely, much, much slower is you screw it up...)
671
+ precision (float): total probability of the envelope used for sampling. Between 0.0 and 1.0.
672
+ beta_bias (float, nonnegative): fiddling with this parameter does not change the result, but might make computations slightly faster (or likely, much, much slower if you screw it up...)
676
673
  get_confs (boolean): should we report the counts of isotopologues?
677
674
  **kwargs: named arguments to the superclass.
678
675
  """
@@ -688,14 +685,14 @@ def IsoBinned(bin_width,
688
685
  target_total_prob=0.9999,
689
686
  bin_middle=0.0,
690
687
  **kwargs):
691
- """Initialize the IsoDistribution isotopic distribution by total probability.
688
+ """Initialize the isotopic distribution as a binned (histogram) envelope.
689
+ Configurations are grouped into bins of equal width and their probabilities summed.
692
690
 
693
691
  Args:
694
- no_molecules (uint): ionic current in instrument
692
+ bin_width (float): width of each bin in Da.
695
693
  formula (str): a chemical formula, e.g. "C2H6O1" or "C2H6O".
696
- precision (float): passed to IsoTotalProbGenerator. Between 0.0 and 1.0.
697
- beta_bias (float, nonnegative): fiddling with this parameter does not change the result, but might make computations slightly faster (or likely, much, much slower is you screw it up...)
698
- get_confs (boolean): should we report the counts of isotopologues?
694
+ target_total_prob (float): total probability of configurations to include before binning. Between 0.0 and 1.0.
695
+ bin_middle (float): offset of bin centres from zero.
699
696
  **kwargs: named arguments to the superclass.
700
697
  """
701
698
  iso = Iso(formula=formula, get_confs=False, **kwargs)
@@ -781,12 +778,13 @@ class IsoThresholdGenerator(IsoGenerator):
781
778
  class IsoLayeredGenerator(IsoGenerator):
782
779
  """Class alowing memory-efficient iteration over the isotopic distribution up till some joint probability of the reported peaks."""
783
780
  def __init__(self, formula="", get_confs=False, reorder_marginals = True, t_prob_hint = 0.99, **kwargs):
784
- """Initialize IsoThresholdGenerator.
781
+ """Initialize IsoLayeredGenerator.
785
782
 
786
783
  Args:
787
784
  formula (str): a chemical formula, e.g. "C2H6O1" or "C2H6O".
788
- absolute (boolean): should we report peaks with probabilities above an absolute probability threshold, or above a relative threshold amounting to a given proportion of the most probable peak?
789
785
  get_confs (boolean): should we report the counts of isotopologues?
786
+ reorder_marginals (boolean): should the marginal distributions be reordered for efficiency?
787
+ t_prob_hint (float): hint for the target total probability, used to size internal buffers.
790
788
  **kwargs: named arguments to the superclass.
791
789
  """
792
790
  super(IsoLayeredGenerator, self).__init__(formula=formula, get_confs=get_confs, **kwargs)
@@ -832,7 +830,7 @@ class IsoOrderedGenerator(IsoGenerator):
832
830
  def __del__(self):
833
831
  try:
834
832
  if self.cgen is not None:
835
- self.ffi.deleteIsoLayeredGenerator(self.cgen)
833
+ self.ffi.deleteIsoOrderedGenerator(self.cgen)
836
834
  self.cgen = None
837
835
  except AttributeError:
838
836
  pass
@@ -20,7 +20,7 @@ symbol_to_masses = dict(symbol_to_masses)
20
20
  symbol_to_probs = dict(symbol_to_probs)
21
21
 
22
22
  # Several derivative convenience dicts...
23
- symbol_to_massprob = dict((key, [zip(symbol_to_masses[key], symbol_to_probs[key])]) for key in symbol_to_probs.keys())
23
+ symbol_to_massprob = dict((key, list(zip(symbol_to_masses[key], symbol_to_probs[key]))) for key in symbol_to_probs.keys())
24
24
 
25
25
  def crossprod(l1, l2):
26
26
  return sum(x1*x2 for x1, x2 in zip(l1, l2))
@@ -24,15 +24,6 @@ def test_log_multinomial_confs_cnt():
24
24
  assert abs(x - 4.189655) < 10**(-5)
25
25
 
26
26
 
27
- def multinomial_confs_cnt(n, i):
28
- """Return the approximate number of configurations of the multinomial distribution.
29
-
30
- Args:
31
- n (int): The number of trials.
32
- i (int): The number of possible outcomes.
33
- """
34
- return exp(log_subiso_cnt_simplex(n, i))
35
-
36
27
 
37
28
  def log_V_simplex(n, i):
38
29
  """Get the natural logarithm of the volume of a simplex {(x_1,..,x_{i-1}): \sum_{j=1}^i = n}.
@@ -184,7 +184,7 @@ sure you want to do that, edit the source and disable this check."""
184
184
  extension = [".so", ".dylib", ".dll"]
185
185
  try:
186
186
  if platform.system() == "Linux":
187
- extension = [".so", "pyd"]
187
+ extension = [".so", ".pyd"]
188
188
  elif platform.system() == "Windows":
189
189
  extension = [".dll", ".pyd"]
190
190
  except:
@@ -236,7 +236,7 @@ sure you want to do that, edit the source and disable this check."""
236
236
  except (IndexError, OSError) as e:
237
237
  errmsg = (
238
238
  "Load libIsoSpec++.so, tried: "
239
- + libpath
239
+ + str(libpath)
240
240
  + "\n"
241
241
  + "Got error: "
242
242
  + str(type(e))
File without changes
File without changes