IsoSpecPy 2.5.0__py3-none-win_amd64.whl

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 (45) hide show
  1. IsoSpecPy/Advanced.py +32 -0
  2. IsoSpecPy/Distributions.py +93 -0
  3. IsoSpecPy/Formulas.py +71 -0
  4. IsoSpecPy/IsoSpec++/aligned_ptr.h +638 -0
  5. IsoSpecPy/IsoSpec++/allocator.h +69 -0
  6. IsoSpecPy/IsoSpec++/btrd.h +206 -0
  7. IsoSpecPy/IsoSpec++/composition.h +126 -0
  8. IsoSpecPy/IsoSpec++/conf.h +38 -0
  9. IsoSpecPy/IsoSpec++/cwrapper.h +271 -0
  10. IsoSpecPy/IsoSpec++/dirtyAllocator.h +58 -0
  11. IsoSpecPy/IsoSpec++/element_lookup.h +36 -0
  12. IsoSpecPy/IsoSpec++/element_tables.h +50 -0
  13. IsoSpecPy/IsoSpec++/fasta.h +70 -0
  14. IsoSpecPy/IsoSpec++/fasta_mods.h +70 -0
  15. IsoSpecPy/IsoSpec++/fixedEnvelopes.h +418 -0
  16. IsoSpecPy/IsoSpec++/isa_kernel_impl.h +192 -0
  17. IsoSpecPy/IsoSpec++/isa_kernels.h +172 -0
  18. IsoSpecPy/IsoSpec++/isoMath.h +121 -0
  19. IsoSpecPy/IsoSpec++/isoSpec++.h +801 -0
  20. IsoSpecPy/IsoSpec++/iso_simd.h +48 -0
  21. IsoSpecPy/IsoSpec++/marginalTrek++.h +614 -0
  22. IsoSpecPy/IsoSpec++/misc.h +229 -0
  23. IsoSpecPy/IsoSpec++/mman.h +67 -0
  24. IsoSpecPy/IsoSpec++/operators.h +150 -0
  25. IsoSpecPy/IsoSpec++/platform.h +180 -0
  26. IsoSpecPy/IsoSpec++/platform_incl.h +33 -0
  27. IsoSpecPy/IsoSpec++/pod_vector.h +400 -0
  28. IsoSpecPy/IsoSpec++/summator.h +118 -0
  29. IsoSpecPy/IsoSpec++/unimod.h +80 -0
  30. IsoSpecPy/IsoSpec++/unimod_support.h +2169 -0
  31. IsoSpecPy/IsoSpec++/unimod_table_data.h +1027 -0
  32. IsoSpecPy/IsoSpecPy.py +1088 -0
  33. IsoSpecPy/IsoSpecPyOld.py +290 -0
  34. IsoSpecPy/PeriodicTbl.py +51 -0
  35. IsoSpecPy/__init__.py +19 -0
  36. IsoSpecPy/__main__.py +25 -0
  37. IsoSpecPy/approximations.py +122 -0
  38. IsoSpecPy/confs_passthrough.py +16 -0
  39. IsoSpecPy/isoFFI.py +290 -0
  40. bin/IsoSpecCppPy.dll +0 -0
  41. isospecpy-2.5.0.dist-info/METADATA +220 -0
  42. isospecpy-2.5.0.dist-info/RECORD +45 -0
  43. isospecpy-2.5.0.dist-info/WHEEL +5 -0
  44. isospecpy-2.5.0.dist-info/licenses/LICENCE +35 -0
  45. lib/IsoSpecCppPy.lib +0 -0
