CORSIKAGen_module.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 /// \file CORSIKAGen_module.cc
3 /// \brief Generator for cosmic-ray secondaries based on pre-generated CORSIKA shower databases.
4 ///
5 /// \author Matthew.Bass@physics.ox.ac.uk
6 ////////////////////////////////////////////////////////////////////////
7 
8 // ROOT includes
9 #include "TDatabasePDG.h"
10 #include "TString.h"
11 #include "TSystem.h" //need BaseName and DirName
12 
13 // Framework includes
17 #include "fhiclcpp/ParameterSet.h"
20 
21 // art extensions
22 #include "nurandom/RandomUtils/NuRandomService.h"
23 
24 // larsoft includes
29 
30 #include <sqlite3.h>
31 #include "CLHEP/Random/RandFlat.h"
32 #include "CLHEP/Random/RandPoissonQ.h"
33 #include "ifdh.h" //to handle flux files
34 
35 namespace evgen {
36 
37  /**
38  * @brief LArSoft interface to CORSIKA event generator.
39  *
40  * @note A presentation on this module by the original author is archived at:
41  * https://indico.fnal.gov/event/10893/contribution/3/material/slides
42  *
43  * In CORSIKA jargon, a "shower" is the cascade of particles resulting from
44  * a primary cosmic ray interaction.
45  * This module creates a single `simb::MCTruth` object (stored as data product
46  * into a `std::vector<simb::MCTruth>` with a single entry) containing all
47  * the particles from cosmic ray showers crossing a _surface_ above the
48  * detector.
49  *
50  * The generation procedure consists of selecting showers from a database of
51  * *pregenerated* events, and then to adapt them to the parameters requested
52  * in the module configuration. Pregenerated showers are "observed" at a
53  * altitude set in CORSIKA configuration.
54  *
55  * Databases need to be stored as files in SQLite3 format. Multiple file
56  * sources can be specified (`ShowerInputFiles` configuration parameter).
57  * From each source, one database file is selected and copied locally via
58  * IFDH.
59  * From each source, showers are extracted proportionally to the relative flux
60  * specified in the configuration (specified in `ShowerFluxConstants`,
61  * see @ref CORSIKAGen_Normalization "normalization" below).
62  * The actual number of showers per event and per source is extracted
63  * according to a Poisson distribution around the predicted average number of
64  * primary cosmic rays for that source.
65  *
66  *
67  * Flux normalization
68  * -------------------
69  *
70  * @anchor CORSIKAGen_Normalization
71  *
72  * CORSIKA generates showers from each specific cosmic ray type @f$ A @f$
73  * (e.g. iron, proton, etc.) according to a power law distribution
74  * @f$ \Phi_{A}(E) \propto E^{-\gamma_{A}} @f$ of the primary
75  * particle energy @f$ E @f$ [GeV]. When sampling pregenerated events, we
76  * bypass the normalization imposed by CORSIKA and gain complete
77  * control on it.
78  *
79  * Within CORSIKAGen, for each source (usually each on a different primary
80  * cosmic ray type, e.g. iron, proton, etc.), the average number of generated
81  * showers is
82  * @f$ n_{A} = \pi S T k_{A} \int E^{-\gamma_{A}} dE @f$ with @f$ S @f$ the
83  * area of the surface the flux passes across, @f$ T @f$ the exposure time,
84  * the integral defined over the full energy range of the pregenerated showers
85  * in the source, and @f$ k_{A} @f$ a factor specified in the configuration
86  * (`ShowerFluxConstants` parameters).
87  * This is the flux of primary cosmic rays, not of the observed particles
88  * from their showers. Note that it depends on an area and a time interval,
89  * but it is uniform with respect to translations and constant in time.
90  *
91  * As explained @ref CORSIKAGen_Coverage "below", we consider only the
92  * secondary particles that cross an "observation" surface.
93  * After cosmic ray primary particles cross the flux surface (@f$ S_{\Phi} @f$
94  * above), they develop into showers of particles that spread across large
95  * areas. Limiting ourself to the observation of particles on a small surface
96  * @f$ S_{o} @f$ has two effects. We lose the part of the showers that misses
97  * that surface @f$ S_{o} @f$. Also, considering a span of time with
98  * multiple showers, we miss particles from other hypothetical showers
99  * whose primaries crossed outside the generation surface @f$ S_{\Phi} @f$
100  * whose shower development would leak into @f$ S_{o} @f$: we did not simulate
101  * those showers at all!
102  * In terms of total flux of observed particles, under the assumption that the
103  * flux is uniform in space, choosing @f$ S_{\Phi} @f$ the same size as
104  * @f$ S_{o} @f$ makes the two effects get the same size: just as many
105  * particles from primaries from @f$ S_{\Phi} @f$ leak out of @f$ S_{o} @f$,
106  * as many particles from primaries from outside @f$ S_{\Phi} @f$ sneak in
107  * @f$ S_{o} @f$.
108  * In that case, counting _all_ the particles from the primaries crossing a
109  * surface @f$ S_{\Phi} @f$ of area _S_ regardless of where they land
110  * yields the right amount of cosmic ray secondary particles across an
111  * observation surface @f$ S_{o} @f$ also of area _S_. Practically,
112  * the particles landing outside @f$ S_{o} @f$ need to be recovered to
113  * preserve the correct normalization, which is described in the
114  * @ref CORSIKAGen_Coverage "next section".
115  *
116  *
117  * Surface coverage, position and timing
118  * --------------------------------------
119  *
120  * @anchor CORSIKAGen_Coverage
121  *
122  * The surface we detect the particles through (let's call it @f$ S_{d} @f$)
123  * is defined by the smallest _rectangle_ including all cryostats in the
124  * detector, and located at the height of the ceiling of the tallest cryostat.
125  * This surface can be increased by specifying a positive value for
126  * `ShowerAreaExtension` configuration parameter, in which case each side of
127  * the rectangle will be extended by that amount.
128  *
129  * Showers are extracted one by one from the pregenerated samples and treated
130  * independently.
131  * Ideally, the detection surface @f$ S_{d} @f$ would be at the same exact
132  * altitude as the observation surface set in CORSIKA (called @f$ S_{o} @f$
133  * above). In practice, we go the other way around, with the assumption that
134  * the shower observed at @f$ S_{d} @f$ would be very close to the one we
135  * actually generated at @f$ S_{o} @f$, and teleport the generated particles
136  * on @f$ S_{d} @f$. Since the cryostats may be just meters from the earth
137  * surface @f$ S_{o} @f$ lies on, this is an acceptable approximation.
138  *
139  * All the particles of one shower are compelled within surface @f$ S_{d} @f$
140  * as a first step. As explained when describing the
141  * @anchor CORSIKAGen_Normalization "normalization", we need to keep all the
142  * shower particles, one way or the other.
143  * So, particles of the shower that fell out of @f$ S_{d} @f$ are repackaged
144  * into other showers and translated back in. This is equivalent to assume the
145  * primary cosmic rays originating such shower would happen outside
146  * our generation volume (@f$ S_{\Phi} @f$), and their shower would then spill
147  * into @f$ S_{d} @f$. Since these repackaged showers are in principle
148  * independent and uncorrelated, they are assigned a random time different
149  * than the main shower, leveraging the assumption of constantness of the
150  * flux.
151  *
152  * As for the azimuth, this module uses an approximation by setting north
153  * direction to match the _z_ axis of LArSoft geometry (typically assumed
154  * to be the direction of the beam particle).
155  *
156  * The particles so manipulated are then back-propagated from the observation
157  * surface to an absolute height defined by `ProjectToHeight` (although for
158  * particular combination of position and direction, the particles might be
159  * propagated back to the edge of the world, or even outside it).
160  *
161  * As a final filter, only the particles whose straight projections cross any
162  * of the cryostats (with some buffer volume around, defined by `BufferBox`)
163  * are stored, while the other ones are discarded. Note that the actual
164  * interactions that particles from the generation surface undergo may deviate
165  * them enough to miss the cryostats anyway, and conversely particles that
166  * have been filtered out because shooting off the cryostats might have been
167  * subsequently deviated to actually cross them. This effect is not corrected
168  * for at this time.
169  *
170  * The time of the showers is uniformly distributed within the configured
171  * time interval, defined by `SampleTime` starting from `TimeOffset`.
172  *
173  *
174  * Configuration parameters
175  * =========================
176  *
177  * * `ShowerInputFiles` (list of paths; mandatory): a list of file paths to
178  * pregenerated CORSIKA shower files. Each entry can be a single file or
179  * use wildcards (`*`) to specify a set of files to choose among.
180  * Paths and wildcards are processed by IFDH.
181  * * `ShowerFluxConstants` (list of real numbers; mandatory): for each entry
182  * @f$ A @f$ in `ShowerInputFiles`, specify the normalization factor
183  * @f$ K_{A} @f$ of their distribution [@f$ m^{-2}s^{-1} @f$]
184  * * `ProjectToHeight` (real, default: `0`): the generated particles will
185  * appear to come from this height [cm]
186  * * `TimeOffset` (real; default: `0`): start time of the exposure window [s],
187  * relative to the
188  * @ref DetectorClocksSimulationTime "simulation time start"
189  * * `SampleTime` (real; mandatory): duration of the simulated exposure to
190  * cosmic rays [s]
191  * * `ShowerAreaExtension` (real; default: `0`):
192  * extend the size of the observation surface of shower particles (_S_)
193  * by this much [cm]; e.g. 1000 will extend 10 m on each side
194  * * `RandomXZShift` (real; default: `0`): the original position of each
195  * shower is randomly shifted within a square with this length as side
196  * [cm]
197  * * `BufferBox` (list of six lengths, all `0` by default):
198  * extension to the volume of each cryostat for the purpose of filtering
199  * out the particles which do not cross the detector; each cryostat volume
200  * is independently extended by the same amount, specified here as
201  * *shifts* to lower _x_, higher _x_, lower _y_, higher _y_, lower _z_
202  * and higher _z_, in that order [cm] (note that to extend e.g. the
203  * negative _x_ side by 5 meters the parameter value should be -500)
204  * * `SeedGenerator` (integer): force random number generator for event
205  * generation to the specified value
206  * * `SeedPoisson` (integer): force random number generator for number of
207  * showers to the specified value
208  * * `Seed`: alias for `SeedGenerator`
209  *
210  *
211  * Random engines
212  * ---------------
213  *
214  * Currently two random engines are used:
215  * * a generator engine (driven by `SeedGenerator`), of general use
216  * * a "Poisson" engine (driven by `SeedPoisson`), only used to determine the
217  * number of showers to be selected on each event
218  *
219  */
220  class CORSIKAGen : public art::EDProducer {
221  public:
222  explicit CORSIKAGen(fhicl::ParameterSet const& pset);
223  virtual ~CORSIKAGen();
224 
225  void produce(art::Event& evt);
226  void beginRun(art::Run& run);
227 
228 
229  private:
230  void openDBs(std::string const& module_label);
231  void populateNShowers();
232  void populateTOffset();
233  void GetSample(simb::MCTruth&);
234  double wrapvar( const double var, const double low, const double high);
235  double wrapvarBoxNo( const double var, const double low, const double high, int& boxno);
236  /**
237  * @brief Propagates a point back to the surface of a box.
238  * @param xyz coordinates of the point to be propagated (`{ x, y, z }`)
239  * @param dxyz direction of the point (`{ dx, dy, dz }`)
240  * @param xlo lower _x_ coordinate of the target box
241  * @param xhi upper _x_ coordinate of the target box
242  * @param ylo lower _y_ coordinate of the target box
243  * @param yhi upper _y_ coordinate of the target box
244  * @param zlo lower _z_ coordinate of the target box
245  * @param zhi upper _z_ coordinate of the target box
246  * @param xyzout _(output, room for at least 3 numbers)_ propagated point
247  *
248  * The point `xyz`, assumed to be inside the box, is propagated at the level
249  * of _the closest among the sides of the box_. Note that this means the
250  * propagated point might still be not on the surface of the box, even if it
251  * would later reach it.
252  * It is anyway guaranteed that `xyzout` is not inside the target box, and
253  * that at least one of its three coordinates is on the box surface.
254  */
255  void ProjectToBoxEdge(const double xyz[],
256  const double dxyz[],
257  const double xlo,
258  const double xhi,
259  const double ylo,
260  const double yhi,
261  const double zlo,
262  const double zhi,
263  double xyzout[]);
264 
265  int fShowerInputs=0; ///< Number of shower inputs to process from
266  std::vector<double> fNShowersPerEvent; ///< Number of showers to put in each event of duration fSampleTime; one per showerinput
267  std::vector<int> fMaxShowers; //< Max number of showers to query, one per showerinput
268  double fShowerBounds[6]={0.,0.,0.,0.,0.,0.}; ///< Boundaries of area over which showers are to be distributed (x(min), x(max), _unused_, y, z(min), z(max) )
269  double fToffset_corsika=0.; ///< Timing offset to account for propagation time through atmosphere, populated from db
270  ifdh_ns::ifdh* fIFDH=0; ///< (optional) flux file handling
271 
272  //fcl parameters
273  double fProjectToHeight=0.; ///< Height to which particles will be projected [cm]
274  std::vector< std::string > fShowerInputFiles; ///< Set of CORSIKA shower data files to use
275  std::string fShowerCopyType; // ifdh file selection and copying (default) or direct file path
276  std::vector< double > fShowerFluxConstants; ///< Set of flux constants to be associated with each shower data file
277  double fSampleTime=0.; ///< Duration of sample [s]
278  double fToffset=0.; ///< Time offset of sample, defaults to zero (no offset) [s]
279  std::vector<double> fBuffBox; ///< Buffer box extensions to cryostat in each direction (6 of them: x_lo,x_hi,y_lo,y_hi,z_lo,z_hi) [cm]
280  double fShowerAreaExtension=0.; ///< Extend distribution of corsika particles in x,z by this much (e.g. 1000 will extend 10 m in -x, +x, -z, and +z) [cm]
281  sqlite3* fdb[5]; ///< Pointers to sqlite3 database object, max of 5
282  double fRandomXZShift=0.; ///< Each shower will be shifted by a random amount in xz so that showers won't repeatedly sample the same space [cm]
283  CLHEP::HepRandomEngine& fGenEngine;
284  CLHEP::HepRandomEngine& fPoisEngine;
285  };
286 }
287 
288 namespace evgen{
289 
291  : EDProducer{p},
292  fProjectToHeight(p.get< double >("ProjectToHeight",0.)),
293  fShowerInputFiles(p.get< std::vector< std::string > >("ShowerInputFiles")),
294  fShowerCopyType(p.get< std::string >("ShowerCopyType", "IFDH")),
295  fShowerFluxConstants(p.get< std::vector< double > >("ShowerFluxConstants")),
296  fSampleTime(p.get< double >("SampleTime",0.)),
297  fToffset(p.get< double >("TimeOffset",0.)),
298  fBuffBox(p.get< std::vector< double > >("BufferBox",{0.0, 0.0, 0.0, 0.0, 0.0, 0.0})),
299  fShowerAreaExtension(p.get< double >("ShowerAreaExtension",0.)),
300  fRandomXZShift(p.get< double >("RandomXZShift",0.)),
301  fGenEngine(art::ServiceHandle<rndm::NuRandomService>{}->createEngine(*this, "HepJamesRandom", "gen", p, { "Seed", "SeedGenerator"})),
302  fPoisEngine(art::ServiceHandle<rndm::NuRandomService>{}->createEngine(*this, "HepJamesRandom", "pois", p, "SeedPoisson"))
303  {
304  if(fShowerInputFiles.size() != fShowerFluxConstants.size() || fShowerInputFiles.size()==0 || fShowerFluxConstants.size()==0)
305  throw cet::exception("CORSIKAGen") << "ShowerInputFiles and ShowerFluxConstants have different or invalid sizes!"<<"\n";
307 
308  if(fSampleTime==0.) throw cet::exception("CORSIKAGen") << "SampleTime not set!";
309 
310  if(fProjectToHeight==0.) mf::LogInfo("CORSIKAGen")<<"Using 0. for fProjectToHeight!"
311  ;
312  // create a default random engine; obtain the random seed from NuRandomService,
313  // unless overridden in configuration with key "Seed"
314 
315  this->openDBs(p.get<std::string>("module_label"));
316  this->populateNShowers();
317  this->populateTOffset();
318 
319  produces< std::vector<simb::MCTruth> >();
320  produces< sumdata::RunData, art::InRun >();
321 
322  }
323 
325  for(int i=0; i<fShowerInputs; i++){
326  sqlite3_close(fdb[i]);
327  }
328  //cleanup temp files
329  fIFDH->cleanup();
330  }
331 
332  void CORSIKAGen::ProjectToBoxEdge( const double xyz[],
333  const double indxyz[],
334  const double xlo,
335  const double xhi,
336  const double ylo,
337  const double yhi,
338  const double zlo,
339  const double zhi,
340  double xyzout[] ){
341 
342 
343  //we want to project backwards, so take mirror of momentum
344  const double dxyz[3]={-indxyz[0],-indxyz[1],-indxyz[2]};
345 
346  // Compute the distances to the x/y/z walls
347  double dx = 99.E99;
348  double dy = 99.E99;
349  double dz = 99.E99;
350  if (dxyz[0] > 0.0) { dx = (xhi-xyz[0])/dxyz[0]; }
351  else if (dxyz[0] < 0.0) { dx = (xlo-xyz[0])/dxyz[0]; }
352  if (dxyz[1] > 0.0) { dy = (yhi-xyz[1])/dxyz[1]; }
353  else if (dxyz[1] < 0.0) { dy = (ylo-xyz[1])/dxyz[1]; }
354  if (dxyz[2] > 0.0) { dz = (zhi-xyz[2])/dxyz[2]; }
355  else if (dxyz[2] < 0.0) { dz = (zlo-xyz[2])/dxyz[2]; }
356 
357 
358  // Choose the shortest distance
359  double d = 0.0;
360  if (dx < dy && dx < dz) d = dx;
361  else if (dy < dz && dy < dx) d = dy;
362  else if (dz < dx && dz < dy) d = dz;
363 
364  // Make the step
365  for (int i = 0; i < 3; ++i) {
366  xyzout[i] = xyz[i] + dxyz[i]*d;
367  }
368 
369  }
370 
371  void CORSIKAGen::openDBs(std::string const& module_label){
372  //choose files based on fShowerInputFiles, copy them with ifdh, open them
373  // for c2: statement is unused
374  //sqlite3_stmt *statement;
375  CLHEP::RandFlat flat(fGenEngine);
376 
377  //setup ifdh object
378  if ( ! fIFDH ) fIFDH = new ifdh_ns::ifdh;
379  const char* ifdh_debug_env = std::getenv("IFDH_DEBUG_LEVEL");
380  if ( ifdh_debug_env ) {
381  mf::LogInfo("CORSIKAGen") << "IFDH_DEBUG_LEVEL: " << ifdh_debug_env<<"\n";
382  fIFDH->set_debug(ifdh_debug_env);
383  }
384 
385  //get ifdh path for each file in fShowerInputFiles, put into selectedflist
386  //if 1 file returned, use that file
387  //if >1 file returned, randomly select one file
388  //if 0 returned, make exeption for missing files
389  std::vector<std::pair<std::string,long>> selectedflist;
390  for(int i=0; i<fShowerInputs; i++){
391  if(fShowerInputFiles[i].find("*")==std::string::npos){
392  //if there are no wildcards, don't call findMatchingFiles
393  selectedflist.push_back(std::make_pair(fShowerInputFiles[i],0));
394  mf::LogInfo("CorsikaGen") << "Selected"<<selectedflist.back().first<<"\n";
395  }else{
396  //use findMatchingFiles
397  //default to IFDH approach when wildcards in path
398  std::vector<std::pair<std::string,long>> flist;
399  std::string path(gSystem->DirName(fShowerInputFiles[i].c_str()));
400  std::string pattern(gSystem->BaseName(fShowerInputFiles[i].c_str()));
401  flist = fIFDH->findMatchingFiles(path,pattern);
402  unsigned int selIndex=-1;
403  if(flist.size()==1){ //0th element is the search path:pattern
404  selIndex=0;
405  }else if(flist.size()>1){
406  selIndex= (unsigned int) (flat()*(flist.size()-1)+0.5); //rnd with rounding, dont allow picking the 0th element
407  }else{
408  throw cet::exception("CORSIKAGen") << "No files returned for path:pattern: "<<path<<":"<<pattern<<std::endl;
409  }
410  selectedflist.push_back(flist[selIndex]);
411  mf::LogInfo("CorsikaGen") << "For "<<fShowerInputFiles[i]<<":"<<pattern
412  <<"\nFound "<< flist.size() << " candidate files"
413  <<"\nChoosing file number "<< selIndex << "\n"
414  <<"\nSelected "<<selectedflist.back().first<<"\n";
415  }
416 
417  }
418 
419  //do the fetching, store local filepaths in locallist
420  std::vector<std::string> locallist;
421  for(unsigned int i=0; i<selectedflist.size(); i++){
422  mf::LogInfo("CorsikaGen")
423  << "Fetching: "<<selectedflist[i].first<<" "<<selectedflist[i].second<<"\n";
424  if (fShowerCopyType == "IFDH") {
425  std::string fetchedfile(fIFDH->fetchInput(selectedflist[i].first));
426  MF_LOG_DEBUG("CorsikaGen") << "Fetched; local path: "<<fetchedfile << "; copy type: IFDH";
427  locallist.push_back(fetchedfile);
428  }
429  else if (fShowerCopyType == "DIRECT") {
430  std::string fetchedfile(selectedflist[i].first);
431  MF_LOG_DEBUG("CorsikaGen") << "Fetched; local path: "<<fetchedfile << "; copy type: DIRECT";
432  locallist.push_back(fetchedfile);
433  }
434  else throw cet::exception("CORSIKAGen") << "Error copying shower db: ShowerCopyType must be \"IFDH\" or \"DIRECT\"\n";
435  }
436 
437  //open the files in fShowerInputFilesLocalPaths with sqlite3
438  for(unsigned int i=0; i<locallist.size(); i++){
439  //prepare and execute statement to attach db file
440  int res=sqlite3_open(locallist[i].c_str(),&fdb[i]);
441  if (res!= SQLITE_OK)
442  throw cet::exception("CORSIKAGen") << "Error opening db: (" <<locallist[i].c_str()<<") ("<<res<<"): " << sqlite3_errmsg(fdb[i]) << "; memory used:<<"<<sqlite3_memory_used()<<"/"<<sqlite3_memory_highwater(0)<<"\n";
443  else
444  mf::LogInfo("CORSIKAGen")<<"Attached db "<< locallist[i]<<"\n";
445  }
446  }
447 
448  double CORSIKAGen::wrapvar( const double var, const double low, const double high){
449  //wrap variable so that it's always between low and high
450  return (var - (high - low) * floor(var/(high-low))) + low;
451  }
452 
453  double CORSIKAGen::wrapvarBoxNo( const double var, const double low, const double high, int& boxno){
454  //wrap variable so that it's always between low and high
455  boxno=int(floor(var/(high-low)));
456  return (var - (high - low) * floor(var/(high-low))) + low;
457  }
458 
460  //populate TOffset_corsika by finding minimum ParticleTime from db file
461 
462  sqlite3_stmt *statement;
463  const std::string kStatement("select min(t) from particles");
464  double t=0.;
465 
466  for(int i=0; i<fShowerInputs; i++){
467  //build and do query to get run min(t) from each db
468  if ( sqlite3_prepare(fdb[i], kStatement.c_str(), -1, &statement, 0 ) == SQLITE_OK ){
469  int res=0;
470  res = sqlite3_step(statement);
471  if ( res == SQLITE_ROW ){
472  t=sqlite3_column_double(statement,0);
473  mf::LogInfo("CORSIKAGen")<<"For showers input "<< i<<" found particles.min(t)="<<t<<"\n";
474  if (i==0 || t<fToffset_corsika) fToffset_corsika=t;
475  }else{
476  throw cet::exception("CORSIKAGen") << "Unexpected sqlite3_step return value: (" <<res<<"); "<<"ERROR:"<<sqlite3_errmsg(fdb[i])<<"\n";
477  }
478  }else{
479  throw cet::exception("CORSIKAGen") << "Error preparing statement: (" <<kStatement<<"); "<<"ERROR:"<<sqlite3_errmsg(fdb[i])<<"\n";
480  }
481  }
482 
483  mf::LogInfo("CORSIKAGen")<<"Found corsika timeoffset [ns]: "<< fToffset_corsika<<"\n";
484  }
485 
487  //populate vector of the number of showers per event based on:
488  //AREA the showers are being distributed over
489  //TIME of the event (fSampleTime)
490  //flux constants that determine the overall normalizations (fShowerFluxConstants)
491  //Energy range over which the sample was generated (ERANGE_*)
492  //power spectrum over which the sample was generated (ESLOPE)
493 
494 
495  //compute shower area based on the maximal x,z dimensions of cryostat boundaries + fShowerAreaExtension
497  for(unsigned int c = 0; c < geom->Ncryostats(); ++c){
498  double bounds[6] = {0.};
499  geom->CryostatBoundaries(bounds, c);
500  for (unsigned int bnd = 0; bnd<6; bnd++){
501  mf::LogVerbatim("CORSIKAGen")<<"Cryo Boundary: "<<bnd<<"="<<bounds[bnd]<<" ( + Buffer="<<fBuffBox[bnd]<<")\n";
502  if(fabs(bounds[bnd])>fabs(fShowerBounds[bnd])){
503  fShowerBounds[bnd]=bounds[bnd];
504  }
505  }
506  }
507  //add on fShowerAreaExtension without being clever
512 
513  double showersArea=(fShowerBounds[1]/100-fShowerBounds[0]/100)*(fShowerBounds[5]/100-fShowerBounds[4]/100);
514 
515  mf::LogInfo("CORSIKAGen")
516  << "Area extended by : "<<fShowerAreaExtension
517  <<"\nShowers to be distributed betweeen: x="<<fShowerBounds[0]<<","<<fShowerBounds[1]
518  <<" & z="<<fShowerBounds[4]<<","<<fShowerBounds[5]
519  <<"\nShowers to be distributed with random XZ shift: "<<fRandomXZShift<<" cm"
520  <<"\nShowers to be distributed over area: "<<showersArea<<" m^2"
521  <<"\nShowers to be distributed over time: "<<fSampleTime<<" s"
522  <<"\nShowers to be distributed with time offset: "<<fToffset<<" s"
523  <<"\nShowers to be distributed at y: "<<fShowerBounds[3]<<" cm"
524  ;
525 
526  //db variables
527  sqlite3_stmt *statement;
528  const std::string kStatement("select erange_high,erange_low,eslope,nshow from input");
529  double upperLimitOfEnergyRange=0.,lowerLimitOfEnergyRange=0.,energySlope=0.,oneMinusGamma=0.,EiToOneMinusGamma=0.,EfToOneMinusGamma=0.;
530 
531  for(int i=0; i<fShowerInputs; i++){
532  //build and do query to get run info from databases
533  // double thisrnd=flat();//need a new random number for each query
534  if ( sqlite3_prepare(fdb[i], kStatement.c_str(), -1, &statement, 0 ) == SQLITE_OK ){
535  int res=0;
536  res = sqlite3_step(statement);
537  if ( res == SQLITE_ROW ){
538  upperLimitOfEnergyRange=sqlite3_column_double(statement,0);
539  lowerLimitOfEnergyRange=sqlite3_column_double(statement,1);
540  energySlope = sqlite3_column_double(statement,2);
541  fMaxShowers.push_back(sqlite3_column_int(statement,3));
542  oneMinusGamma = 1 + energySlope;
543  EiToOneMinusGamma = pow(lowerLimitOfEnergyRange, oneMinusGamma);
544  EfToOneMinusGamma = pow(upperLimitOfEnergyRange, oneMinusGamma);
545  mf::LogVerbatim("CORSIKAGen")<<"For showers input "<< i<<" found e_hi="<<upperLimitOfEnergyRange<<", e_lo="<<lowerLimitOfEnergyRange<<", slope="<<energySlope<<", k="<<fShowerFluxConstants[i]<<"\n";
546  }else{
547  throw cet::exception("CORSIKAGen") << "Unexpected sqlite3_step return value: (" <<res<<"); "<<"ERROR:"<<sqlite3_errmsg(fdb[i])<<"\n";
548  }
549  }else{
550  throw cet::exception("CORSIKAGen") << "Error preparing statement: (" <<kStatement<<"); "<<"ERROR:"<<sqlite3_errmsg(fdb[i])<<"\n";
551  }
552 
553  //this is computed, how?
554  double NShowers=( M_PI * showersArea * fShowerFluxConstants[i] * (EfToOneMinusGamma - EiToOneMinusGamma) / oneMinusGamma )*fSampleTime;
555  fNShowersPerEvent.push_back(NShowers);
556  mf::LogVerbatim("CORSIKAGen")<<"For showers input "<< i
557  <<" the number of showers per event is "<<(int)NShowers<<"\n";
558  }
559  }
560 
562  //for each input, randomly pull fNShowersPerEvent[i] showers from the Particles table
563  //and randomly place them in time (between -fSampleTime/2 and fSampleTime/2)
564  //wrap their positions based on the size of the area under consideration
565  //based on http://nusoft.fnal.gov/larsoft/doxsvn/html/CRYHelper_8cxx_source.html (Sample)
566 
567  //query from sqlite db with select * from particles where shower in (select id from showers ORDER BY substr(id*0.51123124141,length(id)+2) limit 100000) ORDER BY substr(shower*0.51123124141,length(shower)+2);
568  //where 0.51123124141 is a random seed to allow randomly selecting rows and should be randomly generated for each query
569  //the inner order by is to select randomly from the possible shower id's
570  //the outer order by is to make sure the shower numbers are ordered randomly (without this, the showers always come out ordered by shower number
571  //and 100000 is the number of showers to be selected at random and needs to be less than the number of showers in the showers table
572 
573  //TDatabasePDG is for looking up particle masses
574  static TDatabasePDG* pdgt = TDatabasePDG::Instance();
575 
576  //db variables
577  sqlite3_stmt *statement;
578  const TString kStatement("select shower,pdg,px,py,pz,x,z,t,e from particles where shower in (select id from showers ORDER BY substr(id*%f,length(id)+2) limit %d) ORDER BY substr(shower*%f,length(shower)+2)");
579 
580  CLHEP::RandFlat flat(fGenEngine);
581  CLHEP::RandPoissonQ randpois(fPoisEngine);
582 
583  // get geometry and figure where to project particles to, based on CRYHelper
585  double x1, x2;
586  double y1, y2;
587  double z1, z2;
588  geom->WorldBox(&x1, &x2, &y1, &y2, &z1, &z2);
589 
590  // make the world box slightly smaller so that the projection to
591  // the edge avoids possible rounding errors later on with Geant4
592  double fBoxDelta=1.e-5;
593  x1 += fBoxDelta;
594  x2 -= fBoxDelta;
595  y1 += fBoxDelta;
596  y2 = fProjectToHeight;
597  z1 += fBoxDelta;
598  z2 -= fBoxDelta;
599 
600  //populate mctruth
601  int ntotalCtr=0; //count number of particles added to mctruth
602  int lastShower=0; //keep track of last shower id so that t can be randomized on every new shower
603  int nShowerCntr=0; //keep track of how many showers are left to be added to mctruth
604  int nShowerQry=0; //number of showers to query from db
605  int shower,pdg;
606  double px,py,pz,x,z,tParticleTime,etot,showerTime=0.,showerTimex=0.,showerTimez=0.,showerXOffset=0.,showerZOffset=0.,t;
607  for(int i=0; i<fShowerInputs; i++){
608  nShowerCntr=randpois.fire(fNShowersPerEvent[i]);
609  mf::LogInfo("CORSIKAGEN") << " Shower input " << i << " with mean " << fNShowersPerEvent[i] << " generating " << nShowerCntr;
610 
611  while(nShowerCntr>0){
612  //how many showers should we query?
613  if(nShowerCntr>fMaxShowers[i]){
614  nShowerQry=fMaxShowers[i]; //take the group size
615  }else{
616  nShowerQry=nShowerCntr; //take the rest that are needed
617  }
618  //build and do query to get nshowers
619  double thisrnd=flat(); //need a new random number for each query
620  TString kthisStatement=TString::Format(kStatement.Data(),thisrnd,nShowerQry,thisrnd);
621  MF_LOG_DEBUG("CORSIKAGen")<<"Executing: "<<kthisStatement;
622  if ( sqlite3_prepare(fdb[i], kthisStatement.Data(), -1, &statement, 0 ) == SQLITE_OK ){
623  int res=0;
624  //loop over database rows, pushing particles into mctruth object
625  while(1){
626  res = sqlite3_step(statement);
627  if ( res == SQLITE_ROW ){
628  /*
629  * Memo columns:
630  * [0] shower
631  * [1] particle ID (PDG)
632  * [2] momentum: x component [GeV/c]
633  * [3] momentum: y component [GeV/c]
634  * [4] momentum: z component [GeV/c]
635  * [5] position: x component [cm]
636  * [6] position: z component [cm]
637  * [7] time [ns]
638  * [8] energy [GeV]
639  */
640  shower=sqlite3_column_int(statement,0);
641  if(shower!=lastShower){
642  //each new shower gets its own random time and position offsets
643  showerTime=1e9*(flat()*fSampleTime); //converting from s to ns
644  showerTimex=1e9*(flat()*fSampleTime); //converting from s to ns
645  showerTimez=1e9*(flat()*fSampleTime); //converting from s to ns
646  //and a random offset in both z and x controlled by the fRandomXZShift parameter
647  showerXOffset=flat()*fRandomXZShift - (fRandomXZShift/2);
648  showerZOffset=flat()*fRandomXZShift - (fRandomXZShift/2);
649  }
650  pdg=sqlite3_column_int(statement,1);
651  //get mass for this particle
652  double m = 0.; // in GeV
653  TParticlePDG* pdgp = pdgt->GetParticle(pdg);
654  if (pdgp) m = pdgp->Mass();
655 
656  //Note: position/momentum in db have north=-x and west=+z, rotate so that +z is north and +x is west
657  //get momentum components
658  px=sqlite3_column_double(statement,4);//uboone x=Particlez
659  py=sqlite3_column_double(statement,3);
660  pz=-sqlite3_column_double(statement,2);//uboone z=-Particlex
661  etot=sqlite3_column_double(statement,8);
662 
663  //get/calculate position components
664  int boxnoX=0,boxnoZ=0;
665  x=wrapvarBoxNo(sqlite3_column_double(statement,6)+showerXOffset,fShowerBounds[0],fShowerBounds[1],boxnoX);
666  z=wrapvarBoxNo(-sqlite3_column_double(statement,5)+showerZOffset,fShowerBounds[4],fShowerBounds[5],boxnoZ);
667  tParticleTime=sqlite3_column_double(statement,7); //time offset, includes propagation time from top of atmosphere
668  //actual particle time is particle surface arrival time
669  //+ shower start time
670  //+ global offset (fcl parameter, in s)
671  //- propagation time through atmosphere
672  //+ boxNo{X,Z} time offset to make grid boxes have different shower times
673  t=tParticleTime+showerTime+(1e9*fToffset)-fToffset_corsika + showerTimex*boxnoX + showerTimez*boxnoZ;
674  //wrap surface arrival so that it's in the desired time window
675  t=wrapvar(t,(1e9*fToffset),1e9*(fToffset+fSampleTime));
676 
677  simb::MCParticle p(ntotalCtr,pdg,"primary",-200,m,1);
678 
679  //project back to wordvol/fProjectToHeight
680  /*
681  * This back propagation goes from a point on the upper surface of
682  * the cryostat back to the edge of the world, except that that
683  * world is cut short by `fProjectToHeight` (`y2`) ceiling.
684  * The projection will most often lie on that ceiling, but it may
685  * end up instead on one of the side edges of the world, or even
686  * outside it.
687  */
688  double xyzo[3];
689  double x0[3]={x,fShowerBounds[3],z};
690  double dx[3]={px,py,pz};
691  this->ProjectToBoxEdge(x0, dx, x1, x2, y1, y2, z1, z2, xyzo);
692 
693  TLorentzVector pos(xyzo[0],xyzo[1],xyzo[2],t);// time needs to be in ns to match GENIE, etc
694  TLorentzVector mom(px,py,pz,etot);
695  p.AddTrajectoryPoint(pos,mom);
696  mctruth.Add(p);
697  ntotalCtr++;
698  lastShower=shower;
699  }else if ( res == SQLITE_DONE ){
700  break;
701  }else{
702  throw cet::exception("CORSIKAGen") << "Unexpected sqlite3_step return value: (" <<res<<"); "<<"ERROR:"<<sqlite3_errmsg(fdb[i])<<"\n";
703  }
704  }
705  }else{
706  throw cet::exception("CORSIKAGen") << "Error preparing statement: (" <<kthisStatement<<"); "<<"ERROR:"<<sqlite3_errmsg(fdb[i])<<"\n";
707  }
708  nShowerCntr=nShowerCntr-nShowerQry;
709  }
710  }
711  }
712 
714  {
716  run.put(std::make_unique<sumdata::RunData>(geo->DetectorName()));
717  }
718 
720  std::unique_ptr< std::vector<simb::MCTruth> > truthcol(new std::vector<simb::MCTruth>);
721 
723 
724  simb::MCTruth truth;
726 
727  simb::MCTruth pretruth;
728  GetSample(pretruth);
729  mf::LogInfo("CORSIKAGen")<<"GetSample number of particles returned: "<<pretruth.NParticles()<<"\n";
730  // loop over particles in the truth object
731  for(int i = 0; i < pretruth.NParticles(); ++i){
732  simb::MCParticle particle = pretruth.GetParticle(i);
733  const TLorentzVector& v4 = particle.Position();
734  const TLorentzVector& p4 = particle.Momentum();
735  double x0[3] = {v4.X(), v4.Y(), v4.Z() };
736  double dx[3] = {p4.Px(), p4.Py(), p4.Pz()};
737 
738  // now check if the particle goes through any cryostat in the detector
739  // if so, add it to the truth object.
740  for(unsigned int c = 0; c < geom->Ncryostats(); ++c){
741  double bounds[6] = {0.};
742  geom->CryostatBoundaries(bounds, c);
743 
744  //add a buffer box around the cryostat bounds to increase the acceptance and account for scattering
745  //By default, the buffer box has zero size
746  for (unsigned int cb=0; cb<6; cb++)
747  bounds[cb] = bounds[cb]+fBuffBox[cb];
748 
749  //calculate the intersection point with each cryostat surface
750  bool intersects_cryo = false;
751  for (int bnd=0; bnd!=6; ++bnd) {
752  if (bnd<2) {
753  double p2[3] = {bounds[bnd], x0[1] + (dx[1]/dx[0])*(bounds[bnd] - x0[0]), x0[2] + (dx[2]/dx[0])*(bounds[bnd] - x0[0])};
754  if ( p2[1] >= bounds[2] && p2[1] <= bounds[3] &&
755  p2[2] >= bounds[4] && p2[2] <= bounds[5] ) {
756  intersects_cryo = true;
757  break;
758  }
759  }
760  else if (bnd>=2 && bnd<4) {
761  double p2[3] = {x0[0] + (dx[0]/dx[1])*(bounds[bnd] - x0[1]), bounds[bnd], x0[2] + (dx[2]/dx[1])*(bounds[bnd] - x0[1])};
762  if ( p2[0] >= bounds[0] && p2[0] <= bounds[1] &&
763  p2[2] >= bounds[4] && p2[2] <= bounds[5] ) {
764  intersects_cryo = true;
765  break;
766  }
767  }
768  else if (bnd>=4) {
769  double p2[3] = {x0[0] + (dx[0]/dx[2])*(bounds[bnd] - x0[2]), x0[1] + (dx[1]/dx[2])*(bounds[bnd] - x0[2]), bounds[bnd]};
770  if ( p2[0] >= bounds[0] && p2[0] <= bounds[1] &&
771  p2[1] >= bounds[2] && p2[1] <= bounds[3] ) {
772  intersects_cryo = true;
773  break;
774  }
775  }
776  }
777 
778  if (intersects_cryo){
779  truth.Add(particle);
780  break; //leave loop over cryostats to avoid adding particle multiple times
781  }// end if particle goes into a cryostat
782  }// end loop over cryostats in the detector
783 
784  }// loop on particles
785 
786  mf::LogInfo("CORSIKAGen")<<"Number of particles from getsample crossing cryostat + bounding box: "<<truth.NParticles()<<"\n";
787 
788  truthcol->push_back(truth);
789  evt.put(std::move(truthcol));
790 
791  return;
792  }// end produce
793 
794 }// end namespace
795 
796 
797 namespace evgen{
798 
800 
801 }
double fToffset
Time offset of sample, defaults to zero (no offset) [s].
double wrapvar(const double var, const double low, const double high)
MaybeLogger_< ELseverityLevel::ELsev_info, true > LogVerbatim
const TLorentzVector & Position(const int i=0) const
Definition: MCParticle.h:219
double fSampleTime
Duration of sample [s].
Format
Definition: utils.h:7
void AddTrajectoryPoint(TLorentzVector const &position, TLorentzVector const &momentum)
Definition: MCParticle.h:257
double fProjectToHeight
Height to which particles will be projected [cm].
void SetOrigin(simb::Origin_t origin)
Definition: MCTruth.h:82
std::string string
Definition: nybbler.cc:12
MaybeLogger_< ELseverityLevel::ELsev_info, false > LogInfo
void WorldBox(double *xlo, double *xhi, double *ylo, double *yhi, double *zlo, double *zhi) const
Fills the arguments with the boundaries of the world.
EDProducer(fhicl::ParameterSet const &pset)
Definition: EDProducer.h:20
constexpr T pow(T x)
Definition: pow.h:72
LArSoft interface to CORSIKA event generator.
struct sqlite3_stmt sqlite3_stmt
int NParticles() const
Definition: MCTruth.h:75
ifdh_ns::ifdh * fIFDH
(optional) flux file handling
unsigned int Ncryostats() const
Returns the number of cryostats in the detector.
Particle class.
std::vector< std::string > fShowerInputFiles
Set of CORSIKA shower data files to use.
void CryostatBoundaries(double *boundaries, geo::CryostatID const &cid) const
Returns the boundaries of the specified cryostat.
art framework interface to geometry description
Definition: Run.h:17
int fShowerInputs
Number of shower inputs to process from.
sqlite3 * fdb[5]
Pointers to sqlite3 database object, max of 5.
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:67
std::string DetectorName() const
Returns a string with the name of the detector, as configured.
std::vector< double > fShowerFluxConstants
Set of flux constants to be associated with each shower data file.
std::string getenv(std::string const &name)
Definition: getenv.cc:15
def move(depos, offset)
Definition: depos.py:107
void ProjectToBoxEdge(const double xyz[], const double dxyz[], const double xlo, const double xhi, const double ylo, const double yhi, const double zlo, const double zhi, double xyzout[])
Propagates a point back to the surface of a box.
p
Definition: test.py:223
CLHEP::HepRandomEngine & fPoisEngine
ProductID put(std::unique_ptr< PROD > &&edp, std::string const &instance={})
Definition: DataViewImpl.h:686
double wrapvarBoxNo(const double var, const double low, const double high, int &boxno)
std::vector< int > fMaxShowers
BoundingBox bounds(int x, int y, int w, int h)
Definition: main.cpp:37
struct sqlite3 sqlite3
const simb::MCParticle & GetParticle(int i) const
Definition: MCTruth.h:76
#define M_PI
Definition: includeROOT.h:54
double fToffset_corsika
Timing offset to account for propagation time through atmosphere, populated from db.
std::string fShowerCopyType
void produce(art::Event &evt)
void Add(simb::MCParticle const &part)
Definition: MCTruth.h:80
int var
Definition: 018_def.c:9
std::string pattern
Definition: regex_t.cc:35
const TLorentzVector & Momentum(const int i=0) const
Definition: MCParticle.h:220
#define MF_LOG_DEBUG(id)
double fRandomXZShift
Each shower will be shifted by a random amount in xz so that showers won&#39;t repeatedly sample the same...
double fShowerBounds[6]
Boundaries of area over which showers are to be distributed (x(min), x(max), unused, y, z(min), z(max) )
CORSIKAGen(fhicl::ParameterSet const &pset)
void beginRun(art::Run &run)
list x
Definition: train.py:276
TCEvent evt
Definition: DataStructs.cxx:7
Event generator information.
Definition: MCTruth.h:32
void GetSample(simb::MCTruth &)
std::vector< double > fNShowersPerEvent
Number of showers to put in each event of duration fSampleTime; one per showerinput.
CLHEP::HepRandomEngine & fGenEngine
LArSoft geometry interface.
Definition: ChannelGeo.h:16
Event Generation using GENIE, cosmics or single particles.
void openDBs(std::string const &module_label)
double fShowerAreaExtension
Extend distribution of corsika particles in x,z by this much (e.g. 1000 will extend 10 m in -x...
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
Cosmic rays.
Definition: MCTruth.h:24
QTextStream & endl(QTextStream &s)
std::vector< double > fBuffBox
Buffer box extensions to cryostat in each direction (6 of them: x_lo,x_hi,y_lo,y_hi,z_lo,z_hi) [cm].