Loading [MathJax]/extensions/tex2jax.js
ATLAS Offline Software
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
KLGaussianMixtureReduction.cxx
Go to the documentation of this file.
1 /*
2  Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3 */
4 
8 //
10 //
11 #include "CxxUtils/restrict.h"
12 #include "CxxUtils/vec.h"
13 //
14 #include <cmath>
15 #include <memory>
16 #include <limits>
17 #include <numeric>
18 #include <stdexcept>
19 #include <vector>
20 
28 namespace KLReductionFMV {
29 //clang FMV needs a namespace :/
30 #if HAVE_FUNCTION_MULTIVERSIONING
31 [[gnu::target("default")]]
32 #endif
33 int vIdxOfMin(const float* distancesIn, int n) {
34  return vAlgs::vIdxOfMin<128>(distancesIn, n);
35 }
36 #if HAVE_FUNCTION_MULTIVERSIONING
37 [[gnu::target("avx2")]]
38 int vIdxOfMin(const float* distancesIn, int n) {
39  return vAlgs::vIdxOfMin<256>(distancesIn, n);
40 }
41 #endif
42 } // namespace KLReductionFMV
43 
44 namespace {
45 //internal implementation methods
46 
47 //We want to be using up to a 256 ISA, these cover also a narrower one
48 constexpr size_t STRIDEForKL = vAlgs::strideOfNumSIMDVec<256,float>(4);
49 constexpr size_t ALIGNMENTForKL = vAlgs::alignmentForArray<256>();
50 using namespace GSFUtils;
51 
64 inline float
65 symmetricKL(const Component1D& ATH_RESTRICT componentI,
66  const Component1D& ATH_RESTRICT componentJ)
67 {
68  const double meanDifference = componentI.mean - componentJ.mean;
69  const double inverCovSum = componentI.invCov + componentJ.invCov;
70  const double term1 = componentI.invCov * componentJ.cov;
71  const double term2 = componentJ.invCov * componentI.cov;
72  const double term3 = meanDifference * inverCovSum * meanDifference;
73  return term1 + term2 + term3;
74 }
82 inline void
85 {
86 
87  const double sumWeight = updated.weight + removed.weight;
88 
89  const double invSumWeight = 1. / sumWeight;
90  const double weightI_IJ = updated.weight * invSumWeight;
91  const double weightJ_IJ = removed.weight * invSumWeight;
92  const double meanDiff = (updated.mean - removed.mean);
93 
94  const double sumMean = weightI_IJ * updated.mean + weightJ_IJ * removed.mean;
95 
96  const double sumVariance = weightI_IJ * updated.cov +
97  weightJ_IJ * removed.cov +
98  weightI_IJ * weightJ_IJ * meanDiff * meanDiff;
99  updated.mean = sumMean;
100  updated.cov = sumVariance;
101  updated.invCov = 1. / sumVariance;
102  updated.weight = sumWeight;
103 }
104 
105 inline int
106 numDistances(const int n, float* distancesIn)
107 {
108  const int npadded = vAlgs::numPadded<STRIDEForKL>(n);
109  // Make sure the padded elements are set to max
110  std::fill(
111  distancesIn + n, distancesIn + npadded, std::numeric_limits<float>::max());
112  return npadded;
113 }
114 
115 /*
116  * This is a O(N^3) time N^2 space algorithm.
117  * It can be seen as tailored implementation for
118  * our problem of the basic algorithm
119  * for Hierarchical Clustering.
120  *
121  * We rely on the fast findMinimunIndex above
122  * and a triangular array representation to
123  * reduce the actual time for our problem
124  * (max N=72)
125  *
126  * We opt for fixed size arrays of max N(=72) elements,
127  * but dynamic arrays of ~ N*(N-1)/2 elements.
128  *
129  * Existing alternatives in the literature:
130  * - 1. O(N^3) worst-case time , O(n^2) best
131  * - 2. O(N^2 log(N)) worst-time
132  * See :
133  * "Modern hierarchical, agglomerative clustering algorithms"
134  * https://arxiv.org/abs/1109.2378
135  * or
136  * "Efficient algorithms for agglomerative hierarchical clustering methods"
137  *
138  * What we found in the past:
139  * - We seem to hit the O(N^3)/worst case of Alg 1
140  * quite often.
141  * - Alg 2 had significant overhead
142  * from the required data-structures.
143  * We could revisit these in the future.
144  */
145 
146 /*
147  * Pairwise distances implementation:
148  * Assuming N total elements 0... N-1,
149  * the pairwise distance matrix
150  * can be represented in a triangular array: <br>
151  * [ (1,0) ] <br>
152  * [ (2,0), (2,1) ] <br>
153  * [ (3,0), (3,1), (3,2)] <br>
154  * [ (4,0), (4,1), (4,2) , (4,3) <br>
155  * [.............................] <br>
156  * [(N-1,0),(N-1,1),(N-1,2),(N-1,3) ... (N-1,N-2)]<br>
157  * With size 1+2+3+ .... (N-1) = N*(N-1)/2
158  *
159  * The lexicographical storage allocation function is
160  * Loc(i,j) = i*(i-1)/2 + j <br>
161  * e.g : <br>
162  * (1,0) => 1 *(1-1)/2 + 0 => 0 <br>
163  * (2,0) => 2 *(2-1)/2 + 0 => 1 <br>
164  * (2,1) => 2 *(2-1)/2 + 1 => 2 <br>
165  * (3,0) => 3 * (3-1)/2 +0 => 3 <br>
166  * Leading to <br>
167  * [(1,0),(2,0),(2,1),(3,0),(3,1),(3,2).... (N-1,N-2)]
168  *
169  * The N-1 Rows map to the value K of the 1st element in the pair
170  * 1,2,3,..,N-1. <br>
171  * Each Row has size K and starts at array positions K*(K-1)/2 <br>
172  * e.g <br>
173  * The row for element 1 starts at array position 0. <br>
174  * The row for element 2 starts at array position 1. <br>
175  * The row for element N-1 starts at array positon (N-1)*(N-2)/2 <br>
176  *
177  * The N-1 Columns map to the value K of the second element in the pair <br>
178  * K= 0,1,2 .., N-2 <br>
179  * The array positions follows (i-1)*i/2+K <br>
180  * where i : K+1 .... N-1 [for(i=K+1;i<N;++i) <br>
181  * e.g <br>
182  * 0 appears as 2nd element in the pair at array positions [0,1,3,6...] <br>
183  * 1 appears as 2nd element in the pair at array positions [2,4,7...] <br>
184  * 2 appears as 2nd element in the pair at array positions [5,8,12....] <br>
185  * N-2 appears as 2nd element once at position [N(N-1)/2-1] <br>
186  */
187 
191 constexpr std::array<int, GSFConstants::maxComponentsAfterConvolution>
192  offset = []() {
194  std::array<int, n> tmp = {};
195  for (int i = 0; i < n; ++i) {
196  tmp[i] = (i - 1) * i / 2;
197  }
198  return tmp;
199  }();
200 
205 struct triangularToIJ
206 {
207  int8_t I = -1;
208  int8_t J = -1;
209 };
215 inline triangularToIJ
216 convert(int idx)
217 {
218  if (idx<0){
219  throw std::out_of_range("KLGaussianMixtureReduction.cxx::convert : idx is negative");
220  }
221  // We prefer to preMap the maximum 2556 elements.
222  // Alternatively one can use the following
223  // if pre-mapping becomes an issue
224  // (see https://hal.archives-ouvertes.fr/hal-02047514/document)
225  // int8_t i = std::floor((std::sqrt(1 + 8 * idx) + 1) / 2);
226  // int8_t j = idx - (i - 1) * i / 2;
227  static const std::vector<triangularToIJ> preMap = []() {
229  constexpr size_t nn = n * (n - 1) / 2;
230  std::vector<triangularToIJ> indexMap(nn);
231  for (int8_t i = 1; i < n; ++i) {
232  const int indexConst = offset[i];
233  for (int8_t j = 0; j < i; ++j) {
234  indexMap[indexConst + j] = { i, j };
235  }
236  }
237  return indexMap;
238  }();
239  return preMap[idx];
240 }
241 
248 inline void
249 calculateAllDistances(const Component1D* componentsIn,
250  float* distancesIn,
251  const int n)
252 {
253  const Component1D* components =
254  std::assume_aligned<GSFConstants::alignment>(componentsIn);
255  float* distances =
256  std::assume_aligned<GSFConstants::alignment>(distancesIn);
257  for (int i = 1; i < n; ++i) {
258  const int indexConst = offset[i];
259  const Component1D componentI = components[i];
260  for (int j = 0; j < i; ++j) {
261  const Component1D componentJ = components[j];
262  distances[indexConst + j] = symmetricKL(componentI, componentJ);
263  }
264  }
265 }
266 
276 inline int
277 updateDistances(
278  Component1D* ATH_RESTRICT componentsIn,
279  std::array<int8_t, GSFConstants::maxComponentsAfterConvolution>& mergingIndex,
280  float* ATH_RESTRICT distancesIn,
281  int minFrom,
282  int minTo,
283  int n)
284 {
285  float* distances =
286  std::assume_aligned<GSFConstants::alignment>(distancesIn);
287  Component1D* components =
288  std::assume_aligned<GSFConstants::alignment>(componentsIn);
289  // We swap the last elements with the ones indexed by minFrom.
290  // After this the remaining components we care about
291  // are n-1 which we return
292  const int last = (n - 1);
293  const int indexOffsetJ = offset[minFrom];
294  const int indexOffsetLast = offset[last];
295  // we do no need to swap the last with itself
296  if (minFrom != last) {
297  // Rows in distance matrix
298  for (int i = 0; i < minFrom; ++i) {
299  std::swap(distances[indexOffsetJ + i], distances[indexOffsetLast + i]);
300  }
301  // Columns in distance matrix
302  for (int i = minFrom + 1; i < last; ++i) {
303  const int index = offset[i] + minFrom;
304  std::swap(distances[index], distances[indexOffsetLast + i]);
305  }
306  // swap the components
307  std::swap(components[minFrom], components[last]);
308  std::swap(mergingIndex[minFrom], mergingIndex[last]);
309  }
310  // In case minTo was indexing the last now it should
311  // be indexing minFrom due to the above swapping
312  if (minTo == last) {
313  minTo = minFrom;
314  }
315  const int indexConst = offset[minTo];
316  // This is the component that has been updated
317  const Component1D componentJ = components[minTo];
318  // Rows in distance matrix
319  for (int i = 0; i < minTo; ++i) {
320  const Component1D componentI = components[i];
321  const int index = indexConst + i;
322  distances[index] = symmetricKL(componentI, componentJ);
323  }
324  // Columns in distance matrix
325  for (int i = minTo + 1; i < last; ++i) {
326  const Component1D componentI = components[i];
327  const int index = offset[i] + minTo;
328  distances[index] = symmetricKL(componentI, componentJ);
329  }
330  return last;
331 }
332 
337 findMergesImpl(const Component1DArray& componentsIn,
338  const int n,
339  const int8_t reducedSize)
340 {
341  // copy the array for internal use
342  Component1DArray copyComponents(componentsIn);
343  Component1D* components = std::assume_aligned<GSFConstants::alignment>(
344  copyComponents.components.data());
345  // Based on the inputSize n allocate enough space for the pairwise distances
346  int nn = n * (n - 1) / 2;
347  int nnpadded = vAlgs::numPadded<STRIDEForKL>(nn);
349  nnpadded, std::numeric_limits<float>::max());
350  // initial distance calculation
351  calculateAllDistances(components, distances.buffer(), n);
352  // As we merge keep track where things moved
353  std::array<int8_t, GSFConstants::maxComponentsAfterConvolution>
354  mergingIndex{};
355  std::iota(mergingIndex.begin(), mergingIndex.end(), 0);
356  // Result to be returned
357  MergeArray result{};
358  int numberOfComponentsLeft = n;
359  // merge loop
360  while (numberOfComponentsLeft > reducedSize) {
361  // find pair with minimum distance
362  const int minIndex = KLReductionFMV::vIdxOfMin(distances.buffer(), nnpadded);
363  const triangularToIJ conversion = convert(minIndex);
364  int8_t minTo = conversion.I;
365  int8_t minFrom = conversion.J;
366  // This is the convention we had so retained.
367  if (mergingIndex[minTo] < mergingIndex[minFrom]) {
368  std::swap(minTo, minFrom);
369  }
370  // prepare what to return
371  const int8_t miniToreturn = mergingIndex[minTo];
372  const int8_t minjToreturn = mergingIndex[minFrom];
373  result.merges[result.numMerges] = { miniToreturn, minjToreturn };
374  ++result.numMerges;
375  // Combine
376  combine(components[minTo], components[minFrom]);
377  // update distances
378  numberOfComponentsLeft = updateDistances(components,
379  mergingIndex,
380  distances.buffer(),
381  minFrom,
382  minTo,
383  numberOfComponentsLeft);
384 
385  // number of remaining distances padded
386  nn = offset[numberOfComponentsLeft];
387  nnpadded = numDistances(nn, distances.buffer());
388  } // end of merge while
389  return result;
390 }
391 
392 } // anonymous namespace with implementation
393 
394 namespace GSFUtils {
396 findMerges(const Component1DArray& componentsIn, const int8_t reducedSize)
397 {
398  const int n = componentsIn.numComponents;
400  reducedSize > n) {
401  throw std::runtime_error("findMerges :Invalid InputSize or reducedSize");
402  }
403  return findMergesImpl(componentsIn, n, reducedSize);
404 }
405 } // end namespace GSFUtils
get_generator_info.result
result
Definition: get_generator_info.py:21
GSFUtils::MergeArray
struct representing an array or the merges.
Definition: KLGaussianMixtureReduction.h:54
KLReductionFMV::vIdxOfMin
int vIdxOfMin(const float *distancesIn, int n)
Definition: KLGaussianMixtureReduction.cxx:33
GSFUtils::Component1DArray::numComponents
int numComponents
Definition: KLGaussianMixtureReduction.h:46
index
Definition: index.py:1
max
constexpr double max()
Definition: ap_fixedTest.cxx:33
ATH_RESTRICT
#define ATH_RESTRICT
Definition: restrict.h:31
lumiFormat.i
int i
Definition: lumiFormat.py:85
beamspotman.n
n
Definition: beamspotman.py:731
GSFConstants::maxComponentsAfterConvolution
constexpr int8_t maxComponentsAfterConvolution
The maximum size State x Bethe-Heitler components The typical number we use is the max 6x12 = 72 i....
Definition: GsfConstants.h:61
KLReductionFMV
Definition: KLGaussianMixtureReduction.cxx:28
DeMoUpdate.tmp
string tmp
Definition: DeMoUpdate.py:1167
AlignedDynArray.h
WriteCalibToCool.swap
swap
Definition: WriteCalibToCool.py:94
fill
void fill(H5::Group &out_file, size_t iterations)
Definition: test-hdf5-writer.cxx:95
restrict.h
Macro wrapping the nonstandard restrict keyword.
TMVAToMVAUtils::convert
std::unique_ptr< MVAUtils::BDT > convert(TMVA::MethodBDT *bdt, bool isRegression=true, bool useYesNoLeaf=false)
Definition: TMVAToMVAUtils.h:114
KLGaussianMixtureReduction.h
Utilities to facilitate the calculation of the KL divergence/distance between components of the mixtu...
DeMoScan.index
string index
Definition: DeMoScan.py:364
vec.h
Vectorization helpers.
copySelective.target
string target
Definition: copySelective.py:37
GSFFindIndexOfMinimum.h
convertTimingResiduals.offset
offset
Definition: convertTimingResiduals.py:71
LArNewCalib_DelayDump_OFC_Cali.idx
idx
Definition: LArNewCalib_DelayDump_OFC_Cali.py:69
GSFUtils
Dynamic array fullfilling alignment requirements.
Definition: KLGaussianMixtureReduction.cxx:394
GsfConstants.h
I
#define I(x, y, z)
Definition: MD5.cxx:116
GSFUtils::Component1D
struct representing 1D component
Definition: KLGaussianMixtureReduction.h:30
FlavorTagInference::combine
size_t combine(size_t lhs, size_t rhs)
Definition: hash.h:21
GSFUtils::findMerges
MergeArray findMerges(const Component1DArray &componentsIn, const int8_t reducedSize)
Find the order in which the components need to be merged.
Definition: KLGaussianMixtureReduction.cxx:396
GSFUtils::Component1DArray
struct representing an array of 1D component.
Definition: KLGaussianMixtureReduction.h:42
GSFUtils::AlignedDynArray
A wrapper around std::aligned_alloc.
Definition: AlignedDynArray.h:30