ROOTPWA
nBodyPhaseSpaceGen.cc
Go to the documentation of this file.
1 
2 //
3 // Copyright 2010
4 //
5 // This file is part of rootpwa
6 //
7 // rootpwa is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation, either version 3 of the License, or
10 // (at your option) any later version.
11 //
12 // rootpwa is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with rootpwa. If not, see <http://www.gnu.org/licenses/>.
19 //
21 //-------------------------------------------------------------------------
22 // File and Version Information:
23 // $Rev:: $: revision of last commit
24 // $Author:: $: author of last commit
25 // $Date:: $: date of last commit
26 //
27 // Description:
28 //
29 // calculates n-body phase space (constant matrix element) using various algorithms
30 //
31 // the n-body decay is split up into (n - 2) successive 2-body decays
32 // each 2-body decay is considered in its own center-of-mass frame thereby
33 // separating the mass from the (trivial) angular dependence
34 //
35 // the event is boosted into the same frame in which the n-body system is
36 // given
37 //
38 // !Note! some of the implemented weights can be applied only in
39 // certain cases; if you are not sure, use the "S.U. Chung"
40 // weight
41 //
42 // !Note! in case this class is used to calculate the absolute value
43 // of the phase space integral, be sure to use the "S.U. Chung"
44 // weight; if in the integral the phase space is weighted with
45 // some amplitude with angular depdendence, the integral has to
46 // be divided by the correct power of (2) pi (the "S.U. Chung"
47 // already contains a factor (4 pi)^(n - 1) from (trivial)
48 // integration over all angles)
49 //
50 // based on:
51 // GENBOD (CERNLIB W515), see F. James, "Monte Carlo Phase Space", CERN 68-15 (1968)
52 // NUPHAZ, see M. M. Block, "Monte Carlo phase space evaluation", Comp. Phys. Commun. 69, 459 (1992)
53 // S. U. Chung, "Spin Formalism", CERN Yellow Report
54 // S. U. Chung et. al., "Diffractive Dissociation for COMPASS"
55 //
56 // index convention:
57 // - all vectors have the same size (= number of decay daughters)
58 // - index i corresponds to the respective value in the (i + 1)-body system: effective mass M, break-up momentum, angles
59 // - thus some vector elements are not used like breakupMom[0], theta[0], phi[0], ...
60 // this overhead is negligible compared to the ease of notation
61 //
62 // the following graph illustrates how the n-body decay is decomposed into a sequence of two-body decays
63 //
64 // n-body ... 3-body 2-body single daughter
65 //
66 // m[n - 1] m[2] m[1]
67 // ^ ^ ^
68 // | | |
69 // | | |
70 // M[n - 1] --> ... --> M[2] --> M[1] --> M [0] = m[0]
71 // theta[n - 1] ... theta[2] theta[1] theta[0] = 0 (not used)
72 // phi [n - 1] ... phi [2] phi [1] phi [0] = 0 (not used)
73 // mSum [n - 1] ... mSum [2] mSum [1] mSum [0] = m[0]
74 // = sum_0^(n - 1) m[i] = m[2] + m[1] + m[0] = m[1] + m[0]
75 // breakUpMom[n - 1] ... breakUpMom[2] breakUpMom[1] breakUpMom[0] = 0 (not used)
76 // = q(M[n - 1], m[n - 1], M[n - 2]) = q(M[2], m[2], M[1]) = q(M[1], m[1], m[0])
77 //
78 //
79 // Author List:
80 // Boris Grube TUM (original author)
81 //
82 //
83 //-------------------------------------------------------------------------
84 
85 
86 #include <algorithm>
87 
88 #include "TMath.h"
89 
90 #include "reportingUtilsRoot.hpp"
91 #include "physUtils.hpp"
92 #include "factorial.hpp"
93 #include "nBodyPhaseSpaceGen.h"
94 
95 
96 using namespace std;
97 using namespace rpwa;
98 
99 
101 
102 
103 nBodyPhaseSpaceGen::nBodyPhaseSpaceGen()
104  : _n (0),
105  _weightType (S_U_CHUNG),
106  _norm (0),
107  _weight (0),
108  _impweight(1),
109  _maxWeightObserved(0),
110  _maxWeight (0),
111 
112  _kinematicsType (BLOCK),
113 
114  _verbose(false),
115 
116  _isoBWMass(1.0),_isoBWWidth(0.01) // dummy values!!!
117 { }
118 
119 
121 { }
122 
123 
124 // sets decay constants and prepares internal variables
125 bool
126 nBodyPhaseSpaceGen::setDecay(const vector<double>& daughterMasses) // array of daughter particle masses
127 {
128  _n = daughterMasses.size();
129  if (_n < 2) {
130  printErr << "number of daughters = " << _n << " does not make sense." << endl;
131  return false;
132  }
133  // copy daughter masses
134  _m.clear();
135  _m = daughterMasses;
136  // prepare effective mass vector
137  _M.clear();
138  _M.resize(_n, 0);
139  _M[0] = _m[0];
140  // prepare angle vectors
141  _cosTheta.clear();
142  _cosTheta.resize(_n, 0);
143  _phi.clear();
144  _phi.resize(_n, 0);
145  // calculate daughter mass sums
146  _mSum.clear();
147  _mSum.resize(_n, 0);
148  _mSum[0] = _m[0];
149  for (unsigned int i = 1; i < _n; ++i)
150  _mSum[i] = _mSum[i - 1] + _m[i];
151  // prepare breakup momentum vector
152  _breakupMom.clear();
153  _breakupMom.resize(_n, 0);
154  // prepare vector for daughter Lorentz vectors
155  _daughters.clear();
156  _daughters.resize(_n, TLorentzVector(0, 0, 0, 0));
157  // calculate normalization
158  switch (_weightType) {
159  case S_U_CHUNG: case IMPORTANCE:
160  // S. U. Chung's normalization
161  _norm = 1 / (2 * pow(twoPi, 2 * (int)_n - 3) * rpwa::factorial<double>(_n - 2));
162  break;
163  case NUPHAZ:
164  { // NUPHAZ's normalization: calculates phase space for massless daughters
165  const double fact = rpwa::factorial<double>(_n - 2);
166  _norm = 8 / (pow(fourPi, 2 * (int)_n + 1) * fact * fact * (_n - 1));
167  }
168  break;
169  case GENBOD: case FLAT:
170  _norm = 1;
171  break;
172  default:
173  printWarn << "unknown weight type. setting normalization to 1." << endl;
174  _norm = 1;
175  break;
176  }
178  return true;
179 }
180 
181 
182 // set decay constants and prepare internal variables
183 bool
184 nBodyPhaseSpaceGen::setDecay(const unsigned int nmbOfDaughters, // number of daughter particles
185  const double* daughterMasses) // array of daughter particle masses
186 {
187  vector <double> m;
188  m.resize(nmbOfDaughters, 0);
189  for (unsigned int i = 0; i < nmbOfDaughters; ++i)
190  m[i] = daughterMasses[i];
191  return setDecay(m);
192 }
193 
194 
195 // generates event with certain n-body mass and momentum and returns event weigth
196 // general purpose function
197 double
198 nBodyPhaseSpaceGen::generateDecay(const TLorentzVector& nBody) // Lorentz vector of n-body system in lab frame
199 {
200  _weight = 1;
201  const double nBodyMass = nBody.M();
202  if (_n < 2) {
203  printWarn << "number of daughter particles = " << _n << " is smaller than 2. weight is set to 0." << endl;
204  _weight = 0;
205  } else if (nBodyMass < _mSum[_n - 1]) {
206  printWarn << "n-body mass = " << nBodyMass << " is smaller than sum of daughter masses = "
207  << _mSum[_n - 1] << ". weight is set to 0." << endl;
208  _weight = 0;
209  } else {
210  pickMasses(nBodyMass);
211  calcWeight();
212  pickAngles();
213  calcEventKinematics(nBody);
214  }
215 #if DEBUG
216  print();
217 #endif
218  return _weight;
219 }
220 
221 
222 // generates full event with certain n-body mass and momentum only, when event is accepted (return value = true)
223 // this function is more efficient, if only weighted evens are needed
224 bool
225 nBodyPhaseSpaceGen::generateDecayAccepted(const TLorentzVector& nBody, // Lorentz vector of n-body system in lab frame
226  const double maxWeight) // if positive, given value is used as maximum weight, otherwise _maxWeight
227 {
228  const double nBodyMass = nBody.M();
229  if (_n < 2) {
230  printWarn << "number of daughter particles = " << _n << " is smaller than 2. no event generated." << endl;
231  return false;
232  } else if (nBodyMass < _mSum[_n - 1]) {
233  printWarn << "n-body mass = " << nBodyMass << " is smaller than sum of daughter masses = "
234  << _mSum[_n - 1] << ". no event generated." << endl;
235  return false;
236  }
237  pickMasses(nBodyMass);
238  calcWeight();
239  if (!eventAccepted(maxWeight))
240  return false;
241  pickAngles();
242  calcEventKinematics(nBody);
243  return true;
244 }
245 
246 
247 // randomly choses the (n - 2) effective masses of the respective (i + 1)-body systems
248 void
249 nBodyPhaseSpaceGen::pickMasses(const double nBodyMass) // total energy of the system in its RF
250 {
251 
252  _M[_n - 1] = nBodyMass;
253  switch (_weightType) {
254  case NUPHAZ:
255  { // the NUPHAZ algorithm calculates part of the weight along with the effective isobar masses
256  // for better readability notation was slightly changed w.r.t to Block's paper
257  // \mathcal{F} -> F
258  // \xi -> x
259  // U -> u
260  // p_i -> prob
261  _weight = 1;
262  for (unsigned int i = _n - 1; i >= 2; --i) { // loop over 3- to n-bodies
263  // generate variable for (i + 1)-body decay that follows importance sampling distribution as defined in eq. (12)
264  //
265  // 1) calculate probability
266  const double sqrtU = _m[i] / _M[i]; // cf. eq. (39) and (51)
267  const double u = sqrtU * sqrtU;
268  const double xMin = _mSum[i - 1] * _mSum[i - 1] / (_M[i] * _M[i]); // cf. eq. (8)
269  const double xMax = (1 - sqrtU) * (1 - sqrtU);
270  const double deltaX = xMax - xMin;
271  const double term = 1 + u - xMin;
272  const double prob = 1 / (i - (i - 1) * deltaX / term); // cf. eq. (20)
273  // 2) calculate generator for distribution
274  double x;
275  if (random() < prob)
276  x = xMin + deltaX * pow(random(), 1 / (double)i) * pow(random(), 1 / (double)(i - 1)); // cf. eq. (21)
277  else
278  x = xMin + deltaX * pow(random(), 1 / (double)i); // cf. eq. (22)
279  // 3) calculate weight factor
280  const double deltaZ = (i * term - (i - 1) * deltaX) * pow(deltaX, (int)i - 1); // cf. eq. (17) and (18)
281  _weight *= deltaZ * pow(x / (x - xMin), (int)i - 2) * F(x, u) / (1 + u - x); // cf. eq. (24)
282  // 4) set effective isobar mass of i-body using x_i = _M(i - 1)^2 / _Mi^2
283  _M[i - 1] = _M[i] * sqrt(x);
284  }
285  }
286  break;
287  /* case IMPORTANCE:
288  {
289 
290  // set effective masses of (intermediate) two-body decays
291  const double massInterval = nBodyMass - _mSum[_n - 1]; // kinematically allowed mass interval
292 
293  // do first isobar breitwigner importance sampling:
294  double m=0;//unsigned int count=0;
295  //while(m<_mSum[_n-2] || m>_mSum[_n-2]+massInterval){
296  //m=gRandom->BreitWigner(_isoBWMass,_isoBWWidth);
297  m=gRandom->Uniform(_mSum[_n-2],_mSum[_n-2]+massInterval);
298  //printErr << _M[_n-3] << " < " << m << " < " << _mSum[_n-2]+massInterval << endl;
299  //}
300  _M[_n-2]=m;
301  _impweight=TMath::BreitWigner(m,_isoBWMass,_isoBWWidth); // for de-weighting (has to be read out explicitely!!!)
302 
303  // now that the first isobar mass is fixed, generate the rest:
304  // create vector of sorted random values
305  vector<double> r(_n - 2, 0); // (n - 2) values needed for 2- through (n - 1)-body systems
306  r[_n-3]=(_M[_n-2]-_mSum[_n-2])/massInterval;
307 
308 
309  for (unsigned int i = _n-3; i > 0; --i)
310  r[i-1] = gRandom->Uniform(0,r[i]);
311 
312  for (unsigned int i = 1; i < (_n - 2); ++i) // loop over intermediate 2- to (n - 1)-bodies
313  _M[i] = _mSum[i] + r[i - 1] * massInterval; // _mSum[i] is minimum effective mass
314 
315 
316 
317  if(_verbose){
318  for(unsigned int i =0; i < (_n - 1) ; ++i){
319  cerr << "M["<<i<<"]="<<_M[i] << endl;
320  }
321  }// end ifverbose
322  }// end if importance sampling
323 
324 
325  break;*/
326  default:
327  {
328 
329  // create vector of sorted random values
330  vector<double> r(_n - 2, 0); // (n - 2) values needed for 2- through (n - 1)-body systems
331  for (unsigned int i = 0; i < (_n - 2); ++i)
332  r[i] = random();
333  sort(r.begin(), r.end());
334  // set effective masses of (intermediate) two-body decays
335  const double massInterval = nBodyMass - _mSum[_n - 1]; // kinematically allowed mass interval
336  for (unsigned int i = 1; i < (_n - 1); ++i) // loop over intermediate 2- to (n - 1)-bodies
337  _M[i] = _mSum[i] + r[i - 1] * massInterval; // _mSum[i] is minimum effective mass
338 
339  //cerr << _M[1] << endl;
340  if(_weightType==IMPORTANCE){
341  _impweight=TMath::BreitWigner(_M[_n-2],_isoBWMass,_isoBWWidth);
342  // BE CAREFULL:::: hard coded a1 BW in 3pi mass
343  _impweight*=TMath::BreitWigner(_M[_n-3],1.23,0.425);
344  // BE CAREFULL:::: hard coded rho BW in 2pi mass
345  _impweight*=TMath::BreitWigner(_M[_n-4],0.77,0.15);
346  }
347  //cerr << _impweight << endl;
348  } // end default mass picking
349  break;
350  }
351 }
352 
353 
354 // computes event weight (= integrand value) and breakup momenta
355 // uses vector of intermediate two-body masses prepared by pickMasses()
356 double
358 {
359  for (unsigned int i = 1; i < _n; ++i) // loop over 2- to n-bodies
360  _breakupMom[i] = breakupMomentum(_M[i], _M[i - 1], _m[i]);
361  switch (_weightType) {
362  case S_U_CHUNG: case IMPORTANCE:
363  { // S. U. Chung's weight
364  double momProd = 1; // product of breakup momenta
365  for (unsigned int i = 1; i < _n; ++i) // loop over 2- to n-bodies
366  momProd *= _breakupMom[i];
367  const double massInterval = _M[_n - 1] - _mSum[_n - 1]; // kinematically allowed mass interval
368  _weight = _norm * pow(massInterval, (int)_n - 2) * momProd / _M[_n - 1] * _impweight;
369  }
370  break;
371  case NUPHAZ:
372  { // NUPHAZ's weight
373  const double M2 = _M[1] * _M[1];
374  _weight *= _norm * pow(_M[_n - 1], 2 * (int)_n - 4) * F(_m[1] * _m[1] / M2, _m[0] * _m[0] / M2);
375  //_weight *= F(_m[1] * _m[1] / M2, _m[0] * _m[0] / M2);
376  }
377  break;
378  case GENBOD:
379  { // GENBOD's weight; does not reproduce dependence on n-body mass correctly
380  double momProd = 1; // product of breakup momenta
381  for (unsigned int i = 1; i < _n; ++i) // loop over 2- to n-bodies
382  momProd *= _breakupMom[i];
383  double motherMassMax = _M[_n - 1] - _mSum[_n - 1] + _m[0]; // maximum possible value of decaying effective mass
384  double momProdMax = 1; // product of maximum breakup momenta
385  for (unsigned int i = 1; i < _n; ++i) { // loop over 2- to n-bodies
386  motherMassMax += _m[i];
387  momProdMax *= breakupMomentum(motherMassMax, _mSum[i - 1], _m[i]);
388  }
389  _weight = momProd / momProdMax;
390  }
391  break;
392  case FLAT:
393  // no weighting
395  _weight = 1;
396  break;
397  default:
398  printWarn << "unknown weight type. setting weight to 1." << endl;
399  _weight = 1;
400  break;
401  }
404  if (isnan(_weight))
405  printWarn << "weight = " << _weight << endl;
406  return _weight;
407 }
408 
409 
410 // calculates complete event from the effective masses of the (i + 1)-body
411 // systems, the Lorentz vector of the decaying system, and the decay angles
412 // uses the break-up momenta calculated by calcWeight()
413 void
414 nBodyPhaseSpaceGen::calcEventKinematics(const TLorentzVector& nBody) // Lorentz vector of n-body system in lab frame
415 {
416  switch (_kinematicsType) {
417  case RAUBOLD_LYNCH:
418  {
419  // builds event starting in 2-body RF going up to n-body
420  // is less efficicient, since to calculate kinematics in i-body RF
421  // all the daughters of the (i - 1)-body have to bu boosted into the i-body RF
422  // and rotated according to the chosen angles
423  //
424  // construct Lorentz vector of first daughter in 2-body RF
425  _daughters[0].SetPxPyPzE(0, 0, _breakupMom[1], sqrt(_m[0] * _m[0] + _breakupMom[1] * _breakupMom[1]));
426  for (unsigned int i = 1; i < _n; ++i) { // loop over remaining (n - 1) daughters
427  // construct daughter Lorentz vector in (i + 1)-body RF; i-body is along z-axis
428  _daughters[i].SetPxPyPzE(0, 0, -_breakupMom[i], sqrt(_m[i] * _m[i] + _breakupMom[i] * _breakupMom[i]));
429  // rotate all daughters of the (i + 1)-body system
430  const double cT = _cosTheta[i];
431  const double sT = sqrt(1 - cT * cT);
432  const double cP = cos(_phi[i]);
433  const double sP = sin(_phi[i]);
434  for (unsigned int j = 0; j <= i; ++j) {
435  TLorentzVector* daughter = &(_daughters[j]);
436  { // rotate by theta around y-axis
437  const double x = daughter->Px();
438  const double z = daughter->Pz();
439  daughter->SetPz(cT * z - sT * x);
440  daughter->SetPx(sT * z + cT * x);
441  }
442  { // rotate by phi around z-axis
443  const double x = daughter->Px();
444  const double y = daughter->Py();
445  daughter->SetPx(cP * x - sP * y);
446  daughter->SetPy(sP * x + cP * y);
447  }
448  }
449  if (i < (_n - 1)) {
450  // boost all daughters of the (i + 1)-body system from (i + 1)-body RF to (i + 2)-body RF; (i + 2)-body is along z-axis
451  const double betaGammaInv = _M[i] / _breakupMom[i + 1]; // 1 / (beta * gamma) of (i + 1)-body system in the (i + 2) RF
452  const double beta = 1 / sqrt(1 + betaGammaInv * betaGammaInv);
453  for (unsigned int j = 0; j <= i; ++j)
454  _daughters[j].Boost(0, 0, beta);
455  } else {
456  // final boost of all daughters from n-body RF to lab system
457  const TVector3 boost = nBody.BoostVector();
458  for (unsigned int j = 0; j < _n; ++j)
459  _daughters[j].Boost(boost);
460  }
461  }
462  }
463  break;
464  case BLOCK:
465  {
466  // builds event starting in n-body RF going down to 2-body RF
467  // is more efficicient, since it requitres only one rotation and boost per daughter
468  TLorentzVector P = nBody; // Lorentz of (i + 1)-body system in lab frame
469  for (unsigned int i = _n - 1; i >= 1; --i) { // loop from n-body down to 2-body
470  // construct Lorentz vector of daughter _m[i] in (i + 1)-body RF
471  const double sinTheta = sqrt(1 - _cosTheta[i] * _cosTheta[i]);
472  const double pT = _breakupMom[i] * sinTheta;
473  TLorentzVector* daughter = &(_daughters[i]);
474  daughter->SetPxPyPzE(pT * cos(_phi[i]),
475  pT * sin(_phi[i]),
476  _breakupMom[i] * _cosTheta[i],
477  sqrt(_m[i] * _m[i] + _breakupMom[i] * _breakupMom[i]));
478  // boost daughter into lab frame
479  daughter->Boost(P.BoostVector());
480  // calculate Lorentz vector of i-body system in lab frame
481  P -= *daughter;
482  }
483  // set last daughter
484  _daughters[0] = P;
485  }
486  break;
487  default:
488  printErr << "unknown kinematics type. cannot calculate event kinematics." << endl;
489  break;
490  }
491 }
492 
493 
494 
495 // calculates maximum weight for given n-body mass
496 double
497 nBodyPhaseSpaceGen::estimateMaxWeight(const double nBodyMass, // sic!
498  const unsigned int nmbOfIterations) // number of generated events
499 
500 {
501  double maxWeight = 0;
502  for (unsigned int i = 0; i < nmbOfIterations; ++i) {
503  _weight=1;
504  pickMasses(nBodyMass);
505  calcWeight();
506  maxWeight = max(_weight, maxWeight);
507  }
508  return maxWeight;
509 }
510 
511 
512 ostream&
513 nBodyPhaseSpaceGen::print(ostream& out) const
514 {
515  out << "nBodyPhaseSpaceGen parameters:" << endl
516  << " number of daughter particles ............... " << _n << endl;
517  // << " masses of the daughter particles ........... " << _m << endl;
518  // << " sums of daughter particle masses ........... " << _mSum << endl
519  // << " effective masses of (i + 1)-body systems ... " << _M << endl
520  // << " cos(polar angle) in (i + 1)-body systems ... " << _cosTheta << endl
521  // << " azimuth in (i + 1)-body systems ............ " << _phi << endl
522  // << " breakup momenta in (i + 1)-body systems .... " << _breakupMom << endl
523  out << " weight formula ............................. " << _weightType << endl
524  << " normalization value ........................ " << _norm << endl
525  << " weight of generated event .................. " << _weight << endl
526  << " maximum weight used in hit-miss MC ......... " << _maxWeight << endl
527  << " maximum weight since instantiation ......... " << _maxWeightObserved << endl
528  << " algorithm for kinematics calculation ....... " << _kinematicsType << endl
529  << " daughter four-momenta:" << endl;
530 
531  for (unsigned int i = 0; i < _n; ++i)
532  out << " daughter " << i << ": " << _daughters[i] << endl;
533  return out;
534 }