Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-04-19 09:06:51

0001 // -*- C++ -*-
0002 #ifndef RIVET_Correlators_HH
0003 #define RIVET_Correlators_HH
0004 
0005 // Tools for calculating flow coefficients using correlators.
0006 //
0007 // Classes:
0008 //   Correlators: Calculates single event correlators of a given harmonic.
0009 //   Cumulants: An additional base class for flow analyses
0010 //     Use as: class MyAnalysis : public Analysis, Cumulants {};
0011 //     Includes a framework for calculating cumulants and flow coefficients
0012 //     from single event correlators, including automatic handling of
0013 //     statistical errors. Contains multiple internal sub-classes:
0014 //       CorBinBase: Base class for correlators binned in event or particle observables.
0015 //       CorSingleBin: A simple bin for correlators.
0016 //       CorBin: Has the interface of a simple bin, but does automatic calculation
0017 //         of statistical errors by a bootstrap method.
0018 //       ECorrelator: Data type for event averaged correlators.
0019 //
0020 /// @authors Vytautas Vislavicius, Christine O. Rasmussen, Christian Bierlich.
0021 
0022 #include "Rivet/Analysis.hh"
0023 #include "Rivet/Projection.hh"
0024 #include "Rivet/Projections/ParticleFinder.hh"
0025 #include "YODA/Scatter.h"
0026 
0027 namespace Rivet {
0028 
0029 
0030   /// @brief Projection for calculating correlators for flow measurements
0031   ///
0032   ///  A projection which calculates Q-vectors and P-vectors, and projects
0033   ///  them out as correlators. Implementation follows the description of
0034   ///  the ''Generic Framework'':<br>
0035   ///  Phys. Rev. C 83 (2011) 044913, arXiv: 1010.0233<br>
0036   ///  Phys. Rev. C 89 (2014) 064904, arXiv: 1312.3572
0037   class Correlators : public Projection {
0038   public:
0039 
0040     /// Constructor
0041     ///
0042     /// @param fsp The FinalState projection that the correlators should be constructed from
0043     ///
0044     /// @param nMaxIn The maximal sum of harmonics, e.g. for<br>
0045     /// c_2{2} = {2,-2} = 2 + 2 = 4<br>
0046     /// c_2{4} = {2,2,-2,-2} = 2 + 2 + 2 + 2 = 8<br>
0047     /// c_4{2} = {4,-4} = 4 + 4 = 8<br>
0048     /// c_4{4} = {4,4,-4,-4} = 4 + 4 + 4 + 4 = 16.
0049     ///
0050     /// @param pMaxIn The maximal number of particles you want to correlate
0051     ///
0052     /// @param pTbinEdgesIn The (lower) edges of pT bins, the last one the upper edge of the final bin.
0053     Correlators(const ParticleFinder& fsp, int nMaxIn = 2,
0054                 int pMaxIn = 0, vector<double> pTbinEdgesIn = {});
0055 
0056     // Constructor which takes an Estimate1D to estimate bin edges.
0057     Correlators(const ParticleFinder& fsp, int nMaxIn,
0058                 int pMaxIn, const YODA::Estimate1D hIn);
0059 
0060     /// Import to avoid warnings about overload-hiding
0061     using Projection::operator =;
0062 
0063 
0064     /// @brief Integrated correlator of @a n harmonic, with the
0065     /// number of powers being the size of @a n.
0066     /// E.G. @a n should be:<br>
0067     /// <<2>>_2 => n = {2, -2}<br>
0068     /// <<4>>_2 => n = {2, 2, -2, -2}<br>
0069     /// <<2>>_4 => n = {4, -4}<br>
0070     /// <<4>>_4 => n = {4, 4, -4, 4}, and so on.
0071     const pair<double,double> intCorrelator(vector<int> n) const;
0072 
0073     /// @brief pT differential correlator of @a n harmonic, for number of powers @a n.size()
0074     ///
0075     /// The method can include overflow/underflow bins in the beginning/end of
0076     /// the returned vector, by toggling @a overflow = true.
0077     const vector<pair<double,double>> pTBinnedCorrelators(vector<int> n,
0078                                                           bool overflow = false) const;
0079 
0080     /// @brief Integrated correlator of @a n1 harmonic, for number of powers @a n1.size()
0081     ///
0082     /// This method imposes an eta gap, correlating with another phase space,
0083     /// where another Correlators projection @a other should be defined. The
0084     /// harmonics of the other phase space is given as @a n2.
0085     ///
0086     /// To get e.g. integrated <<4>>_2, @a n1 should be:
0087     /// n1 = {2, 2} and n2 = {-2, -2}
0088     const pair<double,double> intCorrelatorGap(const Correlators& other,
0089                                                vector<int> n1, vector<int> n2) const;
0090 
0091     /// @brief pT differential correlators of @a n1 harmonic, for number @a n1.size()
0092     ///
0093     /// This method imposes an eta gap, correlating with another phase space,
0094     /// where another Correlators projection @a other should be defined. The
0095     /// harmonics of the other phase space is given as @a n2.
0096     ///
0097     /// To get e.g. differential <<4'>_2, @a n1 should be: n1 = {2, 2} and
0098     /// @a n2: n2 = {-2, -2}.  To get e.g. differential <<2'>>_4, @a n1
0099     /// should be: n1 = {4} and @a n2: n2 = {-4}.  The method can include
0100     /// overflow/underflow bins in the beginning/end of the returned vector, by
0101     /// toggling @a overflow = true.
0102     const vector<pair<double,double>>
0103     pTBinnedCorrelatorsGap(const Correlators& other, vector<int> n1, vector<int> n2, bool overflow=false) const;
0104 
0105     /// @brief Construct a harmonic vectors from @a n harmonics
0106     /// and @a m number of particles.
0107     ///
0108     /// @todo In C++14 this can be done much nicer with TMP.
0109     static vector<int> hVec(int n, int m) {
0110       if (m % 2 != 0) {
0111         cout << "Harmonic Vector: Number of particles must be an even number." << endl;
0112         return {};
0113       }
0114       vector<int> ret = {};
0115       for (int i = 0; i < m; ++i) {
0116         if (i < m/2) ret.push_back(n);
0117         else ret.push_back(-n);
0118       }
0119       return ret;
0120     }
0121 
0122     /// @brief Return the maximal values for n, p to be used in the
0123     /// constructor of @c Correlators(xxx, nMax, pMax, xxxx)
0124     static pair<int,int> getMaxValues(vector< vector<int> >& hList){
0125       int nMax = 0, pMax = 0;
0126       for (vector<int> h : hList) {
0127         int tmpN = 0, tmpP = 0;
0128         for ( int i = 0; i < int(h.size()); ++i) {
0129           tmpN += abs(h[i]);
0130           ++tmpP;
0131         }
0132         if (tmpN > nMax) nMax = tmpN;
0133         if (tmpP > pMax) pMax = tmpP;
0134       }
0135       return make_pair(nMax,pMax);
0136     }
0137 
0138     // Clone on the heap.
0139     RIVET_DEFAULT_PROJ_CLONE(Correlators);
0140 
0141 
0142   protected:
0143 
0144     /// @brief Loop over array and calculates Q and P vectors if needed
0145     void project(const Event& e);
0146 
0147     /// @brief Compare to other projection, testing harmonics, pT bins and underlying final state similarity
0148     CmpState compare(const Projection& p) const {
0149       const Correlators* other = dynamic_cast<const Correlators*>(&p);
0150       if (nMax != other->nMax) return CmpState::NEQ;
0151       if (pMax != other->pMax) return CmpState::NEQ;
0152       if (pTbinEdges != other->pTbinEdges) return CmpState::NEQ;
0153       return mkPCmp(p, "FS");
0154     }
0155 
0156     /// @brief Calculate correlators from one particle
0157     void fillCorrelators(const Particle& p, const double& weight);
0158 
0159     /// @brief Return a Q-vector.
0160     const complex<double> getQ(int n, int p) const {
0161       bool isNeg = (n < 0);
0162       if (isNeg) return conj( qVec[abs(n)][p] );
0163       else       return qVec[n][p];
0164     }
0165 
0166     /// @brief Return a P-vector
0167     const complex<double> getP(int n, int p, double pT = 0.) const {
0168       bool isNeg = (n < 0);
0169       map<double, Vec2D>::const_iterator pTitr = pVec.lower_bound(pT);
0170       if (pTitr == pVec.end()) return DBL_NAN;
0171       if (isNeg) return conj( pTitr->second[abs(n)][p] );
0172       else       return pTitr->second[n][p];
0173     }
0174 
0175 
0176   private:
0177 
0178     /// @brief Find correlators by recursion
0179     ///
0180     /// Order = M (# of particles), n's are harmonics, p's are the powers of the weights
0181     const complex<double> recCorr(int order, vector<int> n,
0182                                   vector<int> p, bool useP, double pT = 0.) const;
0183 
0184     /// @brief Two-particle correlator
0185     ///
0186     /// Cf. eq. (19) p6. Flag if p-vectors or q-vectors should be used to
0187     /// calculate the correlator.
0188     const complex<double> twoPartCorr(int n1, int n2, int p1 = 1,
0189                                       int p2 = 1, double pT = 0., bool useP = false) const;
0190 
0191     /// Set elements in vectors to zero
0192     void setToZero();
0193 
0194     /// Shorthands for setting and comparing to zero
0195     const complex<double> _ZERO = {0., 0.};
0196     const double _TINY = 1e-10;
0197 
0198     /// Shorthand typedefs for vec<vec<complex>>.
0199     typedef vector< vector<complex<double>> > Vec2D;
0200 
0201     /// Define Q-vectors and P-vectors
0202     Vec2D qVec; // Q[n][p]
0203     map<double, Vec2D> pVec; // p[pT][n][p]
0204 
0205     /// The max values of n and p to be calculated.
0206     int nMax, pMax;
0207 
0208     /// pT bin-edges
0209     vector<double> pTbinEdges;
0210 
0211     bool isPtDiff;
0212 
0213   };
0214 
0215 
0216 
0217   /// @brief Tools for flow analyses
0218   ///
0219   /// The following are helper classes to construct event averaged correlators
0220   /// as well as cummulants and flow coefficents from the basic event
0221   ///  correlators defined above. They are all encapsulated in a Cumulants
0222   ///  class, which can be used as a(nother) base class for flow analyses,
0223   ///  to ensure access.
0224   class CumulantAnalysis : public Analysis {
0225   private:
0226 
0227     // Number of bins used for bootstrap calculation of statistical
0228     // uncertainties. It is hard coded, and shout NOT be changed unless there
0229     // are good reasons to do so.
0230     static const int BOOT_BINS = 9;
0231 
0232     // Enum for choosing the method of error calculation.
0233     enum Error {
0234       VARIANCE,
0235       ENVELOPE
0236     };
0237 
0238     // The desired error method. Can be changed in the analysis constructor
0239     // by setting it appropriately.
0240     Error errorMethod;
0241 
0242 
0243     /// @brief Base class for correlator bins.
0244     class CorBinBase {
0245     public:
0246       CorBinBase() {}
0247       virtual ~CorBinBase() {};
0248       // Derived class should have fill and mean defined.
0249       virtual void fill(const pair<double, double>& cor, const double& weight = 1.0) = 0;
0250       virtual double mean() const = 0;
0251     };
0252 
0253 
0254     /// @brief The basic quantity filled in an ECorrelator
0255     ///
0256     /// It is a simple counter with an even simpler structure than normal
0257     /// YODA type DBNs, but added functionality to test for out of
0258     /// bounds correlators.
0259     class CorSingleBin : public CorBinBase {
0260     public:
0261 
0262       /// Default constructor
0263       CorSingleBin()
0264         : _sumWX(0.), _sumW(0.), _sumW2(0.), _numEntries(0.)
0265       {  }
0266 
0267       // Destructor does nothing but must be implemented (?)
0268       ~CorSingleBin() {}
0269 
0270       /// @brief Fill a correlator bin with the return type from a Correlator
0271       ///
0272       /// The pair gives the numerator and denominator of \<M\>_event.
0273       void fill(const pair<double, double>& cor, const double& weight = 1.0) {
0274         // Test if denominator for the single event average is zero.
0275         if (cor.second < 1e-10) return;
0276         // The single event average <M> is then cor.first / cor.second.
0277         // With weights this becomes just:
0278         _sumWX += cor.first * weight;
0279         _sumW += weight * cor.second;
0280         _sumW2 += weight * weight * cor.second * cor.second;
0281         _numEntries += 1.;
0282       }
0283 
0284       /// Mean
0285       double mean() const {
0286         if (_sumW < 1e-10) return 0;
0287         return _sumWX / _sumW;
0288       }
0289 
0290       /// Sum of weights
0291       double sumW() const {
0292         return _sumW;
0293       }
0294 
0295       /// Sum of weights-squared
0296       double sumW2() const {
0297         return _sumW2;
0298       }
0299 
0300       /// Sum of weight * X
0301       double sumWX() const {
0302         return _sumWX;
0303       }
0304 
0305       /// Number of entries
0306       double numEntries() const {
0307         return _numEntries;
0308       }
0309 
0310       /// Add to all the entries
0311       void addContent(double ne, double sw, double sw2, double swx) {
0312         _numEntries += ne;
0313         _sumW += sw;
0314         _sumW2 += sw2;
0315         _sumWX += swx;
0316       }
0317 
0318 
0319     private:
0320 
0321       double _sumWX, _sumW, _sumW2, _numEntries;
0322 
0323     };
0324 
0325 
0326     /// @brief The basic bin quantity in ECorrelators.
0327     ///
0328     /// Consists of several CorSingleBins, to facilitate
0329     /// bootstrapping calculation of statistical uncertainties.
0330     class CorBin : public CorBinBase {
0331     public:
0332 
0333       /// @brief Constructor.
0334       ///
0335       /// @a nBins signifies the period of the bootstrap
0336       /// calculation, and should never be changed here, but in its definition
0337       /// above -- and only if there are good reasons to do so.
0338       CorBin() : binIndex(0), nBins(BOOT_BINS) {
0339         for(size_t i = 0; i < nBins; ++i) bins.push_back(CorSingleBin());
0340       }
0341 
0342       // Destructor does nothing but must be implemented (?)
0343       ~CorBin() {}
0344 
0345       /// Fill the correct underlying bin and take a step
0346       void fill(const pair<double, double>& cor, const double& weight = 1.0) {
0347         // Test if denominator for the single event average is zero.
0348         if (cor.second < 1e-10) return;
0349         // Fill the correct bin.
0350         bins[binIndex].fill(cor, weight);
0351         if (binIndex == nBins - 1) binIndex = 0;
0352         else ++binIndex;
0353       }
0354 
0355       /// Calculate the total sample mean with all available statistics
0356       double mean() const {
0357         double sow = 0;
0358         double sowx = 0;
0359         for (auto b : bins) {
0360           if (b.sumW() < 1e-10) continue;
0361           sow += b.sumW();
0362           sowx += b.sumWX();
0363         }
0364         return sowx / sow;
0365       }
0366 
0367       /// Return the bins
0368       const vector<CorSingleBin>& getBins() const {
0369         return bins;
0370       }
0371 
0372       /// Return the bins as pointers to the base class
0373       template<class T=CorBinBase>
0374       vector<T*> getBinPtrs() {
0375         vector<T*> ret(bins.size());
0376         transform(bins.begin(), bins.end(), ret.begin(), [](CorSingleBin& b) {return &b;});
0377         return ret;
0378       }
0379 
0380       /// Return the bins as pointers to the base class
0381       template<class T=CorBinBase>
0382       vector<const T*> getBinPtrs() const {
0383         vector<const T*> ret(bins.size());
0384         transform(bins.begin(), bins.end(), ret.begin(), [](const CorSingleBin& b) {return &b;});
0385         return ret;
0386       }
0387 
0388     private:
0389 
0390       vector<CorSingleBin> bins;
0391       size_t binIndex;
0392       size_t nBins;
0393 
0394     };
0395 
0396 
0397   public:
0398 
0399     /// @brief A helper class to calculate all event averages of correlators
0400     ///
0401     /// Useful to construct cumulants.
0402     /// It can be binned in any variable.
0403     class ECorrelator {
0404     public:
0405 
0406       /// @brief Constructor. Takes as argument the desired harmonic and number
0407       /// of correlated particles as a generic framework style vector, eg,
0408       /// {2, -2} for <<2>>_2, no binning.
0409       ///
0410       /// @todo Implement functionality for this if needed.
0411       //ECorrelator(vector<int> h)  : h1(h), h2({}),
0412       //  binX(0), binContent(0), reference() {
0413       //};
0414 
0415       /// @brief Constructor
0416       ///
0417       /// Takes as argument the desired harmonic and number
0418       /// of correlated particles as a generic framework style vector, e.g.
0419       /// {2, -2} for <<2>>_2 and binning.
0420       ECorrelator(const vector<int>& h, const vector<double>& binIn)
0421         : h1(h), h2({}), binX(binIn), binContent(binIn.size() - 1), reference()
0422       {  }
0423 
0424       /// @brief Constructor for gapped correlator
0425       ///
0426       /// Takes as argument the desired harmonics for the two final states, and
0427       /// binning.
0428       ECorrelator(const vector<int>& h1In, const vector<int>& h2In, const vector<double>& binIn)
0429         : h1(h1In), h2(h2In), binX(binIn), binContent(binIn.size() - 1), reference()
0430       {  }
0431 
0432       /// @brief Fill the appropriate bin given an input (per event) observable, e.g. centrality
0433       void fill(const double& obs, const Correlators& c, double weight=1.0) {
0434         int index = getBinIndex(obs);
0435         if (index < 0) return;
0436         binContent[index].fill(c.intCorrelator(h1), weight);
0437       }
0438 
0439       /// @brief Fill the appropriate bin given an input (per event)
0440       /// observable, e.g. centrality, with a rapidity gap between
0441       /// two Correlators.
0442       void fill(const double& obs, const Correlators& c1, const Correlators& c2, double weight=1.0) {
0443         if (!h2.size()) {
0444           cout << "Trying to fill gapped correlator, but harmonics behind the gap (h2) are not given!" << endl;
0445           return;
0446         }
0447         int index = getBinIndex(obs);
0448         if (index < 0) return;
0449         binContent[index].fill(c1.intCorrelatorGap(c2, h1, h2), weight);
0450       }
0451 
0452       /// @brief Fill the bins with the appropriate correlator
0453       ///
0454       /// Takes the binning directly from the Correlators object, and fills
0455       /// also the reference flow.
0456       void fill(const Correlators& c, const double weight=1.0) {
0457         vector< pair<double, double> > diffCorr = c.pTBinnedCorrelators(h1);
0458         // We always skip overflow when calculating the all-event average.
0459         if (diffCorr.size() != binX.size() - 1)
0460           cout << "Tried to fill event with wrong binning (ungapped)" << endl;
0461         for (size_t i = 0; i < diffCorr.size(); ++i) {
0462           int index = getBinIndex(binX[i]);
0463           if (index < 0) return;
0464           binContent[index].fill(diffCorr[i], weight);
0465         }
0466         reference.fill(c.intCorrelator(h1), weight);
0467       }
0468 
0469       /// @brief Fill bins with the appropriate correlator, and a rapidity gap between two Correlators.
0470       ///
0471       /// Takes the binning directly from the Correlators object, and also the
0472       /// reference flow.
0473       void fill(const Correlators& c1, const Correlators& c2, const double weight = 1.0) {
0474         if (!h2.size()) {
0475           cout << "Trying to fill gapped correlator, but harmonics behind "
0476             "the gap (h2) are not given!" << endl;
0477           return;
0478         }
0479         vector< pair<double, double> > diffCorr = c1.pTBinnedCorrelatorsGap(c2, h1, h2);
0480         // We always skip overflow when calculating the all event average.
0481         if (diffCorr.size() != binX.size() - 1)
0482           cout << "Tried to fill event with wrong binning (gapped)" << endl;
0483         for (size_t i = 0; i < diffCorr.size(); ++i) {
0484           int index = getBinIndex(binX[i]);
0485           if (index < 0) return;
0486           binContent[index].fill(diffCorr[i], weight);
0487         }
0488         reference.fill(c1.intCorrelatorGap(c2, h1, h2), weight);
0489       }
0490 
0491       /// Get the bin contents
0492       const vector<CorBin>& getBins() const {
0493         return binContent;
0494       }
0495 
0496       /// Return the bins as pointers to the base class
0497       vector<const CorBinBase*> getBinPtrs() const {
0498         vector<const CorBinBase*> ret(binContent.size());
0499         transform(binContent.begin(), binContent.end(), ret.begin(), [](const CorBin& b) {return &b;});
0500         return ret;
0501       }
0502 
0503       /// Get a copy of the bin x-values
0504       const vector<double>& getBinX() const {
0505         return binX;
0506       }
0507 
0508       /// Get a copy of the @a h1 harmonic vector
0509       const vector<int>& getH1() const {
0510         return h1;
0511       }
0512 
0513       /// Get a copy of the @a h2 harmonic vector
0514       const vector<int>& getH2() const {
0515         return h2;
0516       }
0517 
0518       /// Replace reference flow bin with another, e.g. calculated in another phase space or with other pid
0519       void setReference(CorBin refIn) {
0520         reference = refIn;
0521       }
0522 
0523       /// Extract the reference flow from a differential event averaged correlator.
0524       CorBin getReference() const {
0525         if (reference.mean() < 1e-10)
0526           cout << "Warning: ECorrelator, reference bin is zero." << endl;
0527         return reference;
0528       }
0529 
0530       /// @brief Set the @a prIn list of profile histograms associated with the internal bins
0531       ///
0532       void setProfs(vector<string> prIn) {
0533         profs = prIn;
0534       }
0535 
0536       /// @brief Fill bins with content from preloaded histograms
0537       bool fillFromProfile(YODA::AnalysisObjectPtr yao, string name) {
0538         auto refs = reference.getBinPtrs<CorSingleBin>();
0539         for (size_t i = 0; i < profs.size(); ++i) {
0540           if (yao->path() == "/RAW/"+name+"/TMP/"+profs[i]) {
0541             YODA::Profile1DPtr pPtr = dynamic_pointer_cast<YODA::Profile1D>(yao);
0542             for (size_t j = 0; j < binX.size() - 1; ++j) {
0543               const YODA::Dbn2D& pBin = pPtr->binAt(binX[j]);
0544               auto tmp  = binContent[j].getBinPtrs<CorSingleBin>();
0545               tmp[i]->addContent(pBin.numEntries(), pBin.sumW(), pBin.sumW2(),
0546                                  pBin.sumWY());
0547             }
0548             // Get the reference flow from the underflow bin of the histogram.
0549             const YODA::Dbn2D& uBin = pPtr->bin(0);
0550             refs[i]->addContent(uBin.numEntries(), uBin.sumW(), uBin.sumW2(),
0551                                 uBin.sumWY());
0552             return true;
0553           }
0554         } // End loop of bootstrapped correlators.
0555         return false;
0556       }
0557 
0558     private:
0559 
0560       // Get correct bin index for a given @param obs value
0561       int getBinIndex(const double& obs) const {
0562         // Find the correct index of binContent.
0563         // If we are in overflow, just skip.
0564         if (obs >= binX.back()) return -1;
0565         // If we are in underflow, ditto.
0566         if (obs < binX[0]) return -1;
0567         int index = 0;
0568         for (int i = 0, N = binX.size() - 1; i < N; ++i, ++index)
0569           if (obs >= binX[i] && obs < binX[i + 1]) break;
0570         return index;
0571       }
0572 
0573       // The harmonics vectors.
0574       vector<int> h1;
0575       vector<int> h2;
0576 
0577       // The bins.
0578       vector<double> binX;
0579       vector<CorBin> binContent;
0580       // The reference flow.
0581       CorBin reference;
0582     public:
0583       // The profile histograms associated with the CorBins for streaming.
0584       vector <string> profs;
0585 
0586     };
0587 
0588 
0589     /// Get the correct max N and max P for the set of booked correlators
0590     const pair<int, int> getMaxValues() const {
0591       vector< vector<int>> harmVecs;
0592       for ( auto eItr = eCorrPtrs.begin(); eItr != eCorrPtrs.end(); ++eItr) {
0593         const vector<int>& h1 = (*eItr)->getH1();
0594         const vector<int>& h2 = (*eItr)->getH2();
0595         if (h1.size() > 0)  harmVecs.push_back(h1);
0596         if (h2.size() > 0)  harmVecs.push_back(h2);
0597       }
0598       if (harmVecs.size() == 0) {
0599         cout << "Warning: You tried to extract max values from harmonic "
0600           "vectors, but have not booked any." << endl;
0601         return pair<int,int>();
0602       }
0603       return Correlators::getMaxValues(harmVecs);
0604     }
0605 
0606     /// Typedef of shared pointer to ECorrelator.
0607     typedef shared_ptr<ECorrelator> ECorrPtr;
0608 
0609     /// @brief Book an ECorrelator in the same way as a histogram
0610     /// @todo Rename to book(ECorrPtr, ...)
0611     ECorrPtr bookECorrelator(const string name, const vector<int>& h, const YODA::Estimate1D& hIn) {
0612       vector<double> binIn;
0613       const YODA::Scatter2D s = hIn.mkScatter();
0614       for (const auto& p : s.points())  binIn.push_back(p.xMin());
0615       binIn.push_back(s.points().back().xMax());
0616       return bookECorrelator(name, h, binIn);
0617     }
0618 
0619     /// @brief Book an ECorrelator in the same way as a histogram
0620     /// @todo Rename to book(ECorrPtr, ...)
0621     ECorrPtr bookECorrelator(const string name, const vector<int>& h, const vector<double>& binIn) {
0622       ECorrPtr ecPtr = ECorrPtr(new ECorrelator(h, binIn));
0623       vector<string> eCorrProfs;
0624       for (int i = 0; i < BOOT_BINS; ++i) {
0625         Profile1DPtr tmp;
0626         book(tmp,"TMP/"+name+"-"+to_string(i),binIn);
0627         eCorrProfs.push_back(name+"-"+to_string(i));
0628       }
0629       ecPtr->setProfs(eCorrProfs);
0630       eCorrPtrs.push_back(ecPtr);
0631       return ecPtr;
0632     }
0633 
0634     /// @brief Book a gapped ECorrelator with two harmonic vectors
0635     /// @todo Rename to book(ECorrPtr, ...)
0636     ECorrPtr bookECorrelator(const string name, const vector<int>& h1,
0637                              const vector<int>& h2, const vector<double>& binIn) {
0638       ECorrPtr ecPtr = ECorrPtr(new ECorrelator(h1, h2, binIn));
0639       vector<string> eCorrProfs;
0640       Profile1DPtr tmp;
0641       for (int i = 0; i < BOOT_BINS; ++i) {
0642         book(tmp, "TMP/"+name+"-"+to_string(i), binIn);
0643         eCorrProfs.push_back(name+"-"+to_string(i));
0644       }
0645       ecPtr->setProfs(eCorrProfs);
0646       eCorrPtrs.push_back(ecPtr);
0647       return ecPtr;
0648     }
0649 
0650     /// @brief Book a gapped ECorrelator with two harmonic vectors
0651     /// @todo Rename to book(ECorrPtr, ...)
0652     ECorrPtr bookECorrelator(const string& name, const vector<int>& h1,
0653                              const vector<int>& h2, const YODA::Estimate1D& hIn) {
0654       vector<double> binIn;
0655       const YODA::Scatter2D s = hIn.mkScatter();
0656       for (const auto& p : s.points())  binIn.push_back(p.xMin());
0657       binIn.push_back(s.points().back().xMax());
0658       return bookECorrelator(name, h1, h2, binIn);
0659     }
0660 
0661     /// Shorthand for gapped correlators, splitting the harmonic vector into negative and
0662     /// positive components.
0663     ///
0664     /// @todo Rename to book(ECorrPtr, ...)
0665     ECorrPtr bookECorrelatorGap(const string& name, const vector<int>& h,
0666                                 const YODA::Estimate1D& hIn) {
0667       const vector<int> h1(h.begin(), h.begin() + h.size() / 2);
0668       const vector<int> h2(h.begin() + h.size() / 2, h.end());
0669       return bookECorrelator(name, h1, h2, hIn);
0670     }
0671 
0672     /// @brief Templated version of correlator booking which takes
0673     /// @a N desired harmonic and @a M number of particles, and given bins.
0674     ///
0675     /// @todo Rename to book(ECorrPtr, ...)
0676     template<unsigned int N, unsigned int M>
0677     ECorrPtr bookECorrelator(const string& name, const vector<double>& binIn) {
0678       return bookECorrelator(name, Correlators::hVec(N, M), binIn);
0679     }
0680 
0681     /// @brief Templated version of correlator booking which takes
0682     /// @a N desired harmonic and @a M number of particles
0683     ///
0684     /// @todo Rename to book(ECorrPtr, ...)
0685     template<unsigned int N, unsigned int M>
0686     ECorrPtr bookECorrelator(const string& name, const YODA::Estimate1D& hIn) {
0687       return bookECorrelator(name, Correlators::hVec(N, M), hIn);
0688     }
0689 
0690     /// @brief Templated version of gapped correlator booking which takes
0691     /// @a N desired harmonic and @a M number of particles
0692     ///
0693     /// @todo Rename to book(ECorrPtr, ...)
0694     template<unsigned int N, unsigned int M>
0695     ECorrPtr bookECorrelatorGap(const string& name, const YODA::Estimate1D& hIn) {
0696       const vector<int> h = Correlators::hVec(N,M);
0697       const vector<int> h1(h.begin(), h.begin() + h.size() / 2);
0698       const vector<int> h2(h.begin() + h.size() / 2, h.end());
0699       return bookECorrelator(name, h1, h2, hIn);
0700 
0701     }
0702 
0703   protected:
0704 
0705     // Bookkeeping of the event averaged correlators.
0706     list<ECorrPtr> eCorrPtrs;
0707 
0708 
0709   public:
0710 
0711     /// @brief Constructor
0712     ///
0713     /// Use CumulantAnalysis as base class for the analysis to have access to
0714     /// functionality.
0715     CumulantAnalysis(const string& n)
0716       : Analysis(n), errorMethod(VARIANCE)
0717     {  }
0718 
0719     /// @brief Helper method for turning correlators into Scatter2Ds
0720     ///
0721     /// Takes @a h a pointer to the resulting Scatter2D, @a binx the x-bins and
0722     /// a function @a func defining the transformation.  Makes no attempt at
0723     /// statistical errors.  See usage in the methods below.  Can also be used
0724     /// directly in the analysis if a user wants to perform an unforseen
0725     /// transformation from correlators to Scatter2D.
0726     template<typename T>
0727     static void fillScatter(Scatter2DPtr h, const vector<double>& binx, const T& func) {
0728       vector<YODA::Point2D> points;
0729       // Test if we have proper bins from a booked histogram.
0730       bool hasBins = (h->points().size() > 0);
0731       for (int i = 0, N = binx.size() - 1; i < N; ++i) {
0732         double xMid = (binx[i] + binx[i + 1]) / 2.0;
0733         double xeMin = fabs(xMid - binx[i]);
0734         double xeMax = fabs(xMid - binx[i + 1]);
0735         if (hasBins) {
0736           xMid = h->points()[i].x();
0737           xeMin = h->points()[i].xErrMinus();
0738           xeMax = h->points()[i].xErrPlus();
0739         }
0740         double yVal = func(i);
0741         if (std::isnan(yVal)) yVal = 0.;
0742         double yErr = 0;
0743         points.push_back(YODA::Point2D(xMid, yVal, xeMin, xeMax, yErr, yErr));
0744       }
0745       h->reset();
0746       h->points().clear();
0747       for (int i = 0, N = points.size(); i < N; ++i) h->addPoint(points[i]);
0748     }
0749 
0750     /// @brief Helper method for turning correlators into Scatter2Ds with error estimates
0751     ///
0752     /// Takes @a h a pointer to the resulting Scatter2D, @a binx the x-bins, a
0753     /// function @a func defining the transformation and a vector of errors @a
0754     /// err.  See usage in the methods below.  Can also be used directly in the
0755     /// analysis if a user wants to perform an unforseen transformation from
0756     /// correlators to Scatter2D.
0757     template<typename F>
0758     void fillScatter(Scatter2DPtr h, const vector<double>& binx, const F func,
0759                      vector<pair<double, double> >& yErr) const {
0760       vector<YODA::Point2D> points;
0761       // Test if we have proper bins from a booked histogram.
0762       const bool hasBins = (h->points().size() > 0);
0763       for (int i = 0, N = binx.size() - 1; i < N; ++i) {
0764         double xMid = (binx[i] + binx[i + 1]) / 2.0;
0765         double xeMin = fabs(xMid - binx[i]);
0766         double xeMax = fabs(xMid - binx[i + 1]);
0767         if (hasBins) {
0768           xMid = h->points()[i].x();
0769           xeMin = h->points()[i].xErrMinus();
0770           xeMax = h->points()[i].xErrPlus();
0771         }
0772         const double yVal = func(i);
0773         if (std::isnan(yVal))
0774           points.push_back(YODA::Point2D(xMid, 0., xeMin, xeMax,0., 0.));
0775         else
0776           points.push_back(YODA::Point2D(xMid, yVal, xeMin, xeMax,
0777                                          yErr[i].first, yErr[i].second));
0778       }
0779       h->reset();
0780 
0781       for (int i = 0, N = points.size(); i < N; ++i) {
0782         h->addPoint(points[i]);
0783       }
0784     }
0785 
0786 
0787     /// @brief Take the @a n th power of all points in @a hIn and put the result in @a hOut
0788     ///
0789     /// Optionally put a @a k constant below the root.
0790     static void nthPow(Scatter2DPtr hOut, const Scatter2DPtr hIn, const double& n, const double& k = 1.0) {
0791       if (n == 0 || n == 1) {
0792         cout << "Error: Do not take the 0th or 1st power of a Scatter2D,"
0793           " use scale instead." << endl;
0794         return;
0795       }
0796       if (hIn->points().size() != hOut->points().size()) {
0797         cout << "nthRoot: Scatterplots: " << hIn->name() << " and " <<
0798           hOut->name() << " not initialized with same length." << endl;
0799         return;
0800       }
0801       vector<YODA::Point2D> points;
0802       // The error pre-factor is k^(1/n) / n by Taylors formula.
0803       double eFac = pow(k,1./n)/n;
0804       for (auto b : hIn->points()) {
0805         double yVal = pow(k * b.y(),n);
0806         if (std::isnan(yVal))
0807           points.push_back(YODA::Point2D(b.x(), 0., b.xErrMinus(),
0808                                          b.xErrPlus(), 0, 0 ));
0809         else {
0810           double yemin = abs(eFac * pow(yVal,1./(n - 1.))) * b.yErrMinus();
0811           if (std::isnan(yemin)) yemin = b.yErrMinus();
0812           double yemax = abs(eFac * pow(yVal,1./(n - 1.))) * b.yErrPlus();
0813           if (std::isnan(yemax)) yemax = b.yErrPlus();
0814           points.push_back(YODA::Point2D(b.x(), yVal, b.xErrMinus(),
0815                                          b.xErrPlus(), yemin, yemax ));
0816         }
0817       }
0818       hOut->reset();
0819       for (int i = 0, N = points.size(); i < N; ++i)
0820         hOut->addPoint(points[i]);
0821     }
0822 
0823 
0824     /// @brief Take the @a n th power of all points in @a h, and put the result back in the same Scatter2D.
0825     ///
0826     /// Optionally put a @a k constant below the root.
0827     static void nthPow(Scatter2DPtr h, const double n, const double k = 1.0) {
0828       if (n == 0 || n == 1) {
0829         cout << "Error: Do not take the 0th or 1st power of a Scatter2D,"
0830           " use scale instead." << endl;
0831         return;
0832       }
0833       vector<YODA::Point2D> points;
0834       vector<YODA::Point2D> pIn = h->points();
0835       // The error pre-factor is k^(1/n) / n by Taylors formula.
0836       double eFac = pow(k,1./n)/n;
0837       for (const auto& b : pIn) {
0838         const double yVal =  pow(k * b.y(), n);
0839         if (std::isnan(yVal)) {
0840           points.push_back(YODA::Point2D(b.x(), 0., b.xErrMinus(),
0841                                          b.xErrPlus(), 0, 0 ));
0842         }
0843         else {
0844           double yemin = abs(eFac * pow(yVal,1./(n - 1.))) * b.yErrMinus();
0845           if (std::isnan(yemin))  yemin = b.yErrMinus();
0846           double yemax = abs(eFac * pow(yVal,1./(n - 1.))) * b.yErrPlus();
0847           if (std::isnan(yemax))  yemax = b.yErrPlus();
0848           points.push_back(YODA::Point2D(b.x(), yVal, b.xErrMinus(),
0849                                          b.xErrPlus(), yemin, yemax ));
0850         }
0851       }
0852       h->reset();
0853       for (int i = 0, N = points.size(); i < N; ++i) h->addPoint(points[i]);
0854     }
0855 
0856 
0857     /// @brief Calculate the bootstrapped sample variance
0858     ///
0859     /// Calculate the bootstrapped sample variance for the observable given by
0860     /// correlators and the transformation @a func
0861     template<typename T>
0862     static pair<double, double> sampleVariance(T func) {
0863       // First we calculate the mean (two pass calculation).
0864       double avg = 0.;
0865       for (int i = 0; i < BOOT_BINS; ++i) avg += func(i);
0866       avg /= double(BOOT_BINS);
0867       // Then we find the variance.
0868       double var = 0.;
0869       for (int i = 0; i < BOOT_BINS; ++i) var += pow(func(i) - avg, 2.);
0870       var /= (double(BOOT_BINS) - 1);
0871       return pair<double, double>(var, var);
0872     }
0873 
0874 
0875     /// @brief Calculate the bootstrapped sample envelope
0876     ///
0877     /// Calculate the bootstrapped sample envelope for the observable
0878     /// given by correlators and the transformation @a func.
0879     template<typename T>
0880     static pair<double, double> sampleEnvelope(T func) {
0881       // First we calculate the mean.
0882       double avg = 0.;
0883       for (int i = 0; i < BOOT_BINS; ++i) avg += func(i);
0884       avg /= double(BOOT_BINS);
0885       double yMax = avg;
0886       double yMin = avg;
0887       // Then we find the envelope using the mean as initial value.
0888       for (int i = 0; i < BOOT_BINS; ++i) {
0889         double yVal = func(i);
0890         if (yMin > yVal) yMin = yVal;
0891         else if (yMax < yVal) yMax = yVal;
0892       }
0893       return pair<double, double>(fabs(avg - yMin), fabs(yMax - avg));
0894     }
0895 
0896 
0897     /// Selection method for which sample error to use, given in the constructor
0898     template<typename T>
0899     const pair<double, double> sampleError(T func) const {
0900       if (errorMethod == VARIANCE) return sampleVariance(func);
0901       else if (errorMethod == ENVELOPE) return sampleEnvelope(func);
0902       else
0903         cout << "Error: Error method not found!" << endl;
0904       return pair<double, double>(0.,0.);
0905     }
0906 
0907 
0908     /// Two-particle integrated cn
0909     void cnTwoInt(Scatter2DPtr h, ECorrPtr e2) const {
0910       const vector<CorBin>& bins = e2->getBins();
0911       const vector<double>& binx = e2->getBinX();
0912       // Assert bin size.
0913       if (binx.size() - 1 != bins.size()){
0914         cout << "cnTwoInt: Bin size (x,y) differs!" << endl;
0915         return;
0916       }
0917       vector<const CorBinBase*> binPtrs;
0918       // The mean value of the cumulant.
0919       auto cn = [&](const int i) { return binPtrs[i]->mean(); };
0920       // Error calculation.
0921       vector<pair<double,double>> yErr;
0922       for (int j = 0, N = bins.size(); j < N; ++j) {
0923         binPtrs = bins[j].getBinPtrs();
0924         yErr.push_back(sampleError(cn));
0925       }
0926       binPtrs = e2->getBinPtrs();
0927       fillScatter(h, binx, cn, yErr);
0928     }
0929 
0930 
0931     /// Two particle integrated vn
0932     void vnTwoInt(Scatter2DPtr h, ECorrPtr e2) const {
0933       cnTwoInt(h, e2);
0934       nthPow(h, 0.5);
0935     }
0936 
0937 
0938     /// @brief Put an event-averaged correlator into a Scatter2D
0939     ///
0940     /// Reduces to cnTwoInt, but better with a proper name.
0941     void corrPlot(Scatter2DPtr h, ECorrPtr e) const {
0942       cnTwoInt(h, e);
0943     }
0944 
0945 
0946 
0947 
0948     // TODO Use full path for lookup, change to single AU in output, rename.
0949     void rawHookIn(YODA::AnalysisObjectPtr yao) final {
0950       // Fill the corresponding ECorrelator.
0951       for (auto ec : eCorrPtrs) if(ec->fillFromProfile(yao, name())) break;;
0952     }
0953 
0954     /// @brief Transform RAW ECorrelator Profiles to have content
0955     /// before writing them.
0956     /// Overloaded method from Analysis base class should not be
0957     /// overridden further.
0958     void rawHookOut(const vector<MultiplexAOPtr>& raos, size_t iW) final {
0959       // Loop over the correlators and extract the numbers.
0960       for (auto ec : eCorrPtrs) {
0961         const vector<CorBin>& corBins = ec->getBins();
0962         const vector<double>& binx = ec->getBinX();
0963         auto ref = ec->getReference();
0964         auto refBins = ref.getBinPtrs<CorSingleBin>();
0965         // Assert bin size.
0966         if (binx.size() - 1 != corBins.size()){
0967           cout << "corrPlot: Bin size (x,y) differs!" << endl;
0968           return;
0969         }
0970         // Loop over the booked histograms using their names.
0971         for (int i = 0, N = ec->profs.size(); i < N; ++i) {
0972           for (auto rao : raos) {
0973             if (rao->path() != "/"+name()+"/TMP/"+ec->profs[i]) continue;
0974             // Get a pointer to the active profile.
0975             rao.get()->setActiveWeightIdx(iW);
0976             YODA::Profile1DPtr pPtr = dynamic_pointer_cast<YODA::Profile1D>(rao.get()->activeAO());
0977             // New bins.
0978             vector<YODA::Dbn2D> profBins;
0979             // Add reference flow in the underflow bin.
0980             pPtr->bin(0).set( YODA::Dbn2D(refBins[i]->numEntries(), refBins[i]->sumW(),
0981                                           refBins[i]->sumW2(), 0., 0., refBins[i]->sumWX(), 0., 0.) );
0982             for (size_t j = 0, N = binx.size() - 1; j < N; ++j) {
0983               vector<const CorSingleBin*> binPtrs = corBins[j].getBinPtrs<CorSingleBin>();
0984               // Construct bin of the profiled quantities and put the
0985               // ECorrelator into the raw histogram. We have no information
0986               // (and no desire to add it) of sumWX of the profile, so really
0987               // we should use a Dbn1D - but that does not work for Profile1D's.
0988               pPtr->bin(j).set( YODA::Dbn2D(binPtrs[i]->numEntries(), binPtrs[i]->sumW(),
0989                                             binPtrs[i]->sumW2(), 0., 0., binPtrs[i]->sumWX(), 0, 0) );
0990             }
0991           }
0992         }
0993       }
0994     }
0995 
0996     /// @brief Four particle integrated cn.
0997     void cnFourInt(Scatter2DPtr h, ECorrPtr e2, ECorrPtr e4) const {
0998       const auto& e2bins = e2->getBins();
0999       const auto& e4bins = e4->getBins();
1000       const vector<double>& binx = e2->getBinX();
1001       if (binx.size() - 1 != e2bins.size()){
1002         cout << "cnFourInt: Bin size (x,y) differs!" << endl;
1003         return;
1004       }
1005       if (binx != e4->getBinX()) {
1006         cout << "Error in cnFourInt: Correlator x-binning differs!" << endl;
1007         return;
1008       }
1009       vector<const CorBinBase*> e2binPtrs;
1010       vector<const CorBinBase*> e4binPtrs;
1011       auto cn = [&] (const int i) {
1012         double e22 = e2binPtrs[i]->mean() * e2binPtrs[i]->mean();
1013         return e4binPtrs[i]->mean() - 2. * e22;
1014       };
1015       // Error calculation.
1016       vector<pair<double, double> > yErr;
1017       for (int j = 0, N = e2bins.size(); j < N; ++j) {
1018         e2binPtrs = e2bins[j].getBinPtrs();
1019         e4binPtrs = e4bins[j].getBinPtrs();
1020         yErr.push_back(sampleError(cn));
1021       }
1022       // Put the bin ptrs back in place.
1023       e2binPtrs = e2->getBinPtrs();
1024       e4binPtrs = e4->getBinPtrs();
1025       fillScatter(h, binx, cn, yErr);
1026     }
1027 
1028 
1029     /// Four-particle integrated vn
1030     void vnFourInt(Scatter2DPtr h, ECorrPtr e2, ECorrPtr e4) const {
1031       cnFourInt(h, e2, e4);
1032       nthPow(h, 0.25, -1.0);
1033     }
1034 
1035 
1036     /// Six-particle integrated cn
1037     void cnSixInt(Scatter2DPtr h, ECorrPtr e2, ECorrPtr e4,
1038                   ECorrPtr e6) const {
1039       const auto& e2bins = e2->getBins();
1040       const auto& e4bins = e4->getBins();
1041       const auto& e6bins = e6->getBins();
1042       const auto& binx = e2->getBinX();
1043       if (binx.size() - 1 != e2bins.size()){
1044         cout << "cnSixInt: Bin size (x,y) differs!" << endl;
1045         return;
1046       }
1047       if (binx != e4->getBinX() || binx != e6->getBinX()) {
1048         cout << "Error in cnSixInt: Correlator x-binning differs!" << endl;
1049         return;
1050       }
1051       vector<const CorBinBase*> e2binPtrs;
1052       vector<const CorBinBase*> e4binPtrs;
1053       vector<const CorBinBase*> e6binPtrs;
1054       auto cn = [&] (const int i) {
1055         const double e2mean = e2binPtrs[i]->mean();
1056         const double e4mean = e4binPtrs[i]->mean();
1057         const double e6mean = e6binPtrs[i]->mean();
1058         const double e23 = pow(e2mean, 3.0);
1059         return e6mean - 9.*e2mean*e4mean + 12.*e23;
1060       };
1061       // Error calculation.
1062       vector<pair<double, double> > yErr;
1063       for (int j = 0, N = e2bins.size(); j < N; ++j) {
1064         e2binPtrs = e2bins[j].getBinPtrs();
1065         e4binPtrs = e4bins[j].getBinPtrs();
1066         e6binPtrs = e6bins[j].getBinPtrs();
1067         yErr.push_back(sampleError(cn));
1068       }
1069       // Put the bin ptrs back in place.
1070       e2binPtrs = e2->getBinPtrs();
1071       e4binPtrs = e4->getBinPtrs();
1072       e6binPtrs = e6->getBinPtrs();
1073       fillScatter(h, binx, cn, yErr);
1074     }
1075 
1076 
1077     /// Six-particle integrated vn
1078     void vnSixInt(Scatter2DPtr h, ECorrPtr e2, ECorrPtr e4,
1079                   ECorrPtr e6) const {
1080       cnSixInt(h, e2, e4, e6);
1081       nthPow(h, 1./6., 0.25);
1082     }
1083 
1084 
1085     /// Eight-particle integrated cn
1086     void cnEightInt(Scatter2DPtr h, ECorrPtr e2, ECorrPtr e4,
1087                     ECorrPtr e6, ECorrPtr e8) const {
1088       const auto& e2bins = e2->getBins();
1089       const auto& e4bins = e4->getBins();
1090       const auto& e6bins = e6->getBins();
1091       const auto& e8bins = e8->getBins();
1092       const vector<double>& binx = e2->getBinX();
1093       if (binx.size() - 1 != e2bins.size()){
1094         cout << "cnEightInt: Bin size (x,y) differs!" << endl;
1095         return;
1096       }
1097       if (binx != e4->getBinX() || binx != e6->getBinX() || binx != e8->getBinX()) {
1098         cout << "Error in cnEightInt: Correlator x-binning differs!" << endl;
1099         return;
1100       }
1101       vector<const CorBinBase*> e2binPtrs;
1102       vector<const CorBinBase*> e4binPtrs;
1103       vector<const CorBinBase*> e6binPtrs;
1104       vector<const CorBinBase*> e8binPtrs;
1105       auto cn = [&] (const int i) {
1106         const double e2mean = e2binPtrs[i]->mean();
1107         const double e4mean = e4binPtrs[i]->mean();
1108         const double e6mean = e6binPtrs[i]->mean();
1109         const double e8mean = e8binPtrs[i]->mean();
1110         const double e22 = sqr(e2mean);
1111         const double e24 = sqr(e22);
1112         const double e42 = sqr(e4mean);
1113         return e8mean - 16. * e6mean * e2mean - 18. * e42 + 144. * e4mean*e22 - 144. * e24;
1114       };
1115       // Error calculation.
1116       vector<pair<double, double> > yErr;
1117       for (int j = 0, N = e2bins.size(); j < N; ++j) {
1118         e2binPtrs = e2bins[j].getBinPtrs();
1119         e4binPtrs = e4bins[j].getBinPtrs();
1120         e6binPtrs = e6bins[j].getBinPtrs();
1121         e8binPtrs = e8bins[j].getBinPtrs();
1122         yErr.push_back(sampleError(cn));
1123       }
1124       // Put the bin ptrs back in place.
1125       e2binPtrs = e2->getBinPtrs();
1126       e4binPtrs = e4->getBinPtrs();
1127       e6binPtrs = e6->getBinPtrs();
1128       e8binPtrs = e8->getBinPtrs();
1129       fillScatter(h, binx, cn, yErr);
1130     }
1131 
1132 
1133     /// Eight-particle integrated vn
1134     void vnEightInt(Scatter2DPtr h, ECorrPtr e2, ECorrPtr e4, ECorrPtr e6, ECorrPtr e8) const {
1135       cnEightInt(h, e2, e4, e6, e8);
1136       nthPow(h, 1./8., -1./33.);
1137     }
1138 
1139 
1140     /// Two-particle differential vn
1141     void vnTwoDiff(Scatter2DPtr h, ECorrPtr e2Dif) const {
1142       const auto& e2bins = e2Dif->getBins();
1143       const auto& ref = e2Dif->getReference();
1144       const auto& binx = e2Dif->getBinX();
1145       if (binx.size() -1 != e2bins.size()) {
1146         cout << "vnTwoDif: Bin size (x,y) differs!" << endl;
1147         return;
1148       }
1149       vector<const CorBinBase*> e2binPtrs;
1150       vector<const CorBinBase*> refPtrs;
1151       auto vn = [&] (const int i) {
1152         // Test reference flow.
1153         if (ref.mean() <= 0) return 0.;
1154         return e2binPtrs[i]->mean() / sqrt(ref.mean());
1155       };
1156       // We need here a separate error function, as we don't iterate over the reference flow.
1157       auto vnerr = [&] (const int i) {
1158         // Test reference flow.
1159         if (refPtrs[i]->mean() <=0) return 0.;
1160         return e2binPtrs[i]->mean() / sqrt(refPtrs[i]->mean());
1161       };
1162       // Error calculation.
1163       vector<pair<double,double>> yErr;
1164       refPtrs = ref.getBinPtrs();
1165       for (int j = 0, N = e2bins.size(); j < N; ++j) {
1166         e2binPtrs = e2bins[j].getBinPtrs();
1167         yErr.push_back(sampleError(vnerr));
1168       }
1169       // Put the e2binPtrs back in place.
1170       e2binPtrs = e2Dif->getBinPtrs();
1171       fillScatter(h, binx, vn);
1172     }
1173 
1174 
1175     /// Four-particle differential vn
1176     void vnFourDiff(Scatter2DPtr h, ECorrPtr e2Dif, ECorrPtr e4Dif) const {
1177       const auto& e2bins = e2Dif->getBins();
1178       const auto& e4bins = e4Dif->getBins();
1179       const auto& ref2 = e2Dif->getReference();
1180       const auto& ref4 = e4Dif->getReference();
1181       const auto& binx = e2Dif->getBinX();
1182       if (binx.size() - 1 != e2bins.size()){
1183         cout << "vnFourDif: Bin size (x,y) differs!" << endl;
1184         return;
1185       }
1186       if (binx != e4Dif->getBinX()) {
1187         cout << "Error in vnFourDif: Correlator x-binning differs!" << endl;
1188         return;
1189       }
1190       vector<const CorBinBase*> e2binPtrs;
1191       vector<const CorBinBase*> e4binPtrs;
1192       vector<const CorBinBase*> ref2Ptrs;
1193       vector<const CorBinBase*> ref4Ptrs;
1194       double denom = 2 * ref2.mean() * ref2.mean() - ref4.mean();
1195       auto vn = [&] (const int i) {
1196         // Test denominator.
1197         if (denom <= 0 ) return 0.;
1198         return ((2 * ref2.mean() * e2bins[i].mean() - e4bins[i].mean()) / pow(denom, 0.75));
1199       };
1200       // We need here a separate error function, as we don't iterate over the reference flow.
1201       auto vnerr = [&] (const int i) {
1202         double denom2 = 2 * ref2Ptrs[i]->mean() * ref2Ptrs[i]->mean() -
1203         ref4Ptrs[i]->mean();
1204         // Test denominator.
1205         if (denom2 <= 0) return 0.;
1206         return ((2 * ref2Ptrs[i]->mean() * e2binPtrs[i]->mean() - e4binPtrs[i]->mean()) / pow(denom2, 0.75));
1207       };
1208       // Error calculation.
1209       vector<pair<double,double> > yErr;
1210       ref2Ptrs = ref2.getBinPtrs();
1211       ref4Ptrs = ref4.getBinPtrs();
1212       for (int j = 0, N = e2bins.size(); j < N; ++j) {
1213         e2binPtrs = e2bins[j].getBinPtrs();
1214         e4binPtrs = e4bins[j].getBinPtrs();
1215         yErr.push_back(sampleError(vnerr));
1216       }
1217       // Put the binPtrs back in place.
1218       e2binPtrs = e2Dif->getBinPtrs();
1219       e4binPtrs = e4Dif->getBinPtrs();
1220       fillScatter(h, binx, vn, yErr);
1221     }
1222 
1223   };
1224 
1225 
1226 }
1227 
1228 #endif