8namespace ParticleBinning {
10 template <typename size_type, typename bin_index_type, typename value_type, bool UseDualView, class... Properties>
11 void Histogram<size_type, bin_index_type, value_type, UseDualView, Properties...>::copyFields(const Histogram& other) {
12 debug_name_m = other.debug_name_m;
13 numBins_m = other.numBins_m;
14 totalBinWidth_m = other.totalBinWidth_m;
16 binningAlpha_m = other.binningAlpha_m;
17 binningBeta_m = other.binningBeta_m;
18 desiredWidth_m = other.desiredWidth_m;
20 histogram_m = other.histogram_m;
21 binWidths_m = other.binWidths_m;
22 postSum_m = other.postSum_m;
28 template <typename size_type, typename bin_index_type, typename value_type, bool UseDualView, class... Properties>
30 Histogram<size_type, bin_index_type, value_type, UseDualView, Properties...>::adaptiveBinningCostFunction(
31 const size_type& sumCount,
32 const value_type& sumWidth,
33 const size_type& totalNumParticles
37 Inform err("mergeBins");
38 err << "Error in adaptiveBinningCostFunction: "
39 << "sumCount = " << sumCount
40 << ", sumWidth = " << sumWidth
41 << ", totalNumParticles = " << totalNumParticles << endl;
46 value_type totalSum = static_cast<value_type>(totalNumParticles);
47 value_type sumCountNorm = sumCount / totalSum;
49 value_type penalty = sumWidth - desiredWidth_m;
50 value_type wideBinPenalty = binningAlpha_m;
51 value_type binSizeBias = binningBeta_m;
53 value_type sparse_penalty = (sumCountNorm < desiredWidth_m)
54 ? desiredWidth_m / sumCountNorm
58 if (sumCountNorm <= 0 || sumCountNorm > 1) {
59 Inform err("mergeBins");
60 err << "Error in adaptiveBinningCostFunction: "
61 << "sumCountNorm = " << sumCountNorm
62 << ", sumWidth = " << sumWidth
63 << ", totalNumParticles = " << totalNumParticles << endl;
67 return sumCountNorm*log(sumCountNorm)*sumWidth // minimize shannon entropy as a basis
68 + wideBinPenalty * pow(sumWidth, 2) // >0 wants smallest possible bin
69 // <0 wants largest possible bin
70 // Use ^2 to make it reasonably sensitive
71 + binSizeBias * pow(penalty, 2) // bias towards desiredWidth
72 + sparse_penalty; // penalty for too few particles (specifically "distribution tails")
76 template <typename size_type, typename bin_index_type, typename value_type, bool UseDualView, class... Properties>
77 Histogram<size_type, bin_index_type, value_type, UseDualView, Properties...>::hindex_transform_type
78 Histogram<size_type, bin_index_type, value_type, UseDualView, Properties...>::mergeBins() {
79 Inform m("Histogram");
82 The following if makes sure that the mergeBins function is only called if the histogram is
83 actually available on host!.
85 if constexpr (!std::is_same<typename hview_type::memory_space, Kokkos::HostSpace>::value) {
86 m << "This does not work if the histogram is not saved in a DualView, since it needs host access to the data." << endl;
88 return hindex_transform_type("error", 0);
92 hview_type oldHistHost = getHostView<hview_type>(histogram_m);
93 hwidth_view_type oldBinWHost = getHostView<hwidth_view_type>(binWidths_m);
95 const bin_index_type n = numBins_m;
97 // Should not happen, since this function is to be called after generating a very fine histogram, e.g. 128 bins
98 m << "Not merging, since n_bins = " << n << " is too small!" << endl;
99 hindex_transform_type oldToNewBinsView("oldToNewBinsView", n);
100 Kokkos::deep_copy(oldToNewBinsView, 0);
101 return oldToNewBinsView;
105 IpplTimings::startTimer(bMergeBinsT);
106 // ----------------------------------------------------------------
107 // 1) Build prefix sums on the host
108 // prefixCount[k] = sum of counts in bins [0..k-1]
109 // prefixWidth[k] = sum of widths in bins [0..k-1]
110 // ----------------------------------------------------------------
111 hview_type prefixCount("prefixCount", n+1);
112 hwidth_view_type prefixWidth("prefixWidth", n+1);
117 for (bin_index_type i = 0; i < n; ++i) {
118 prefixCount(i+1) = prefixCount(i) + oldHistHost(i);
119 prefixWidth(i+1) = prefixWidth(i) + oldBinWHost(i);
121 const size_type totalNumParticles = prefixCount(n); // Last value in prefixCount is the total number of particles
124 // ----------------------------------------------------------------
125 // 2) Dynamic Programming arrays:
126 // dp(k) = minimal total cost covering [0..k-1]
127 // prevIdx(k) = the index i that yields that minimal cost
128 // ----------------------------------------------------------------
129 Kokkos::View<value_type*, Kokkos::HostSpace> dp("dp", n+1);
130 // Kokkos::View<value_type*, Kokkos::HostSpace> dpMoment("dpMoment", n+1); // Store cumulative moments --> allow first order moment error estimation
131 Kokkos::View<int*, Kokkos::HostSpace> prevIdx("prevIdx", n+1);
133 // Initialize dp with something large.
134 // Note that it is divided by 2 to prevent and overflow should sumCount==0 and dp(i)!=0
135 // in the following dp loop (step 3).
136 // Also: /2 is fine, since dp(i) should never be "largeValue". Since this can only happen
137 // for empty binning configurations. Since the first bin always contains a particle (binning
138 // is done between min/max values) and since the function is short circuited should there be
139 // less than 2 particles, we can assume that dp(i) will never remain "largeValue" (note: i<k
141 value_type largeVal = std::numeric_limits<value_type>::max() / value_type(2);
142 for (bin_index_type k = 0; k <= n; ++k) {
147 dp(0) = value_type(0); // 0 cost to cover an empty set
148 //m << "DP arrays initialized." << endl;
151 // ----------------------------------------------------------------
152 // 3) Fill dp with an O(n^2) algorithm to find the minimal total cost
153 // ----------------------------------------------------------------
154 for (bin_index_type k = 1; k <= n; ++k) {
155 // Try all possible start indices i for the last merged bin
156 for (bin_index_type i = 0; i < k; ++i) {
157 size_type sumCount = prefixCount(k) - prefixCount(i);
158 value_type sumWidth = prefixWidth(k) - prefixWidth(i);
159 value_type segCost = largeVal;
161 // Division by 0 if called with sumCount=0. Merge anyways --> set cost to a "very large" value instead
162 segCost = adaptiveBinningCostFunction(sumCount, sumWidth, totalNumParticles);
164 value_type candidate = dp(i) + segCost;
165 if (candidate < dp(k)) {
171 //m << "DP arrays filled." << endl;
173 // dp(n) is the minimal total cost for covering [0..n-1].
174 value_type totalCost = dp(n);
175 if (totalCost >= largeVal) {
176 // Means everything was effectively "impossible" => fallback (should not happen!)
177 std::cerr << "Warning: no feasible merges found. Setting cost=0, no merges." << std::endl;
178 totalCost = value_type(0);
182 // ----------------------------------------------------------------
183 // 4) Reconstruct boundaries from prevIdx
184 // We start from k=n and step backwards until k=0
185 // ----------------------------------------------------------------
186 std::vector<int> boundaries;
187 boundaries.reserve(20); // should be sufficient for most use cases (usually aim for <10)
189 // We'll just push them in reverse
191 int start = prevIdx(cur);
193 std::cerr << "Error: prevIdx(" << cur << ") < 0. "
194 << "Merging not successful, aborted loop." << std::endl;
195 // fallback, break out
198 boundaries.push_back(start);
201 // boundaries are reversed (e.g. [startK, i2, i1, 0])
202 std::reverse(boundaries.begin(), boundaries.end());
203 // final boundary is n
204 boundaries.push_back(n);
206 // Now the number of merged bins is boundaries.size() - 1
207 size_type mergedBinsCount = static_cast<size_type>(boundaries.size()) - 1;
208 //m << "Merged bins (based on minimal cost partition): " << mergedBinsCount << ". Minimal total cost = " << totalCost << endl;
211 // ----------------------------------------------------------------
212 // 5) Build new arrays for the merged bins
213 // ----------------------------------------------------------------
214 Kokkos::View<size_type*, Kokkos::HostSpace> newCounts("newCounts", mergedBinsCount);
215 Kokkos::View<value_type*, Kokkos::HostSpace> newWidths("newWidths", mergedBinsCount);
217 for (size_type j = 0; j < mergedBinsCount; ++j) {
218 bin_index_type start = boundaries[j];
219 bin_index_type end = boundaries[j+1] - 1; // inclusive
220 size_type sumCount = prefixCount(end+1) - prefixCount(start);
221 value_type sumWidth = prefixWidth(end+1) - prefixWidth(start);
222 newCounts(j) = sumCount;
223 newWidths(j) = sumWidth;
225 //m << "New bins computed." << endl;
227 // Also generate a lookup table that maps the old bin index to the new bin index (for "rebin")
228 hindex_transform_type oldToNewBinsView("oldToNewBinsView", n);
229 for (size_type j = 0; j < mergedBinsCount; ++j) {
230 bin_index_type startIdx = boundaries[j];
231 bin_index_type endIdx = boundaries[j+1]; // exclusive
232 for (bin_index_type i = startIdx; i < endIdx; ++i) {
233 oldToNewBinsView(i) = j;
236 //m << "Lookup table generated." << endl;
239 // ----------------------------------------------------------------
240 // 6) Overwrite the old histogram arrays with the new merged ones
241 // ----------------------------------------------------------------
242 numBins_m = static_cast<bin_index_type>(mergedBinsCount);
243 instantiateHistograms();
244 //m << "New histograms instantiated." << endl;
246 // Copy the data into the new Kokkos Views (on host)
247 hview_type newHistHost = getHostView<hview_type>(histogram_m);
248 hwidth_view_type newWidthHost = getHostView<hwidth_view_type>(binWidths_m);
249 Kokkos::deep_copy(newHistHost, newCounts);
250 Kokkos::deep_copy(newWidthHost, newWidths);
251 //m << "New histograms filled." << endl;
254 // ----------------------------------------------------------------
255 // 7) If using DualView, mark host as modified & sync
256 // ----------------------------------------------------------------
257 if constexpr (UseDualView) {
261 binWidths_m.modify_host();
263 IpplTimings::startTimer(bDeviceSyncronizationT);
264 binWidths_m.sync_device();
265 IpplTimings::stopTimer(bDeviceSyncronizationT);
266 //m << "Host views modified/synced." << endl;
270 // ----------------------------------------------------------------
271 // 8) Recompute postSum for the new merged histogram
272 // ----------------------------------------------------------------
274 IpplTimings::stopTimer(bMergeBinsT);
276 m << "Re-binned from " << n << " bins down to "
277 << numBins_m << " bins. Total deviation cost = "
278 << totalCost << endl;
280 // Return the old->new index transform
281 return oldToNewBinsView;
285} // namespace ParticleBinning
287#endif // BINHISTO_TPP