@@ -0,0 +1,418 @@
1
+ /*
2
+ * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek.
3
+ *
4
+ * This file is part of IsoSpec.
5
+ *
6
+ * IsoSpec is free software: you can redistribute it and/or modify
7
+ * it under the terms of the Simplified ("2-clause") BSD licence.
8
+ *
9
+ * IsoSpec is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+ *
13
+ * You should have received a copy of the Simplified BSD Licence
14
+ * along with IsoSpec. If not, see <https://opensource.org/licenses/BSD-2-Clause>.
15
+ */
16
+
17
+ #pragma once
18
+
19
+ #include <cstdlib>
20
+ #include <cstring>
21
+ #include <algorithm>
22
+ #include <vector>
23
+ #include <utility>
24
+ #include <tuple>
25
+
26
+ #include "aligned_ptr.h"
27
+ #include "isoSpec++.h"
28
+
29
+ #ifdef DEBUG
30
+ #define ISOSPEC_INIT_TABLE_SIZE 16
31
+ #else
32
+ #define ISOSPEC_INIT_TABLE_SIZE 1024
33
+ #endif
34
+
35
+ // Widest layer step Binned() will take. total_prob_init uses -5.0, but it trims
36
+ // afterwards so overshoot is harmless there; Binned keeps everything it
37
+ // generates, so a step that overshoots the target is pure waste. -3.0 measured
38
+ // as a uniform ~16-22% win over the fixed -2.0 step advanceToNextConfiguration()
39
+ // would take, across small and large molecules with no overshoot, whereas -5.0
40
+ // regressed on large ones. Overridable at build time for retuning; declared
41
+ // here rather than in the .cpp so the tests can mirror the layer stepping.
42
+ #ifndef ISOSPEC_BINNED_LAYER_MAXSTEP
43
+ # define ISOSPEC_BINNED_LAYER_MAXSTEP (-3.0)
44
+ #endif
45
+
46
+ // The same knob for total_prob_init, which can afford a wider step than Binned:
47
+ // it trims afterwards, so overshooting the target costs only the work of
48
+ // generating what gets thrown away.
49
+ #ifndef ISOSPEC_TOTALPROB_LAYER_MAXSTEP
50
+ # define ISOSPEC_TOTALPROB_LAYER_MAXSTEP (-5.0)
51
+ #endif
52
+
53
+ namespace IsoSpec
54
+ {
55
+
56
+ class FixedEnvelope;
57
+
58
+ //! One of FixedEnvelope's arrays, handed over to the caller together with the
59
+ //! means of freeing it.
60
+ /*!
61
+ release_masses() and friends promise a pointer that plain free() accepts,
62
+ which costs a copy whenever the array is not actually a malloc()'d one (see
63
+ aligned_unique_ptr::release()). These are the copy-free counterpart: the
64
+ caller must invoke deleter(ptr, size) exactly once, and nothing else --
65
+ in particular not free(), unless the deleter happens to be that.
66
+ */
67
+ struct ISOSPEC_EXPORT_SYMBOL ReleasedArray {
68
+ void* ptr; /*!< nullptr if the envelope had no such array. */
69
+ std::size_t size; /*!< Byte count the deleter expects; meaningless on its own. */
70
+ void (*deleter)(void*, std::size_t) noexcept; /*!< Never null when ptr isn't. */
71
+ };
72
+
73
+ class ISOSPEC_EXPORT_SYMBOL FixedEnvelope {
74
+ protected:
75
+ double* _masses;
76
+ double* _probs;
77
+ int* _confs;
78
+ size_t _confs_no;
79
+ int allDim;
80
+ bool sorted_by_mass;
81
+ bool sorted_by_prob;
82
+ double total_prob;
83
+ size_t current_size;
84
+ double* tmasses;
85
+ double* tprobs;
86
+ int* tconfs;
87
+ int allDimSizeofInt;
88
+
89
+ // Ownership records for the three arrays above. The arrays themselves are
90
+ // always reached through the plain pointers, so every hot loop indexes them
91
+ // directly with no branch; these only say how each one has to be given back.
92
+ // An array comes from exactly one of two places, and the two free differently:
93
+ // * allocated by us -- the matching aligned_unique_ptr is engaged and holds
94
+ // it. Always DOUBLE_SIMD_ALIGNMENT-aligned, so the SIMD fills can store
95
+ // to it; a buffer that grows repeatedly also becomes VM-backed past
96
+ // ISOSPEC_ALIGNED_PTR_VM_THRESHOLD, so growing it stops copying.
97
+ // * adopted across the C ABI -- the aligned_unique_ptr is empty and the
98
+ // plain pointer is a malloc()'d buffer somebody else built (a cffi array
99
+ // taken over zero-copy). Arbitrarily aligned; goes back to free().
100
+ // Invariant: the owner is engaged iff the pointer beside it is ours, and
101
+ // then the two are equal.
102
+ aligned_unique_ptr<double, DOUBLE_SIMD_ALIGNMENT> _masses_owner;
103
+ aligned_unique_ptr<double, DOUBLE_SIMD_ALIGNMENT> _probs_owner;
104
+ aligned_unique_ptr<int, DOUBLE_SIMD_ALIGNMENT> _confs_owner;
105
+
106
+ static void free_deleter(void* p, std::size_t) noexcept { free(p); }
107
+
108
+ //! Free whichever of the two backends holds ptr, and null it.
109
+ template<typename T, std::size_t A>
110
+ static void free_array(aligned_unique_ptr<T, A>& owner, T*& ptr) noexcept
111
+ {
112
+ if(owner)
113
+ owner.reset(0);
114
+ else
115
+ free(ptr);
116
+ ptr = nullptr;
117
+ }
118
+
119
+ //! Hand ptr back as something free() accepts, copying only if it has to.
120
+ template<typename T, std::size_t A>
121
+ static T* release_array(aligned_unique_ptr<T, A>& owner, T*& ptr)
122
+ {
123
+ T* ret = owner ? owner.release() : ptr;
124
+ ptr = nullptr;
125
+ return ret;
126
+ }
127
+
128
+ //! Hand ptr back with its deleter. Never copies.
129
+ template<typename T, std::size_t A>
130
+ static ReleasedArray release_array_with_deleter(aligned_unique_ptr<T, A>& owner, T*& ptr) noexcept
131
+ {
132
+ ReleasedArray ret;
133
+ if(owner)
134
+ {
135
+ typename aligned_unique_ptr<T, A>::release_result r = owner.release_with_deleter();
136
+ ret = ReleasedArray{static_cast<void*>(r.ptr), r.size, r.deleter};
137
+ }
138
+ else
139
+ ret = ReleasedArray{static_cast<void*>(ptr), 0, &free_deleter};
140
+ ptr = nullptr;
141
+ return ret;
142
+ }
143
+
144
+ //! Forget ptr without freeing it -- whoever asked for this now owns it.
145
+ template<typename T, std::size_t A>
146
+ static void disown_array(aligned_unique_ptr<T, A>& owner, T*& ptr) noexcept
147
+ {
148
+ if(owner)
149
+ (void)owner.release_with_deleter(); // discards the deleter, but never copies
150
+ ptr = nullptr;
151
+ }
152
+
153
+ //! Allocate new_n elements, keeping nothing. See ISOSPEC_ALIGNED_PTR_VM_THRESHOLD
154
+ //! for why a one-shot allocation of a known size is not the same operation
155
+ //! as growing into one.
156
+ template<typename T, std::size_t A>
157
+ static void fresh_array(aligned_unique_ptr<T, A>& owner, T*& ptr, size_t new_n)
158
+ {
159
+ if(!owner && ptr != nullptr)
160
+ {
161
+ free(ptr);
162
+ ptr = nullptr;
163
+ }
164
+ owner.reset(new_n);
165
+ ptr = owner.get();
166
+ }
167
+
168
+ //! Resize to new_n elements, preserving min(old_n, new_n) of them.
169
+ template<typename T, std::size_t A>
170
+ static void grow_array(aligned_unique_ptr<T, A>& owner, T*& ptr, size_t new_n, size_t old_n)
171
+ {
172
+ if(!owner && ptr != nullptr)
173
+ {
174
+ // An adopted buffer did not come from the aligned allocator and so
175
+ // cannot be handed to it for resizing: move the contents across into
176
+ // one that did, then give the original back to free(). Only reachable
177
+ // if somebody grows an envelope built from foreign arrays.
178
+ aligned_unique_ptr<T, A> fresh;
179
+ fresh.realloc(new_n);
180
+ const size_t keep = (std::min)(old_n, new_n);
181
+ if(keep > 0)
182
+ memcpy(fresh.get(), ptr, sizeof(T) * keep);
183
+ free(ptr);
184
+ owner = std::move(fresh);
185
+ }
186
+ else
187
+ // Self-allocated: for a VM-backed buffer this is an in-place remap
188
+ // rather than a copy.
189
+ owner.realloc(new_n);
190
+ ptr = owner.get();
191
+ }
192
+
193
+ public:
194
+ ISOSPEC_FORCE_INLINE FixedEnvelope() : _masses(nullptr),
195
+ _probs(nullptr),
196
+ _confs(nullptr),
197
+ _confs_no(0),
198
+ allDim(0),
199
+ sorted_by_mass(false),
200
+ sorted_by_prob(false),
201
+ total_prob(NAN),
202
+ current_size(0),
203
+ allDimSizeofInt(0)
204
+ // Deliberately not initializing tmasses, tprobs, tconfs
205
+ {};
206
+
207
+ FixedEnvelope(const FixedEnvelope& other);
208
+ FixedEnvelope(FixedEnvelope&& other);
209
+ FixedEnvelope& operator=(const FixedEnvelope& other);
210
+ FixedEnvelope& operator=(FixedEnvelope&& other);
211
+
212
+ //! Adopt foreign, malloc()'d arrays (see the ownership note above).
213
+ FixedEnvelope(double* masses, double* probs, size_t confs_no, bool masses_sorted = false, bool probs_sorted = false, double _total_prob = NAN);
214
+ 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);
215
+
216
+ //! Internal counterpart to the two adopting constructors above: takes over
217
+ //! arrays this library allocated itself, so that everything the library
218
+ //! produces stays aligned instead of being demoted to a plain malloc()'d,
219
+ //! foreign buffer.
220
+ FixedEnvelope(aligned_unique_ptr<double, DOUBLE_SIMD_ALIGNMENT>&& masses,
221
+ aligned_unique_ptr<double, DOUBLE_SIMD_ALIGNMENT>&& probs,
222
+ size_t confs_no, bool masses_sorted = false, bool probs_sorted = false, double _total_prob = NAN);
223
+
224
+ virtual ~FixedEnvelope()
225
+ {
226
+ free_array(_masses_owner, _masses);
227
+ free_array(_probs_owner, _probs);
228
+ free_array(_confs_owner, _confs);
229
+ };
230
+
231
+ FixedEnvelope operator+(const FixedEnvelope& other) const;
232
+ FixedEnvelope operator*(const FixedEnvelope& other) const;
233
+
234
+ inline size_t confs_no() const { return _confs_no; }
235
+ inline int getAllDim() const { return allDim; }
236
+
237
+ inline const double* masses() const { return _masses; }
238
+ inline const double* probs() const { return _probs; }
239
+ inline const int* confs() const { return _confs; }
240
+
241
+ //! Hand an array over as a pointer the caller must free() itself.
242
+ /*! Copies the array first if it is not already a malloc()'d one -- see
243
+ ReleasedArray and the release_*_with_deleter() overloads below for the
244
+ variant that never copies. */
245
+ inline double* release_masses() { return release_array(_masses_owner, _masses); }
246
+ inline double* release_probs() { return release_array(_probs_owner, _probs); }
247
+ inline int* release_confs() { return release_array(_confs_owner, _confs); }
248
+
249
+ //! Hand an array over together with its deleter. Never copies.
250
+ inline ReleasedArray release_masses_with_deleter() { return release_array_with_deleter(_masses_owner, _masses); }
251
+ inline ReleasedArray release_probs_with_deleter() { return release_array_with_deleter(_probs_owner, _probs); }
252
+ inline ReleasedArray release_confs_with_deleter() { return release_array_with_deleter(_confs_owner, _confs); }
253
+
254
+ //! Drop all three arrays without freeing any of them: somebody else owns them.
255
+ inline void release_everything() { disown_array(_masses_owner, _masses); disown_array(_probs_owner, _probs); disown_array(_confs_owner, _confs); }
256
+
257
+
258
+ inline double mass(size_t i) const { return _masses[i]; }
259
+ inline double prob(size_t i) const { return _probs[i]; }
260
+ inline const int* conf(size_t i) const { return _confs + i*allDim; }
261
+
262
+ void sort_by_mass();
263
+ void sort_by_prob();
264
+
265
+ double get_total_prob();
266
+ void scale(double factor);
267
+ void normalize();
268
+ void shift_mass(double shift);
269
+ void resample(size_t ionic_current, double beta_bias = 1.0);
270
+
271
+ double empiric_average_mass();
272
+ double empiric_variance();
273
+ double empiric_stddev() { return sqrt(empiric_variance()); }
274
+
275
+ double WassersteinDistance(FixedEnvelope& other);
276
+ double OrientedWassersteinDistance(FixedEnvelope& other);
277
+ double AbyssalWassersteinDistance(FixedEnvelope& other, double abyss_depth, double other_scale = 1.0);
278
+ std::tuple<double, double, double> WassersteinMatch(FixedEnvelope& other, double flow_distance, double other_scale = 1.0);
279
+
280
+
281
+ static FixedEnvelope LinearCombination(const std::vector<const FixedEnvelope*>& spectra, const std::vector<double>& intensities);
282
+ static FixedEnvelope LinearCombination(const FixedEnvelope* const * spectra, const double* intensities, size_t size);
283
+
284
+
285
+ FixedEnvelope bin(double bin_width = 1.0, double middle = 0.0);
286
+
287
+ private:
288
+ void sort_by(double* order);
289
+
290
+
291
+ protected:
292
+ template<typename T, bool tgetConfs> ISOSPEC_FORCE_INLINE void store_conf(const T& generator)
293
+ {
294
+ *tmasses = generator.mass(); tmasses++;
295
+ *tprobs = generator.prob(); tprobs++;
296
+ constexpr_if(tgetConfs) { generator.get_conf_signature(tconfs); tconfs += allDim; }
297
+ }
298
+
299
+ ISOSPEC_FORCE_INLINE void store_conf(double _mass, double _prob)
300
+ {
301
+ if(_confs_no == current_size)
302
+ reallocate_memory<false>(current_size*2);
303
+
304
+ *tprobs = _prob;
305
+ *tmasses = _mass;
306
+ tprobs++;
307
+ tmasses++;
308
+
309
+ _confs_no++;
310
+ }
311
+
312
+ template<bool tgetConfs> ISOSPEC_FORCE_INLINE void swap(size_t idx1, size_t idx2, ISOSPEC_MAYBE_UNUSED int* conf_swapspace)
313
+ {
314
+ std::swap<double>(this->_probs[idx1], this->_probs[idx2]);
315
+ std::swap<double>(this->_masses[idx1], this->_masses[idx2]);
316
+ constexpr_if(tgetConfs)
317
+ {
318
+ int* c1 = this->_confs + (idx1*this->allDim);
319
+ int* c2 = this->_confs + (idx2*this->allDim);
320
+ memcpy(conf_swapspace, c1, this->allDimSizeofInt);
321
+ memcpy(c1, c2, this->allDimSizeofInt);
322
+ memcpy(c2, conf_swapspace, this->allDimSizeofInt);
323
+ }
324
+ }
325
+
326
+ //! Resize the three arrays to hold new_size configurations, keeping the
327
+ //! _confs_no already stored -- the growth path, for fills that do not know
328
+ //! how much they will produce.
329
+ template<bool tgetConfs> void reallocate_memory(size_t new_size);
330
+
331
+ //! Allocate the three arrays for exactly new_size configurations, keeping
332
+ //! nothing -- for fills that counted their output up front.
333
+ template<bool tgetConfs> void allocate_memory(size_t new_size);
334
+
335
+ //! Store every remaining configuration of the generator's current layer.
336
+ template<bool tgetConfs> void store_layer(IsoLayeredGenerator& generator);
337
+
338
+ //! Store configurations of the generator's current layer, adding each one's
339
+ //! probability to prob_so_far, until that reaches target_total_prob or the
340
+ //! layer runs out; returns whether it was reached.
341
+ template<bool tgetConfs> bool store_layer_to_prob(IsoLayeredGenerator& generator,
342
+ double& prob_so_far,
343
+ double target_total_prob);
344
+
345
+ public:
346
+ template<bool tgetConfs> void threshold_init(Iso&& iso, double threshold, bool absolute);
347
+
348
+ template<bool tgetConfs, typename GenType = IsoLayeredGenerator> void addConfILG(const GenType& generator)
349
+ {
350
+ if(this->_confs_no == this->current_size)
351
+ this->template reallocate_memory<tgetConfs>(this->current_size*2);
352
+
353
+ this->template store_conf<GenType, tgetConfs>(generator);
354
+ this->_confs_no++;
355
+ }
356
+
357
+ template<bool tgetConfs> void total_prob_init(Iso&& iso, double target_prob, bool trim);
358
+
359
+ static FixedEnvelope FromThreshold(Iso&& iso, double threshold, bool absolute, bool tgetConfs = false)
360
+ {
361
+ FixedEnvelope ret;
362
+
363
+ if(tgetConfs)
364
+ ret.threshold_init<true>(std::move(iso), threshold, absolute);
365
+ else
366
+ ret.threshold_init<false>(std::move(iso), threshold, absolute);
367
+ return ret;
368
+ }
369
+
370
+ inline static FixedEnvelope FromThreshold(const Iso& iso, double _threshold, bool _absolute, bool tgetConfs = false)
371
+ {
372
+ return FromThreshold(Iso(iso, true), _threshold, _absolute, tgetConfs);
373
+ }
374
+
375
+ static FixedEnvelope FromTotalProb(Iso&& iso, double target_total_prob, bool optimize, bool tgetConfs = false)
376
+ {
377
+ FixedEnvelope ret;
378
+
379
+ if(tgetConfs)
380
+ ret.total_prob_init<true>(std::move(iso), target_total_prob, optimize);
381
+ else
382
+ ret.total_prob_init<false>(std::move(iso), target_total_prob, optimize);
383
+
384
+ return ret;
385
+ }
386
+
387
+ inline static FixedEnvelope FromTotalProb(const Iso& iso, double _target_total_prob, bool _optimize, bool tgetConfs = false)
388
+ {
389
+ return FromTotalProb(Iso(iso, true), _target_total_prob, _optimize, tgetConfs);
390
+ }
391
+
392
+ template<bool tgetConfs> void stochastic_init(Iso&& iso, size_t _no_molecules, double _precision, double _beta_bias);
393
+
394
+ inline static FixedEnvelope FromStochastic(Iso&& iso, size_t _no_molecules, double _precision = 0.9999, double _beta_bias = 5.0, bool tgetConfs = false)
395
+ {
396
+ FixedEnvelope ret;
397
+
398
+ if(tgetConfs)
399
+ ret.stochastic_init<true>(std::move(iso), _no_molecules, _precision, _beta_bias);
400
+ else
401
+ ret.stochastic_init<false>(std::move(iso), _no_molecules, _precision, _beta_bias);
402
+
403
+ return ret;
404
+ }
405
+
406
+ static FixedEnvelope FromStochastic(const Iso& iso, size_t _no_molecules, double _precision = 0.9999, double _beta_bias = 5.0, bool tgetConfs = false)
407
+ {
408
+ return FromStochastic(Iso(iso, true), _no_molecules, _precision, _beta_bias, tgetConfs);
409
+ }
410
+
411
+ static FixedEnvelope Binned(Iso&& iso, double target_total_prob, double bin_width, double bin_middle = 0.0);
412
+ static FixedEnvelope Binned(const Iso& iso, double target_total_prob, double bin_width, double bin_middle = 0.0)
413
+ {
414
+ return Binned(Iso(iso, true), target_total_prob, bin_width, bin_middle);
415
+ }
416
+ };
417
+
418
+ } // namespace IsoSpec
@@ -0,0 +1,192 @@
1
+ /*
2
+ * Copyright (C) 2015-2020 Mateusz Łącki and Michał Startek.
3
+ *
4
+ * This file is part of IsoSpec.
5
+ *
6
+ * IsoSpec is free software: you can redistribute it and/or modify
7
+ * it under the terms of the Simplified ("2-clause") BSD licence.
8
+ *
9
+ * IsoSpec is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+ *
13
+ * You should have received a copy of the Simplified BSD Licence
14
+ * along with IsoSpec. If not, see <https://opensource.org/licenses/BSD-2-Clause>.
15
+ */
16
+
17
+ // The bodies of the two batched fill kernels, in a form meant to be included
18
+ // once per instruction-set level, by a translation unit compiled with that
19
+ // level's -march. Define ISOSPEC_ISA_NAMESPACE to the namespace to put them in
20
+ // before including this; everything lands there, so several levels can coexist
21
+ // in one binary.
22
+ //
23
+ // Nothing here may be given external linkage except the kernels table at the
24
+ // bottom, and nothing outside <experimental/simd> and this project's POD-only
25
+ // headers may be included -- see the long note in isa_kernels.h for what goes
26
+ // wrong otherwise. This file therefore has NO include guard: it is included
27
+ // several times on purpose, with a different namespace each time.
28
+
29
+ #ifndef ISOSPEC_ISA_NAMESPACE
30
+ #error "define ISOSPEC_ISA_NAMESPACE before including isa_kernel_impl.h"
31
+ #endif
32
+
33
+ #include <cstddef>
34
+ #include "platform.h" // ISOSPEC_HAS_SIMD, simd_double, simd_ns -- macros and typedefs only
35
+ #include "isa_kernels.h" // the POD interface these implement
36
+
37
+ namespace IsoSpec
38
+ {
39
+ namespace ISOSPEC_ISA_NAMESPACE
40
+ {
41
+
42
+ #if ISOSPEC_HAS_SIMD
43
+
44
+ // Both kernels below walk the run the same way. Each step takes the W positions
45
+ // after the cursor at once: one compare decides that all W are above the cutoff
46
+ // -- sound because lProbs descends, so the highest index guards the rest, and
47
+ // safe to read because of the ISOSPEC_LPROB_GUARDIANS sentinels past the end --
48
+ // and then the marginal's mass and probability tables are read as vectors and
49
+ // combined with the higher marginals' (loop-invariant) partial mass and product.
50
+ //
51
+ // Because the compare only succeeds when the *last* lane is above the cutoff,
52
+ // every lane produced is a real configuration, so the reads from the mass and
53
+ // probability tables, which carry no sentinels, stay in bounds.
54
+ //
55
+ // The output is packed contiguously and the caller's scalar drain moves the
56
+ // cursor by a non-multiple of W, so neither the loads nor the stores can be
57
+ // assumed vector-aligned: element_aligned throughout.
58
+
59
+ static std::size_t fill_run(const double** lProbs_ptr,
60
+ const double* lProbs_start,
61
+ const double* marginal_masses,
62
+ const double* marginal_probs,
63
+ double lcfmsv,
64
+ double partial_mass,
65
+ double partial_prob,
66
+ double* out_masses,
67
+ double* out_probs)
68
+ {
69
+ constexpr std::size_t W = simd_double::size();
70
+ const double* p = *lProbs_ptr;
71
+ std::size_t emitted = 0;
72
+
73
+ const simd_double vpartial_prob(partial_prob);
74
+ const simd_double vpartial_mass(partial_mass);
75
+
76
+ simd_double masses;
77
+ simd_double probs;
78
+
79
+ while(ISOSPEC_LIKELY(*(p + W) >= lcfmsv))
80
+ {
81
+ const std::size_t offset = static_cast<std::size_t>(p - lProbs_start) + 1;
82
+
83
+ probs.copy_from(marginal_probs + offset, simd_ns::element_aligned);
84
+ probs *= vpartial_prob;
85
+ masses.copy_from(marginal_masses + offset, simd_ns::element_aligned);
86
+ masses += vpartial_mass;
87
+
88
+ masses.copy_to(out_masses + emitted, simd_ns::element_aligned);
89
+ probs.copy_to(out_probs + emitted, simd_ns::element_aligned);
90
+
91
+ emitted += W;
92
+ p += W;
93
+ }
94
+
95
+ *lProbs_ptr = p;
96
+ return emitted;
97
+ }
98
+
99
+ static std::size_t bin_run(const double** lProbs_ptr,
100
+ const double* lProbs_start,
101
+ const double* marginal_masses,
102
+ const double* marginal_probs,
103
+ double lcfmsv,
104
+ double partial_mass,
105
+ double partial_prob,
106
+ double* acc,
107
+ double hwmm,
108
+ double inv_bin_width)
109
+ {
110
+ constexpr std::size_t W = simd_double::size();
111
+ const double* p = *lProbs_ptr;
112
+ std::size_t emitted = 0;
113
+
114
+ const simd_double vpartial_prob(partial_prob);
115
+ const simd_double vpartial_mass(partial_mass);
116
+ const simd_double vhwmm(hwmm);
117
+ const simd_double vinv_bin_width(inv_bin_width);
118
+
119
+ simd_double masses;
120
+ simd_double probs;
121
+
122
+ while(ISOSPEC_LIKELY(*(p + W) >= lcfmsv))
123
+ {
124
+ const std::size_t offset = static_cast<std::size_t>(p - lProbs_start) + 1;
125
+
126
+ probs.copy_from(marginal_probs + offset, simd_ns::element_aligned);
127
+ probs *= vpartial_prob;
128
+ masses.copy_from(marginal_masses + offset, simd_ns::element_aligned);
129
+ masses += vpartial_mass;
130
+
131
+ // The vector form of bin_index(): the same arithmetic in the same order,
132
+ // so it lands in the same bin and, like it, stays inside the allocation.
133
+ // The scatter that consumes it cannot be vectorised -- the indices repeat
134
+ // and collide, and resolving that would need AVX-512CD conflict detection
135
+ // -- but everything ahead of it is, which is where the work is.
136
+ const simd_double idx = simd_ns::floor((masses + vhwmm) * vinv_bin_width);
137
+
138
+ // Hand the probabilities to the scatter through a buffer rather than by
139
+ // subscripting the vector. Clang otherwise sees every lane extracted and
140
+ // un-vectorises the multiply above into W scalar vmulsd's, each with its
141
+ // own 8-byte load, putting a load->mul->add->store chain on every lane:
142
+ // measured 2.03x slower on Alder Lake, 1.36x on Piledriver. The spill
143
+ // itself is free -- these values are consumed one lane at a time either
144
+ // way, so the round trip through memory was always going to happen; this
145
+ // only fixes where the multiply lands relative to it. GCC is unaffected
146
+ // (it kept the packed form regardless) and the results are bit-identical.
147
+ alignas(DOUBLE_SIMD_ALIGNMENT) double pbuf[W];
148
+ probs.copy_to(pbuf, simd_ns::vector_aligned);
149
+
150
+ for(std::size_t ii = 0; ii < W; ii++)
151
+ acc[static_cast<std::ptrdiff_t>(idx[ii])] += pbuf[ii];
152
+
153
+ emitted += W;
154
+ p += W;
155
+ }
156
+
157
+ *lProbs_ptr = p;
158
+ return emitted;
159
+ }
160
+
161
+ constexpr std::size_t kernel_lanes = simd_double::size();
162
+
163
+ #else // no <experimental/simd> on this toolchain
164
+
165
+ // Emit nothing and let the caller's scalar drain do all the work: that drain is
166
+ // a complete implementation on its own, so this needs no scalar duplicate of
167
+ // the loops above.
168
+
169
+ static std::size_t fill_run(const double**, const double*, const double*, const double*,
170
+ double, double, double, double*, double*)
171
+ {
172
+ return 0;
173
+ }
174
+
175
+ static std::size_t bin_run(const double**, const double*, const double*, const double*,
176
+ double, double, double, double*, double, double)
177
+ {
178
+ return 0;
179
+ }
180
+
181
+ constexpr std::size_t kernel_lanes = 1;
182
+
183
+ #endif // ISOSPEC_HAS_SIMD
184
+
185
+ // The one symbol with external linkage. `extern` is load-bearing: a const object
186
+ // at namespace scope would otherwise have internal linkage and the whole level
187
+ // would be optimised out of the object file.
188
+ extern const IsaKernels kernels;
189
+ extern const IsaKernels kernels = { &fill_run, &bin_run, ISOSPEC_ISA_NAME, kernel_lanes };
190
+
191
+ } // namespace ISOSPEC_ISA_NAMESPACE
192
+ } // namespace IsoSpec