Jpp  15.0.5
the software that should make you happy
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
JMarkovPathGenerator.hh
Go to the documentation of this file.
1 #ifndef H_MARKOV_PATH
2 #define H_MARKOV_PATH
3 
4 /**
5  * \author mjongen
6  */
7 
8 // c++ standard library includes
9 #include<iostream>
10 #include<iomanip>
11 #include<cmath>
12 #include<cstdlib>
13 
14 // root includes
15 #include "TObject.h"
16 #include "TH1F.h"
17 #include "TRandom3.h"
18 
19 // JPP includes
21 
22 namespace JMARKOV {}
23 namespace JPP { using namespace JMARKOV; }
24 
25 namespace JMARKOV {
26 
27  /**
28  The JMarkovPathGenerator generates ensembles of photon paths using a
29  Markov Chain Monte Carlo (MCMC).
30  In the Markov chain, the number of scatterings does not change.
31  Therefore the ensembles have to be generated separately for each given
32  number of scatterings.
33 
34  The small modification of the path that is proposed in each step, is
35  defined by the function 'randomPathChange'. This function is virtual
36  so that it can be overrided in a derived class.
37  The initial and final vertex are fixed and one of the other vertices
38  is displaced in a random direction following an exponential distribution.
39 
40  The source, target and scattering models are used to calculate the
41  probability density of the path.
42 
43  Random numbers are drawn from gRandom.
44 
45  By default, the coordinates are remapped such that the 1/r^2 singularities
46  in the path probability density disappear. This option can be turned off
47  (but do not do it, unless you want to convince yourself that it is
48  necessary) with the setCoordinateRemapping method.
49  **/
51  public:
52 
53  /// standard constructor
55  naccepted_r = -1 ;
56  nrejected_r = -1 ;
57  naccepted_t = -1 ;
58  nrejected_t = -1 ;
60  setRadialStepSize_m(1.0) ;
63  }
64 
65  /**
66  Generate an ensemble of n paths with a fixed number of scatterings by
67  MCMC-sampling the given scattering model.
68  The paths start at the source (a well-defined position) and end on the
69  target (either a fixed position or, in the case of a finite source, the
70  surface of a sphere).
71 
72  start_path is a photon path that is used a starting point for the MCMC.
73  It has to be supplied by the user (due to the various source and target
74  profiles it is not straightforward to generate one automatically) and
75  must have a path probability density != 0.
76  The number of scatterings is also defined by the start_path.
77 
78  nsteps_burn_in is the number of steps taken for burn-in.
79 
80  nsteps_save determines the number of MCMC steps between the paths
81  that are saved to the ensemble.
82  **/
83  std::vector<JPhotonPath> generateEnsemble( int n, const JPhotonPath& start_path, JSourceModel* src, JScatteringModel* sm, JTargetModel* trg, double lambda_abs, int nsteps_burn_in, int nsteps_save ) ;
84 
85  /// get the number of Markov steps taken in the last call to generateEnsemble (after burn-in)
87 
88  /// get the number of accepted steps taken during the last call to generateEnsemble (after burn-in)
90 
91  /// get the number of rejected steps during the last call to generateEnsemble (after burn-in)
93 
94  /// get the fraction of accepted steps during the last call to generateEnsemble (after burn-in)
95  double getFractionAccepted() {
96  if( getNsteps() <= 0 ) return 0 ;
97  return 1.0*getNacceptedSteps()/getNsteps() ;
98  }
99 
100  /// get the fraction of steps that were accepted when a radial step was performed during the last call to generateEnsemble (after burn-in)
102  if( naccepted_r + nrejected_r <= 0 ) return 0 ;
103  return 1.0*naccepted_r/(naccepted_r+nrejected_r) ;
104  }
105 
106  /// get the fraction of steps that were accepted when a tangential step was performed during the last call to generateEnsemble (after burn-in)
108  if( naccepted_t+nrejected_t <= 0 ) return 0 ;
109  return 1.0*naccepted_t/(naccepted_t+nrejected_t) ;
110  }
111 
112  /// make one Markov chain step for a path
113  bool doMarkovStep( JSourceModel* src, JScatteringModel* sm, JTargetModel* trg, double lambda_abs, JPhotonPath& p ) ;
114 
115  virtual int randomPathChange( JPhotonPath& p, JTargetModel* trg ) ;
116 
117  /// set the average step size in degrees for the impact point on the target
118  void setTargetStepSize_deg( double val ) {
119  stepsize_angle_target = val * M_PI / 180 ;
120  }
121 
122  /// set the average step size in [m] in the radial direction for the scattering vertices
123  void setRadialStepSize_m( double val ) {
124  stepsize_r = val ;
125  }
126 
127  /**
128  Set the average step size theta in degrees for steps in the tangential direction
129  for the scattering vertices.
130  When r * theta is larger than the radial step size, the effective theta will be
131  temporarily set to a smaller value to avoid taking steps that are too large.
132  **/
133  void setTangentialStepSize_deg( double val ) {
134  stepsize_angle = val * M_PI / 180 ;
135  }
136 
137  /// activate or deactive coordinate remapping
138  void setCoordinateRemapping( bool val=true ) {
140  }
141 
142  /// returns true when coordinate remapping is activated, false otherwise
144 
145 
146  private:
147 
148  /// return R0; the length scale used in the coordinate remapping
149  inline double getR0() { return 3 * stepsize_r ; }
150 
151  /**
152  return the distance between the remapped vertex and the previous vertex
153  given the distance between the not-remapped vertex and the previous vertex
154  **/
155  double getRemappedDistance(double r) {
156  if( 3.0*sqrt(3)*r < getR0() ) return pow(r*getR0()*getR0(),1.0/3.0) ;
157  return r + 2.0 / (3.0*sqrt(3.0))*getR0() ;
158  }
159 
160  /// inverse of getRemappedDistance
161  double getUnmappedDistance(double rprime) {
162  if( sqrt(3.0)*rprime < getR0() ) return rprime*rprime*rprime/(getR0()*getR0()) ;
163  return rprime - 2.0/(3.0*sqrt(3.0))*getR0() ;
164  }
165 
166  /**
167  Return the remapped vertex position of x.
168  xprev is the (not remapped!) coordinate of the previous vertex in the photon path.
169  **/
171  double r = x.getDistance(xprev) ;
172  double rprime = getRemappedDistance(r) ;
173  return xprev + (rprime/r)*(x-xprev) ;
174  }
175 
176  /**
177  Inverse of getRemappedPosition. Again, xprev is the (not remapped!) coordinate of the previous
178  vertex in the photon path.
179  **/
180  JPosition3D getUnmappedPosition( const JPosition3D& xprev, const JPosition3D& xprime ) {
181  double rprime = xprime.getDistance(xprev) ;
182  double r = getUnmappedDistance(rprime) ;
183  return xprev + (r/rprime)*(xprime-xprev) ;
184  }
185 
186  /// returns a remapped version of the photon path
188  JPhotonPath pprime(p) ;
189  for( int i=1; i<(int)pprime.size()-1; ++i ) {
190  pprime[i] = getRemappedPosition(p[i-1],p[i]) ;
191  }
192  return pprime ;
193  }
194 
195  /// inverse of getRemappedPhotonPath
197  JPhotonPath p(pprime) ;
198  for( int i=1; i<(int)p.size()-1; ++i ) {
199  p[i] = getUnmappedPosition(p[i-1],p[i]) ;
200  }
201  return p ;
202  }
203 
204  /**
205  Returns the conversion factor J needed to compute the path probability density
206  in the remapped coordinates, for a given vertex.
207 
208  xprime is the remapped vertex position
209  xprev is the (not remapped!) position of the previous vertex
210 
211  In casual notation (the ' indicates remapped coordinates):
212 
213  rho(x) dV = rho(x) r^2 dr dOmega
214  = rho(x) r^2 / r'^2 dr/dr' r'^2 dr' dOmega
215  = rho( x(x') ) r^2 / r'^2 dr/dr' dV' <--- since dOmega == dOmega'
216  := J x rho( x(x') ) dV'
217  := rho'(x') dV'
218  Where J := r^2 / r'^2 dr/dr'
219 
220  The point of this is that we typically pick r proportional to r'^3, so that
221  dr/dr' is proportional to r'^2, which cancels the factor 1/r'^2 in rho'.
222  The new factor r^2 then cancels the typical 1/r^2 singularity present in rho.
223  **/
224  double getRemappingCorrection( const JPosition3D& xprev, const JPosition3D& xprime ) {
225  double rprime = xprev.getDistance(xprime) ;
226  double r = getUnmappedDistance(rprime) ;
227  if( sqrt(3.0)*rprime >= getR0() ) {
228  // J = r^2 / r'^2 * dr/dr'
229  // dr/dr' = 1
230  return r*r/(rprime*rprime) ;
231  }
232  // J = r^2 / r'^2 * dr/dr'
233  // dr/dr' = 3 * r'^2/R0^2
234  // ==> J = 3 * r^2 / R0^2
235  return 3*r*r/(getR0()*getR0()) ;
236  }
237 
238  /**
239  Return the remapping correction for an entire photon path (product of the
240  remapping correction for the individual scattering vertices).
241 
242  p is the photon path in normal coordinates
243  pprime is the remapped version of p
244  **/
245  double getRemappingCorrection( const JPhotonPath& p, const JPhotonPath& pprime ) {
246  double J = 1 ;
247  for( int i=1; i<(int)p.size()-1; ++i ) {
248  J *= getRemappingCorrection(p[i-1],pprime[i]) ;
249  }
250  return J ;
251  }
252 
253 
255  int naccepted_r ; // number of accepted radial steps
256  int nrejected_r ; // number of rejected radial steps
257  int naccepted_t ; // number of accepted tangential steps
258  int nrejected_t ; // number of rejected tangential steps
260  double stepsize_r ;
261  double stepsize_angle ;
262  } ;
263 
265  int nscat = p.n-2 ;
266  int type = 0 ;
267 
268  if( nscat>0 ) {
269  // pick vertex from 1 to nscat+1 (so excluding the start and end point)
270  int nv = gRandom->Integer(nscat)+1 ;
271 
272  // distance w.r.t. previous vertex
273  double r = p[nv-1].getDistance(p[nv]) ;
274 
275  if( gRandom->Uniform() > 0.5 ) {
276  // with P=1/2, take a step in the radial direction
277  type = 1 ;
278 
279  // to avoid nasty phase space Jacobian issues, I use this rather primitive method
280  // where I simply generate a new position with a step in a random direction, then
281  // only apply the radial part of the change
282  //
283  // NOTE: simply choosing
284  // *) r+dr with P proportional to (r+dr)^2
285  // *) r-dr with P proportional to (r-dr)^2
286  // does not work. Trust me. I've tried.
287  double _r = gRandom->Exp(stepsize_r) ;
288  double _x, _y, _z ;
289  gRandom->Sphere( _x, _y, _z, _r ) ;
290  double _rnew = ( JPosition3D(_x,_y,_z) + JPosition3D(0,0,r) ).getLength() ;
291  double dr = _rnew - r ;
292 
293  p[nv] = p[nv] + dr * JPosition3D(JVersor3D(p[nv]-p[nv-1])) ;
294 
295  } else {
296  // with P=1/2, take a step in the tangential direction
297  type = 2 ;
298 
299  // for large radii, limit the maximum angle over which we can move
300  // so that the average change in distance does not exceed stepsize_r too much
301  double _stepsize_angle = min(stepsize_angle,stepsize_r/r) ;
302  double theta = gRandom->Exp(_stepsize_angle) ;
303  double phi = gRandom->Uniform(2.0*M_PI) ;
304  JVersor3D dir( sin(theta)*cos(phi), sin(theta)*sin(phi), cos(theta) ) ;
305  JVersor3D xdir( p[nv] - p[nv-1] ) ;
306  JRotation3D rot(xdir) ;
307  dir = dir.rotate_back(rot) ;
308  p[nv] = p[nv-1] + r * JVector3D(dir) ;
309  }
310  }
311 
312  // in case of a finite target, also move the end point
313  if( trg->getRadius() > 0 ) {
314  const double dct = 1-cos(stepsize_angle_target) ;
315  double ct = -2 ;
316  while( ct<=-1 ) ct = 1-gRandom->Exp(dct) ;
317  double theta = acos(ct) ;
318  double phi = gRandom->Uniform(2*M_PI) ;
319  JVersor3D original_dir( p.back()-trg->getPosition() ) ;
320  const JRotation3D R( original_dir ) ;
321  JVersor3D new_dir( JAngle3D(theta,phi) ) ;
322  new_dir = new_dir.rotate_back(R) ;
323  p.back() = trg->getPosition() + trg->getRadius() * JVector3D(new_dir) ;
324  }
325 
326  return type ;
327  }
328 
329  std::vector<JPhotonPath> JMarkovPathGenerator::generateEnsemble( int n, const JPhotonPath& start_path, JSourceModel* src, JScatteringModel* sm, JTargetModel* trg, double lambda_abs, int nsteps_burn_in, int nsteps_save ) {
331 
332  JPhotonPath testpath(start_path) ;
333 
334  // check that the starting path is okay
335  double dist = testpath[0].getDistanceSquared(src->getPosition()) ;
336  if( dist != 0 ) {
337  cerr << "ERROR in generateEnsemble: testpath position " << testpath[0]
338  << "does not match source position " << src->getPosition() << "!" << endl ;
339  return result ;
340  }
341  {
342  double rho = getPhotonPathProbabilityDensity(testpath,src,sm,trg,lambda_abs) ;
343  if( rho == 0 ) {
344  cerr << "ERROR in generateEnsemble: testpath probability density = 0" << endl ;
345  return result ;
346  }
347  }
348 
349  // convert the path to remapped coordinates
350  if( apply_coordinate_remapping ) testpath = getRemappedPhotonPath(testpath) ;
351 
352  // burn-in
353  int i = 0 ;
354  while( i!=nsteps_burn_in ) {
355  doMarkovStep(src,sm,trg,lambda_abs,testpath) ;
356  ++i ;
357  }
358 
359  // ensemble generation
360  i = 0 ;
361  naccepted_r = 0 ;
362  nrejected_r = 0 ;
363  naccepted_t = 0 ;
364  nrejected_t = 0 ;
365  while( i!=n ) {
366  // take nsteps_save steps
367  int j = 0 ;
368  while( j!=nsteps_save ) {
369  if( doMarkovStep(src,sm,trg,lambda_abs,testpath) ) {
370  ++j ;
371  }
372  }
373  // save the path to the ensemble
375  result.push_back( getUnmappedPhotonPath(testpath) ) ;
376  } else {
377  result.push_back( testpath ) ;
378  }
379  ++i ;
380  }
381 
382  return result ;
383  }
384 
386  int nscat = p.n-2 ;
387  if( nscat == 0 && !trg->getRadius()>0 ) {
388  return false ;
389  }
390 
391  // copy path
392  JPhotonPath copy = p ;
393 
394  double rhoBefore ;
396  rhoBefore = getPhotonPathProbabilityDensity(copy,src,sm,trg,lambda_abs) ;
397  } else {
398  // we are working in remapped coordinates, so we have to calculate a corrected
399  // probability density
400  JPhotonPath copy_unmapped = getUnmappedPhotonPath(copy) ;
401  rhoBefore = getPhotonPathProbabilityDensity(copy_unmapped,src,sm,trg,lambda_abs) ;
402  rhoBefore *= getRemappingCorrection(copy_unmapped,copy) ;
403  }
404 
405  if( rhoBefore == 0 ) {
406  cerr << "FATAL ERROR: starting probability density = 0" << endl ;
407  exit(1) ;
408  }
409 
410  // apply random change to the path
411  int type = randomPathChange(copy,trg) ;
412 
413  // recalculate log L
414  double rhoAfter ;
416  rhoAfter = getPhotonPathProbabilityDensity(copy,src,sm,trg,lambda_abs) ;
417  } else {
418  // we are working in remapped coordinates, so we have to calculate a corrected
419  // probability density
420  JPhotonPath copy_unmapped = getUnmappedPhotonPath(copy) ;
421  rhoAfter = getPhotonPathProbabilityDensity(copy_unmapped,src,sm,trg,lambda_abs) ;
422  rhoAfter *= getRemappingCorrection(copy_unmapped,copy) ;
423  }
424 
425  // use Metropolis-Hastings algorithm to keep or reject change
426  if( rhoAfter > rhoBefore ) {
427  // accept the change
428  p = copy ;
429  if( type < 2 ) ++naccepted_r ;
430  if( type == 2 ) ++naccepted_t ;
431  return true ;
432  } else {
433  // accept the change with some probability
434  double P = rhoAfter/rhoBefore ;
435  if( gRandom->Uniform()<P ) {
436  p = copy ;
437  if( type < 2 ) ++naccepted_r ;
438  if( type == 2 ) ++naccepted_t ;
439  return true ;
440  }
441  }
442  if( type < 2 ) ++nrejected_r ;
443  if( type == 2 ) ++nrejected_t ;
444  return false ;
445  }
446 
447 }
448 
449 #endif
Data structure for angles in three dimensions.
Definition: JAngle3D.hh:33
JPhotonPath getRemappedPhotonPath(const JPhotonPath &p)
returns a remapped version of the photon path
The JMarkovPathGenerator generates ensembles of photon paths using a Markov Chain Monte Carlo (MCMC)...
double getRadius() const
const JPosition3D & getPosition() const
A photon path.
Definition: JPhotonPath.hh:38
Rotation matrix.
Definition: JRotation3D.hh:111
int getNsteps()
get the number of Markov steps taken in the last call to generateEnsemble (after burn-in) ...
double getUnmappedDistance(double rprime)
inverse of getRemappedDistance
exit
Definition: JPizza.sh:36
data_type r[M+1]
Definition: JPolint.hh:742
double getDistance(const JVector3D &pos) const
Get distance to point.
Definition: JVector3D.hh:270
void setCoordinateRemapping(bool val=true)
activate or deactive coordinate remapping
int getNacceptedSteps()
get the number of accepted steps taken during the last call to generateEnsemble (after burn-in) ...
bool doMarkovStep(JSourceModel *src, JScatteringModel *sm, JTargetModel *trg, double lambda_abs, JPhotonPath &p)
make one Markov chain step for a path
void setTangentialStepSize_deg(double val)
Set the average step size theta in degrees for steps in the tangential direction for the scattering v...
const int n
Definition: JPolint.hh:660
const JPosition3D & getPosition() const
std::vector< JPhotonPath > generateEnsemble(int n, const JPhotonPath &start_path, JSourceModel *src, JScatteringModel *sm, JTargetModel *trg, double lambda_abs, int nsteps_burn_in, int nsteps_save)
Generate an ensemble of n paths with a fixed number of scatterings by MCMC-sampling the given scatter...
Data structure for vector in three dimensions.
Definition: JVector3D.hh:34
JPosition3D getRemappedPosition(const JPosition3D &xprev, const JPosition3D &x)
Return the remapped vertex position of x.
double getRemappingCorrection(const JPhotonPath &p, const JPhotonPath &pprime)
Return the remapping correction for an entire photon path (product of the remapping correction for th...
double getFractionAccepted_radial()
get the fraction of steps that were accepted when a radial step was performed during the last call to...
double getFractionAccepted()
get the fraction of accepted steps during the last call to generateEnsemble (after burn-in) ...
return result
Definition: JPolint.hh:727
Virtual base class for a light source.
T pow(const T &x, const double y)
Power .
Definition: JMath.hh:98
bool getCoordinateRemapping()
returns true when coordinate remapping is activated, false otherwise
then usage $script[distance] fi case set_variable R
Definition: JDrawLED.sh:43
double getR0()
return R0; the length scale used in the coordinate remapping
double getFractionAccepted_tangential()
get the fraction of steps that were accepted when a tangential step was performed during the last cal...
JPhotonPath getUnmappedPhotonPath(const JPhotonPath &pprime)
inverse of getRemappedPhotonPath
JPosition3D getUnmappedPosition(const JPosition3D &xprev, const JPosition3D &xprime)
Inverse of getRemappedPosition.
double getRemappedDistance(double r)
return the distance between the remapped vertex and the previous vertex given the distance between th...
JMarkovPathGenerator()
standard constructor
int getNrejectedSteps()
get the number of rejected steps during the last call to generateEnsemble (after burn-in) ...
void copy(const Head &from, JHead &to)
Copy header from from to to.
Definition: JHead.cc:139
Virtual base class for a scattering model.
Virtual base class for a light detector (&quot;photon target&quot;).
int j
Definition: JPolint.hh:666
Data structure for position in three dimensions.
Definition: JPosition3D.hh:36
void setRadialStepSize_m(double val)
set the average step size in [m] in the radial direction for the scattering vertices ...
Data structure for normalised vector in three dimensions.
Definition: JVersor3D.hh:26
virtual int randomPathChange(JPhotonPath &p, JTargetModel *trg)
double getPhotonPathProbabilityDensity(JPhotonPath &p, JSourceModel *src, JScatteringModel *sm, JTargetModel *trg, double lambda_abs)
Return the probability density for a photon path with the given ingredients.
double getRemappingCorrection(const JPosition3D &xprev, const JPosition3D &xprime)
Returns the conversion factor J needed to compute the path probability density in the remapped coordi...
then $DIR JPlotNPE PDG P
Definition: JPlotNPE-PDG.sh:62
void setTargetStepSize_deg(double val)
set the average step size in degrees for the impact point on the target