OPAL (Object Oriented Parallel Accelerator Library) 2022.1
OPAL
DistributionMoments.cpp
Go to the documentation of this file.
1
2// Class DistributionMoments
3// Computes the statistics of particle distributions.
4//
5// Copyright (c) 2021, Christof Metzger-Kraus
6// All rights reserved
7//
8// This file is part of OPAL.
9//
10// OPAL is free software: you can redistribute it and/or modify
11// it under the terms of the GNU General Public License as published by
12// the Free Software Foundation, either version 3 of the License, or
13// (at your option) any later version.
14//
15// You should have received a copy of the GNU General Public License
16// along with OPAL. If not, see <https://www.gnu.org/licenses/>.
17//
18#include "DistributionMoments.h"
19
20#include "Utilities/Options.h"
21#include "Utilities/Util.h"
22
23#include "Message/GlobalComm.h"
24#include "Utility/Inform.h"
25
26#include "OpalParticle.h"
27#include "PartBunchBase.h"
28
29#include <gsl/gsl_histogram.h>
30
31#include <boost/numeric/conversion/cast.hpp>
32
33extern Inform* gmsg;
34
39
41{
42 reset();
44}
45
47{
48 computeStatistics(bunch.begin(), bunch.end());
49}
50
51void DistributionMoments::compute(const std::vector<OpalParticle>::const_iterator &first,
52 const std::vector<OpalParticle>::const_iterator &last)
53{
54 computeStatistics(first, last);
55}
56
57template<class InputIt>
58void DistributionMoments::computeMeans(const InputIt &first, const InputIt &last)
59{
60 unsigned int localNum = last - first;
61 std::vector<double> localStatistics(10);
62 std::vector<double> maxima(6, std::numeric_limits<double>::lowest());
63 for (InputIt it = first; it != last; ++ it) {
64 OpalParticle const& particle = *it;
65 if (isParticleExcluded(particle)) {
66 -- localNum;
67 continue;
68 }
69 for (unsigned int i = 0; i < 3; ++ i) {
70 maxima[2 * i] = std::max(maxima[2 * i], particle[2 * i]);
71 maxima[2 * i + 1] = std::max(maxima[2 * i + 1], -particle[2 * i]); // calculates the minimum
72 }
73
74 unsigned int l = 0;
75 for (unsigned int i = 0; i < 6; ++ i, ++ l) {
76 localStatistics[l] += particle[i];
77 }
78
79 // l = 6
80 localStatistics[l++] += particle.getTime();
81
82 double gamma = Util::getGamma(particle.getP());
83 double eKin = (gamma - 1.0) * particle.getMass();
84 localStatistics[l++] += eKin;
85 localStatistics[l++] += gamma;
86 }
87 localStatistics.back() = localNum;
88
89 allreduce(localStatistics.data(), localStatistics.size(), std::plus<double>());
90 allreduce(maxima.data(), 6, std::greater<double>());
91 totalNumParticles_m = localStatistics.back();
92
93 double perParticle = 1.0 / totalNumParticles_m;
94 unsigned int l = 0;
95
96 for (; l < 6; ++ l) {
97 centroid_m[l] = localStatistics[l];
98 }
99 for (unsigned int i = 0; i < 3; ++ i) {
100 meanR_m(i) = centroid_m[2 * i] * perParticle;
101 meanP_m(i) = centroid_m[2 * i + 1] * perParticle;
102 maxR_m(i) = maxima[2 * i];
103 minR_m(i) = -maxima[2 * i + 1];
104 }
105
106 meanTime_m = localStatistics[l++] * perParticle;
107
108 meanKineticEnergy_m = localStatistics[l++] * perParticle;
109 meanGamma_m = localStatistics[l++] * perParticle;
110}
111
112/* 2 * Dim centroids + Dim * ( 2 * Dim + 1 ) 2nd moments + 2 * Dim (3rd and 4th order moments)
113 * --> 1st order moments: 0, ..., 2 * Dim - 1
114 * --> 2nd order moments: 2 * Dim, ..., Dim * ( 2 * Dim + 1 )
115 * --> 3rd order moments: Dim * ( 2 * Dim + 1 ) + 1, ..., Dim * ( 2 * Dim + 1 ) + Dim
116 * (only, <x^3>, <y^3> and <z^3>)
117 * --> 4th order moments: Dim * ( 2 * Dim + 1 ) + Dim + 1, ..., Dim * ( 2 * Dim + 1 ) + 2 * Dim
118 *
119 * For a 6x6 matrix we have each 2nd order moment (except diagonal
120 * entries) twice. We only store the upper half of the matrix.
121 */
122template<class InputIt>
123void DistributionMoments::computeStatistics(const InputIt &first, const InputIt &last)
124{
125 reset();
126
127 computeMeans(first, last);
128
129 double perParticle = 1.0 / totalNumParticles_m;
130 std::vector<double> localStatistics(37);
131 for (InputIt it = first; it != last; ++ it) {
132 OpalParticle const& particle = *it;
133 if (isParticleExcluded(particle)) {
134 continue;
135 }
136 unsigned int l = 6;
137 for (unsigned int i = 0; i < 6; ++ i) {
138 localStatistics[i] += std::pow(particle[i] - centroid_m[i] * perParticle, 2);
139 for (unsigned int j = 0; j <= i; ++ j, ++ l) {
140 localStatistics[l] += particle[i] * particle[j];
141 }
142 }
143
144 localStatistics[l++] += std::pow(particle.getTime() - meanTime_m, 2);
145
146 for (unsigned int i = 0; i < 3; ++ i, l += 2) {
147 double r2 = std::pow(particle[i], 2);
148 localStatistics[l] += r2 * particle[i];
149 localStatistics[l + 1] += r2 * r2;
150 }
151
152 double eKin = Util::getKineticEnergy(particle.getP(), particle.getMass());
153 localStatistics[l++] += std::pow(eKin - meanKineticEnergy_m, 2);
154 localStatistics[l++] += particle.getCharge();
155 localStatistics[l++] += particle.getMass();
156 }
157
158 allreduce(localStatistics.data(), localStatistics.size(), std::plus<double>());
159
160 fillMembers(localStatistics);
161
162 computePercentiles(first, last);
163}
164
165
166template<class InputIt>
167void DistributionMoments::computePercentiles(const InputIt & first, const InputIt & last) {
169 return;
170 }
171
172 std::vector<gsl_histogram*> histograms(3);
173 // For a normal distribution the number of exchanged data between the cores is minimized
174 // if the number of histogram bins follows the following formula. Since we can't know
175 // how many particles are in each bin for the real distribution we use this formula.
176 unsigned int numBins = 3.5 * std::pow(3, std::log10(totalNumParticles_m));
177
178 Vector_t maxR;
179 for (unsigned int d = 0; d < 3; ++ d) {
180 maxR(d) = 1.0000001 * std::max(maxR_m[d] - meanR_m[d], meanR_m[d] - minR_m[d]);
181 histograms[d] = gsl_histogram_alloc(numBins);
182 gsl_histogram_set_ranges_uniform(histograms[d], 0.0, maxR(d));
183 }
184 for (InputIt it = first; it != last; ++ it) {
185 OpalParticle const& particle = *it;
186 for (unsigned int d = 0; d < 3; ++ d) {
187 gsl_histogram_increment(histograms[d], std::abs(particle[2 * d] - meanR_m[d]));
188 }
189 }
190
191 std::vector<int> localHistogramValues(3 * (numBins + 1)), globalHistogramValues(3 * (numBins + 1));
192 for (unsigned int d = 0; d < 3; ++ d) {
193 int j = 0;
194 size_t accumulated = 0;
195 std::generate(localHistogramValues.begin() + d * (numBins + 1) + 1,
196 localHistogramValues.begin() + (d + 1) * (numBins + 1),
197 [&histograms,&d,&j,&accumulated](){
198 accumulated += gsl_histogram_get(histograms[d], j ++);
199 return accumulated;});
200
201 gsl_histogram_free(histograms[d]);
202 }
203
204 allreduce(localHistogramValues.data(), globalHistogramValues.data(), 3 * (numBins + 1), std::plus<int>());
205
206 int numParticles68 = boost::numeric_cast<int>(std::floor(totalNumParticles_m * percentileOneSigmaNormalDist_m + 0.5));
207 int numParticles95 = boost::numeric_cast<int>(std::floor(totalNumParticles_m * percentileTwoSigmasNormalDist_m + 0.5));
208 int numParticles99 = boost::numeric_cast<int>(std::floor(totalNumParticles_m * percentileThreeSigmasNormalDist_m + 0.5));
209 int numParticles99_99 = boost::numeric_cast<int>(std::floor(totalNumParticles_m * percentileFourSigmasNormalDist_m + 0.5));
210
211 for (int d = 0; d < 3; ++ d) {
212 unsigned int localNum = last - first, current = 0;
213 std::vector<Vektor<double, 2>> oneDPhaseSpace(localNum);
214 for (InputIt it = first; it != last; ++ it, ++ current) {
215 OpalParticle const& particle = *it;
216 oneDPhaseSpace[current](0) = particle[2 * d];
217 oneDPhaseSpace[current](1) = particle[2 * d + 1];
218 }
219 std::sort(oneDPhaseSpace.begin(), oneDPhaseSpace.end(),
220 [d, this](Vektor<double, 2>& left, Vektor<double, 2>& right) {
221 return std::abs(left[0] - meanR_m[d]) < std::abs(right[0] - meanR_m[d]);
222 });
223
224 iterator_t endSixtyEight, endNinetyFive, endNinetyNine, endNinetyNine_NinetyNine;
225 endSixtyEight = endNinetyFive = endNinetyNine = endNinetyNine_NinetyNine = oneDPhaseSpace.end();
226
227 std::tie(sixtyEightPercentile_m[d], endSixtyEight) =
228 determinePercentilesDetail(oneDPhaseSpace.begin(),
229 oneDPhaseSpace.end(),
230 globalHistogramValues,
231 localHistogramValues,
232 d, numParticles68);
233
234 std::tie(ninetyFivePercentile_m[d], endNinetyFive) =
235 determinePercentilesDetail(oneDPhaseSpace.begin(),
236 oneDPhaseSpace.end(),
237 globalHistogramValues,
238 localHistogramValues,
239 d, numParticles95);
240
241 std::tie(ninetyNinePercentile_m[d], endNinetyNine) =
242 determinePercentilesDetail(oneDPhaseSpace.begin(),
243 oneDPhaseSpace.end(),
244 globalHistogramValues,
245 localHistogramValues,
246 d, numParticles99);
247
248 std::tie(ninetyNine_NinetyNinePercentile_m[d], endNinetyNine_NinetyNine) =
249 determinePercentilesDetail(oneDPhaseSpace.begin(),
250 oneDPhaseSpace.end(),
251 globalHistogramValues,
252 localHistogramValues,
253 d, numParticles99_99);
254
255 normalizedEps68Percentile_m[d] = computeNormalizedEmittance(oneDPhaseSpace.begin(), endSixtyEight);
256 normalizedEps95Percentile_m[d] = computeNormalizedEmittance(oneDPhaseSpace.begin(), endNinetyFive);
257 normalizedEps99Percentile_m[d] = computeNormalizedEmittance(oneDPhaseSpace.begin(), endNinetyNine);
258 normalizedEps99_99Percentile_m[d] = computeNormalizedEmittance(oneDPhaseSpace.begin(), endNinetyNine_NinetyNine);
259 }
260}
261
291std::pair<double, DistributionMoments::iterator_t>
294 const std::vector<int>& globalAccumulatedHistogram,
295 const std::vector<int>& localAccumulatedHistogram,
296 unsigned int dimension,
297 int numRequiredParticles) const {
298 unsigned int numBins = globalAccumulatedHistogram.size() / 3;
299 double percentile = 0.0;
300 iterator_t endPercentile = end;
301 for (unsigned int i = 1; i < numBins; ++ i) {
302 unsigned int idx = dimension * numBins + i;
303 if (globalAccumulatedHistogram[idx] > numRequiredParticles) {
304 iterator_t beginBin = begin + localAccumulatedHistogram[idx - 1];
305 iterator_t endBin = begin + localAccumulatedHistogram[idx];
306 unsigned int numMissingParticles = numRequiredParticles - globalAccumulatedHistogram[idx - 1];
307 unsigned int shift = 2;
308 while (numMissingParticles == 0) {
309 beginBin = begin + localAccumulatedHistogram[idx - shift];
310 numMissingParticles = numRequiredParticles - globalAccumulatedHistogram[idx - shift];
311 ++ shift;
312 }
313
314 std::vector<unsigned int> numParticlesInBin(Ippl::getNodes() + 1);
315 numParticlesInBin[Ippl::myNode() + 1] = endBin - beginBin;
316 allreduce(&(numParticlesInBin[1]), Ippl::getNodes(), std::plus<unsigned int>());
317 std::partial_sum(numParticlesInBin.begin(), numParticlesInBin.end(), numParticlesInBin.begin());
318
319 std::vector<double> positions(numParticlesInBin.back());
320 std::transform(beginBin, endBin, positions.begin() + numParticlesInBin[Ippl::myNode()],
321 [&dimension, this](Vektor<double, 2> const& particle)
322 { return std::abs(particle[0] - meanR_m[dimension]); });
323 allreduce(&(positions[0]), positions.size(), std::plus<double>());
324 std::sort(positions.begin(), positions.end());
325
326 percentile = (*(positions.begin() + numMissingParticles - 1)
327 + *(positions.begin() + numMissingParticles)) / 2;
328 for (iterator_t it = beginBin; it != endBin; ++ it) {
329 if (std::abs((*it)[0] - meanR_m[dimension]) > percentile) {
330 return std::make_pair(percentile, it);
331 }
332 }
333 endPercentile = endBin;
334 break;
335 }
336 }
337 return std::make_pair(percentile, endPercentile);
338}
339
342 double localStatistics[] = {0.0, 0.0, 0.0, 0.0};
343 localStatistics[0] = end - begin;
344 for (iterator_t it = begin; it < end; ++ it) {
345 const Vektor<double, 2>& rp = *it;
346 localStatistics[1] += rp(0);
347 localStatistics[2] += rp(1);
348 localStatistics[3] += rp(0) * rp(1);
349 }
350 allreduce(&(localStatistics[0]), 4, std::plus<double>());
351
352 double numParticles = localStatistics[0];
353 double perParticle = 1 / localStatistics[0];
354 double meanR = localStatistics[1] * perParticle;
355 double meanP = localStatistics[2] * perParticle;
356 double RP = localStatistics[3] * perParticle;
357
358 localStatistics[0] = 0.0;
359 localStatistics[1] = 0.0;
360 for (iterator_t it = begin; it < end; ++ it) {
361 const Vektor<double, 2>& rp = *it;
362 localStatistics[0] += std::pow(rp(0) - meanR, 2);
363 localStatistics[1] += std::pow(rp(1) - meanP, 2);
364 }
365 allreduce(&(localStatistics[0]), 2, std::plus<double>());
366
367 double stdR = std::sqrt(localStatistics[0] / numParticles);
368 double stdP = std::sqrt(localStatistics[1] / numParticles);
369 double sumRP = RP - meanR * meanP;
370 double squaredEps = std::pow(stdR * stdP, 2) - std::pow(sumRP, 2);
371 double normalizedEps = std::sqrt(std::max(squaredEps, 0.0));
372
373 return normalizedEps;
374}
375
376void DistributionMoments::fillMembers(std::vector<double> const& localMoments) {
377 Vector_t squaredEps, fac, sumRP;
378 double perParticle = 1.0 / totalNumParticles_m;
379
380 unsigned int l = 0;
381 for (; l < 6; l += 2) {
382 stdR_m(l / 2) = std::sqrt(localMoments[l] / totalNumParticles_m);
383 stdP_m(l / 2) = std::sqrt(localMoments[l + 1] / totalNumParticles_m);
384 }
385
386 for (unsigned int i = 0; i < 6; ++ i) {
387 for (unsigned int j = 0; j <= i; ++ j, ++ l) {
388 moments_m(i, j) = localMoments[l] * perParticle;
389 moments_m(j, i) = moments_m(i, j);
390 }
391 }
392
393 stdTime_m = localMoments[l++] * perParticle;
394
395 for (unsigned int i = 0; i < 3; ++ i, l += 2) {
396 double w1 = centroid_m[2 * i] * perParticle;
397 double w2 = moments_m(2 * i , 2 * i);
398 double w3 = localMoments[l] * perParticle;
399 double w4 = localMoments[l + 1] * perParticle;
400 double tmp = w2 - std::pow(w1, 2);
401
402 halo_m(i) = (w4 + w1 * (-4 * w3 + 3 * w1 * (tmp + w2))) / tmp;
404 }
405
406 stdKineticEnergy_m = std::sqrt(localMoments[l++] * perParticle);
407
408 totalCharge_m = localMoments[l++];
409 totalMass_m = localMoments[l++];
410
411 for (unsigned int i = 0; i < 3; ++ i) {
412 sumRP(i) = moments_m(2 * i, 2 * i + 1) - meanR_m(i) * meanP_m(i);
413 stdRP_m(i) = sumRP(i) / (stdR_m(i) * stdP_m(i));
414 squaredEps(i) = std::pow(stdR_m(i) * stdP_m(i), 2) - std::pow(sumRP(i), 2);
415 normalizedEps_m(i) = std::sqrt(std::max(squaredEps(i), 0.0));
416 }
417
418 double betaGamma = std::sqrt(std::pow(meanGamma_m, 2) - 1.0);
420}
421
423{
424 double data[] = {0.0, 0.0};
425 for (OpalParticle const& particle: bunch) {
426 data[0] += Util::getKineticEnergy(particle.getP(), particle.getMass());
427 }
428 data[1] = bunch.getLocalNum();
429 allreduce(data, 2, std::plus<double>());
430
431 meanKineticEnergy_m = data[0] / data[1];
432}
433
435{
436
438 double avgVel[3]={0.0,0.0,0.0};
439
440 //From P in \beta\gamma to get v in m/s: v = (P*c)/\gamma
441 for (OpalParticle const& particle_r: bunch_r) {
442 for(unsigned i = 0; i < 3; i++) {
443 avgVel[i] += ((particle_r.getP()[i] * Physics::c)/
444 (Util::getGamma(particle_r.getP())));
445 }
446 }
447
448 allreduce(avgVel, 3, std::plus<double>());
449
450 const double N = static_cast<double>(bunch_r.getTotalNum());
451 for(unsigned i = 0; i < 3; i++) {
452 avgVel[i]= avgVel[i]/N;
453 }
454
455 double tempAvg = 0.0;
456
457 for (OpalParticle const& particle_r: bunch_r) {
458 for(unsigned i = 0; i < 3; i++) {
459 tempAvg += std::pow((((particle_r.getP()[i] * Physics::c)/
460 (Util::getGamma(particle_r.getP()))) - avgVel[i]),2);
461 }
462 }
463 allreduce(tempAvg, 1, std::plus<double>());
464
465 // Compute the average temperature k_B T in units of kg m^2/s^2, where k_B is
466 // Boltzmann constant
467 temperature_m = (1.0/3.0) * Units::eV2kg * Units::GeV2eV * Physics::m_e * (tempAvg/N);
468
470 (density * std::pow(Physics::q_e,2)));
471
472 computePlasmaParameter(density);
473
474}
475
477{
478 // Plasma parameter: Average number of particles within the Debye sphere
479 plasmaParameter_m = (4.0/3.0) * Physics::pi * std::pow(debyeLength_m,3) * density;
480}
481
482
484{
485 std::fill(std::begin(centroid_m), std::end(centroid_m), 0.0);
486 meanR_m = 0.0;
487 meanP_m = 0.0;
488 stdR_m = 0.0;
489 stdP_m = 0.0;
490 stdRP_m = 0.0;
491 normalizedEps_m = 0.0;
492 geometricEps_m = 0.0;
493 halo_m = 0.0;
494
496 stdKineticEnergy_m = 0.0;
498
499 totalCharge_m = 0.0;
500 totalMass_m = 0.0;
502
511}
512
513
515{
516 temperature_m = 0.0;
517 debyeLength_m = 0.0;
518 plasmaParameter_m = 0.0;
519}
520
522{
523 //FIXME After issue 287 is resolved this shouldn't be necessary anymore
524 return !Options::amr
526 && particle.getId() == 0;
527}
FTps< T, N > erf(const FTps< T, N > &x, int trunc=(FTps< T, N >::EXACT))
Error function.
Definition: FTpsMath.h:385
Inform * gmsg
Definition: Main.cpp:61
PartBunchBase< T, Dim >::ConstIterator end(PartBunchBase< T, Dim > const &bunch)
PartBunchBase< T, Dim >::ConstIterator begin(PartBunchBase< T, Dim > const &bunch)
Tps< T > pow(const Tps< T > &x, int y)
Integer power.
Definition: TpsMath.h:76
Tps< T > sqrt(const Tps< T > &x)
Square root.
Definition: TpsMath.h:91
void allreduce(const T *input, T *output, int count, Op op)
Definition: GlobalComm.hpp:510
T::PETE_Expr_t::PETE_Return_t max(const PETE_Expr< T > &expr, NDIndex< D > &loc)
Definition: ReductionLoc.h:84
PETE_TUTree< FnLog10, typename T::PETE_Expr_t > log10(const PETE_Expr< T > &l)
Definition: PETE.h:735
PETE_TUTree< FnFloor, typename T::PETE_Expr_t > floor(const PETE_Expr< T > &l)
Definition: PETE.h:733
PETE_TUTree< FnAbs, typename T::PETE_Expr_t > abs(const PETE_Expr< T > &l)
constexpr double epsilon_0
The permittivity of vacuum in As/Vm.
Definition: Physics.h:51
constexpr double q_e
The elementary charge in As.
Definition: Physics.h:69
constexpr double m_e
The electron rest mass in GeV.
Definition: Physics.h:78
constexpr double c
The velocity of light in m/s.
Definition: Physics.h:45
constexpr double pi
The value of.
Definition: Physics.h:30
constexpr double GeV2eV
Definition: Units.h:68
constexpr double eV2kg
Definition: Units.h:110
bool computePercentiles
Definition: Options.cpp:111
double haloShift
The constant parameter C to shift halo, by < w^4 > / < w^2 > ^2 - C (w=x,y,z)
Definition: Options.cpp:107
bool amr
Enable AMR if true.
Definition: Options.cpp:99
double getKineticEnergy(Vector_t p, double mass)
Definition: Util.h:55
double getGamma(Vector_t p)
Definition: Util.h:45
size_t getLocalNum() const
size_t getTotalNum() const
ConstIterator end() const
ConstIterator begin() const
bool isInOPALCyclMode()
Definition: OpalData.cpp:272
static OpalData * getInstance()
Definition: OpalData.cpp:196
void computeDebyeLength(PartBunchBase< double, 3 > const &, double)
void computePlasmaParameter(double)
FMatrix< double, 6, 6 > moments_m
static const double percentileFourSigmasNormalDist_m
void computeMeans(const InputIt &, const InputIt &)
Vector_t normalizedEps99_99Percentile_m
void computeMeanKineticEnergy(PartBunchBase< double, 3 > const &)
unsigned int totalNumParticles_m
void compute(const std::vector< OpalParticle >::const_iterator &, const std::vector< OpalParticle >::const_iterator &)
std::vector< Vektor< double, 2 > >::const_iterator iterator_t
void computeStatistics(const InputIt &, const InputIt &)
double computeNormalizedEmittance(const iterator_t &begin, const iterator_t &end) const
static const double percentileThreeSigmasNormalDist_m
void computePercentiles(const InputIt &, const InputIt &)
static const double percentileTwoSigmasNormalDist_m
bool isParticleExcluded(const OpalParticle &) const
Vector_t ninetyNine_NinetyNinePercentile_m
void fillMembers(std::vector< double > const &)
std::pair< double, iterator_t > determinePercentilesDetail(const iterator_t &begin, const iterator_t &end, const std::vector< int > &globalAccumulatedHistogram, const std::vector< int > &localAccumulatedHistogram, unsigned int dimension, int numRequiredParticles) const
static const double percentileOneSigmaNormalDist_m
const Vector_t & getP() const
Get momentum.
Definition: OpalParticle.h:231
double getCharge() const
Get charge in Coulomb.
Definition: OpalParticle.h:243
double getMass() const
Get mass in GeV/c^2.
Definition: OpalParticle.h:249
double getTime() const
Get time.
Definition: OpalParticle.h:237
int64_t getId() const
Get the id of the particle.
Definition: OpalParticle.h:176
Definition: Inform.h:42
static int getNodes()
Definition: IpplInfo.cpp:670
static int myNode()
Definition: IpplInfo.cpp:691
Vektor< double, 3 > Vector_t
Definition: Vektor.h:6