CorsikaShowerFile.cc
Go to the documentation of this file.
1 
12 #include <io/CorsikaShowerFile.h>
13 #include <io/CorsikaShowerFileParticleIterator.h>
14 #include <io/CorsikaShowerFileGeometryProducer.h>
15 #include <io/CorsikaIOException.h>
16 #include <io/RawCorsikaFile.h>
17 #include <io/CorsikaBlock.h>
18 #include <io/CorsikaUtilities.h>
19 
20 #include <evt/Event.h>
21 #include <evt/ShowerSimData.h>
22 #include <evt/GaisserHillas6Parameter.h>
23 #include <evt/AtmosphereParameters.h>
24 #include <evt/DefaultShowerGeometryProducer.h>
25 
26 #include <utl/TabularStream.h>
27 #include <utl/ErrorLogger.h>
28 #include <utl/AugerUnits.h>
29 #include <utl/TabulatedFunction.h>
30 #include <utl/MathConstants.h>
31 #include <utl/Math.h>
32 #include <utl/PhysicalConstants.h>
33 #include <utl/PhysicalFunctions.h>
34 #include <utl/Reader.h>
35 #include <utl/String.h>
36 #include <utl/VParticleProperties.h>
37 #include <utl/ShowerParticleList.h>
38 #include <utl/ParticlePropertiesFactory.h>
39 #include <utl/Point.h>
40 #include <utl/UTMPoint.h>
41 
42 #include <fwk/CentralConfig.h>
43 #include <fwk/CoordinateSystemRegistry.h>
44 
45 #include <boost/format.hpp>
46 #include <boost/lexical_cast.hpp>
47 #include <boost/algorithm/string/trim.hpp>
48 #include <boost/algorithm/string/replace.hpp>
49 #include <boost/algorithm/string.hpp>
50 #include <boost/algorithm/string/case_conv.hpp>
51 #include <boost/filesystem.hpp>
52 #include <boost/regex.hpp>
53 
54 #include <TMinuit.h>
55 #include <TMath.h>
56 
57 #include <sstream>
58 #include <string>
59 #include <cmath>
60 #include <iostream>
61 
62 using namespace io;
63 using namespace evt;
64 using namespace utl;
65 using boost::format;
66 // do not import, namespce pollution! using namespace std;
67 using std::string;
68 using std::vector;
69 using std::ostringstream;
70 using std::stringstream;
71 using std::ifstream;
72 using std::cerr;
73 using std::endl;
74 
75 
76 namespace io {
77 
78  namespace GHFit {
79 
80  // store the necessary variables outside the loop they are defined in
81  struct Profile {
82  vector<double> fSlantDepth;
83  vector<double> fEnergyDeposit;
85  };
86 
87 
88  // global data for Minuit fitting
90 
91 
92  // The 2 functions below and the structure are used for the correct calculation of GH parameters of up-going events
93 
94  inline
95  double
96  GaisserHillas4ParameterFunction(const double x, const double dEdXmax, const double x0, const double xmax, const double lambda)
97  {
98  const double xm0 = xmax - x0;
99  return dEdXmax * pow((x - x0) / xm0, xm0 / lambda) * exp((xmax - x)/lambda);
100  }
101 
102 
103  // function for TMinuit for dE/dX GH parameters -> get dEdXmax, xMax, x0 and lambda
104  // fit done for dE/dx in PeV/(g/cm2)
105  void
106  GH4ParamFitFunctiondEdxGH(int& /*npar*/, double* /*gin*/, double& f, double* par, int /*iflag*/)
107  {
108  double chi2 = 0;
109 
110  const double dEdXmax = par[0];
111  const double x0 = par[1];
112  const double xMax = par[2];
113  const double lambda = par[3];
114 
115  for (unsigned int i = 0, n = GHFit::gProfile.fSlantDepth.size(); i < n; ++i) {
116  const double x = GHFit::gProfile.fSlantDepth[i] / (g/cm2);
117  const double dEdX = GHFit::gProfile.fEnergyDeposit[i] / (PeV / (g/cm2));
118  //const double dEdXerr = 0.01 * data.EnergyDepositStruct[i] + 0.0003; // not used as we don't care about chi2 value
119  const double dEdXFct = GaisserHillas4ParameterFunction(x, dEdXmax, x0, xMax, lambda);
120  const double residual = dEdX - dEdXFct;
121  chi2 += Sqr(residual);
122  }
123 
124  f = chi2;
125  }
126 
127  }
128 
129 
130  inline
131  bool
132  Contains(const string& str, const string& what)
133  {
134  return str.find(what) != string::npos;
135  }
136 
137 
138  vector<string>
139  FixedColumnWidthSplit(const string& line,
140  const unsigned int nColumns,
141  const unsigned int firstColumnWidth, const unsigned int columnWidth)
142  {
143  using boost::algorithm::trim;
144 
145  if (!nColumns || line.length() != firstColumnWidth + (nColumns - 1)*columnWidth) {
146  const string err = "Line is too short to fix it.";
147  ERROR(err);
148  throw CorsikaIOException(err);
149  }
150  vector<string> res(nColumns);
151  res[0] = line.substr(0, firstColumnWidth);
152  trim(res[0]);
153  unsigned int pos = firstColumnWidth;
154  for (unsigned int i = 1; i < nColumns; ++i) {
155  res[i] = line.substr(pos, columnWidth);
156  trim(res[i]);
157  pos += columnWidth;
158  }
159  return res;
160  }
161 
162 
164  {
165  delete fParticleIterator;
166  }
167 
168 
169  CorsikaShowerFile::CorsikaShowerFile(const string& fileName,
170  const Mode mode,
171  utl::Branch* const branch)
172  {
173  if (!branch || !*branch) {
174  WARNING("No Branch was passed for configuration.");
175  } else {
176  fObservationLevel = 1;
177  if (branch->GetChild("CorsikaObservationLevel"))
178  branch->GetChild("CorsikaObservationLevel").GetData(fObservationLevel);
179  if (fObservationLevel < 1 || fObservationLevel > 10) {
180  ostringstream err;
181  err << "CORSIKA observation level must be within [1,10] and " << fObservationLevel << " is not allowed";
182  ERROR(err);
183  throw CorsikaIOException(err.str());
184  }
185  ostringstream info;
186  info << "Reading particles at observation level: " << fObservationLevel;
187  INFO(info);
188 
189  info.str("");
190  if (branch->GetChild("UseRealisticMagneticDeclination"))
191  branch->GetChild("UseRealisticMagneticDeclination").GetData(fUseRealisticMagneticDeclination);
192  info << "Using magnetic field declination from time-dependent model "
193  "(If False, a hardcoded value (-4.233 deg) is taken or the value"
194  " specified in the .reas file (if used)): "
195  << fUseRealisticMagneticDeclination;
196  INFO(info);
197 
198  Branch bRef = branch->GetChild("ReferenceCoordinateSystem");
199  if (bRef) {
200  fRefCoordinateSystemName = bRef.Get<string>();
201  ostringstream inf;
202  inf << "Core reference system: " << fRefCoordinateSystemName;
203  INFO(inf);
204  }
205  Branch bMulti = branch->GetChild("MultiCoreSelection");
206  if (bMulti) {
207  ostringstream info;
208  info << "Multi-core selection: ";
209  string entry = bMulti.Get<string>();
210  boost::trim(entry);
211  if (boost::to_lower_copy(entry) == "all") {
212  info << "all";
213  } else {
214  vector<string> strs;
215  vector<string> range;
216  boost::split(strs, entry, boost::is_any_of(","));
217  string comma = "";
218  for (const auto& it : strs) {
219  boost::split(range, it, boost::is_any_of("-"));
220  auto x = range.begin();
221  const int low = boost::lexical_cast<int>(range[0]);
222  int high = low;
223  ++x;
224  if (x != range.end())
225  high = boost::lexical_cast<int>(range[1]);
226  for (int i = low; i <= high; ++i) {
227  if (i < 1) {
228  const string err = "CORIKSA (multi) core counting start at 1.";
229  ERROR(err);
230  throw CorsikaIOException(err);
231  }
232  fMultiCoreSelection.push_back(i);
233  info << comma << i;
234  comma = ", ";
235  }
236  }
237  }
238  INFO(info);
239  }
240  }
241  Open(fileName, mode, branch);
242  }
243 
244 
245  void
246  CorsikaShowerFile::Open(const string& fileName,
247  const Mode mode,
248  utl::Branch* const branch)
249  {
250  if (fRawFile.IsOpen())
251  Close();
252  for (auto& cherFile : fCherenkovFile)
253  if (cherFile.second.IsOpen())
254  cherFile.second.Close();
255 
256  if (mode != eRead) {
257  const string msg = "Cannot open file '" + fileName + "' - Corsika files are read-only";
258  ERROR(msg);
259  throw CorsikaIOException(msg);
260  }
261 
262  fOpenedAnything = false;
263  bool foundShowerDefinition = false;
264 
265  if (!branch) {
266 
267  ostringstream warn;
268  warn << "No Branch was passed for configuration. Try to open file(s) based on " << fileName << ".";
269  WARNING(warn);
270 
271  // base name should be as close as possible to "DATxxxxxx" as possible
272  string base = fileName.substr(0, fileName.rfind(".")); // check for any extension and remove
273  boost::replace_last(base, "RUN", "DAT");
274  boost::replace_last(base, "SIM", "DAT");
275  // check if fileName is a ground particle file, or w/ extension
276  if (ScanGroundFile(base, fRawFile, fPositionToRaw, fPositionOfEventTrailer, !foundShowerDefinition, true) == eSuccess) {
277  foundShowerDefinition = true;
278  fOpenedAnything = true;
279  INFO(string("Found: ") + base);
280  } else if (ScanGroundFile(base + ".part", fRawFile, fPositionToRaw, fPositionOfEventTrailer, !foundShowerDefinition, true) == eSuccess) {
281  foundShowerDefinition = true;
282  fOpenedAnything = true;
283  INFO(string("Found: ") + base + ".part");
284  } else if (ScanGroundFile(base + ".grdptcls", fRawFile, fPositionToRaw, fPositionOfEventTrailer, !foundShowerDefinition, true) == eSuccess) {
285  foundShowerDefinition = true;
286  fOpenedAnything = true;
287  INFO(string("Found: ") + base + ".grdptcls");
288  } else if (ScanGroundFile(fileName, fRawFile, fPositionToRaw, fPositionOfEventTrailer, !foundShowerDefinition, true) == eSuccess) {
289  foundShowerDefinition = true;
290  fOpenedAnything = true;
291  INFO(string("Found: ") + fileName);
292  } /*else if (boost::filesystem::exists(fileName)) {
293  fRawFile.Open(fileName);
294  ScanGroundFile(fRawFile, fPositionToRaw, fPositionOfEventTrailer, !foundShowerDefinition);
295  foundShowerDefinition = true;
296  fOpenedAnything = true;
297  }*/
298 
299  // check if there is a long file
300  if (ScanLongFile(base + ".long") == eSuccess) {
301  fLongFile = base + ".long";
302  fReadLongFile = true;
303  fOpenedAnything = true;
304  INFO(string("Found profile: ") + base + ".long");
305  }
306 
307  // check if ther is an INP steering file
308  if (ScanSteerFile(base + ".inp") == eSuccess) {
309  if (!fNumberOfEvents)
310  fNumberOfEvents = 1;
311  fSteerFile = base + ".inp";
312  fReadSteerFile = true;
313  fOpenedAnything = true;
314  foundShowerDefinition = true; // at least maybe...
315  INFO(string("Found steerfile: ") + base + ".inp");
316  } else {
317  string testBase = base;
318  boost::replace_last(testBase, "DAT", "SIM");
319  if (ScanSteerFile(testBase + ".inp") == eSuccess) {
320  if (!fNumberOfEvents)
321  fNumberOfEvents = 1;
322  fSteerFile = testBase + ".inp";
323  fReadSteerFile = true;
324  fOpenedAnything = true;
325  foundShowerDefinition = true; // at least maybe...
326  INFO(string("Found steerfile: ") + testBase + ".inp");
327  } else {
328  string testBase = base;
329  boost::replace_last(testBase, "DAT", "RUN");
330  if (ScanSteerFile(testBase + ".inp") == eSuccess) {
331  if (!fNumberOfEvents)
332  fNumberOfEvents = 1;
333  fSteerFile = testBase + ".inp";
334  fReadSteerFile = true;
335  fOpenedAnything = true;
336  foundShowerDefinition = true; // at least maybe...
337  INFO(string("Found steerfile: ") + testBase + ".inp");
338  }
339  }
340  }
341  // check if there is an LST output file
342  if (boost::filesystem::exists(base + ".lst")) {
343  // not implemented yet
344  /*fListFile = base + ".lst";
345  fReadListFile = true;
346  fOpenedAnything = true;
347  foundShowerDefinition = true;
348  */
349  }
350 
351  } else {
352 
353  utl::Branch mapping = branch->GetChild("InputMapping");
354  utl::Branch bRegex = branch->GetChild("PatternRegex");
355  utl::Branch bPath = branch->GetChild("PatternPath");
356 
357  boost::regex re;
358  if (bRegex) {
359 
360  try {
361  re = boost::regex(bRegex.Get<string>());
362  } catch (const boost::regex_error& e) {
363  const string err = "Please make sure to specify valid regex expression in PatternRegex!";
364  ERROR(err);
365  throw CorsikaIOException(err);
366  }
367 
368  } else {
369 
370  if (!bPath) {
371  const string err = "InputMapping requires one of pattern_regex or pattern_path attribute!";
372  ERROR(err);
373  throw CorsikaIOException(err);
374  }
375 
376  auto pattern = bPath.Get<string>();
377  boost::replace_all(pattern, "\\", "\\\\");
378  boost::replace_all(pattern, "^", "\\^");
379  boost::replace_all(pattern, ".", "\\.");
380  boost::replace_all(pattern, "$", "\\$");
381  boost::replace_all(pattern, "|", "\\|");
382  boost::replace_all(pattern, "(", "\\(");
383  boost::replace_all(pattern, ")", "\\)");
384  boost::replace_all(pattern, "[", "\\[");
385  boost::replace_all(pattern, "]", "\\]");
386  boost::replace_all(pattern, "*", "\\*");
387  boost::replace_all(pattern, "+", "\\+");
388  boost::replace_all(pattern, "?", "\\?");
389  boost::replace_all(pattern, "/", "\\/");
390  // I hope this is enough .... please extend if needed
391  boost::replace_all(pattern, "\\?", "."); // -> group !!
392  boost::replace_all(pattern, "\\*", "(.*)"); // -> group
393  re = boost::regex(pattern);
394  }
395 
396  vector<string> patterns;
397  try {
398  boost::smatch what;
399  if (boost::regex_match(fileName, what, re)) {
400  for (int i = 1, n = what.size(); i < n; ++i)
401  patterns.push_back(what[i]);
402  } else {
403  const string err = "Please make sure you specify valid regex/path expression! No match was found.";
404  ERROR(err);
405  throw CorsikaIOException(err);
406  }
407  } catch (const boost::regex_error& e) {
408  const string err = "Please make sure you specify valid regex/path expression!";
409  ERROR(err);
410  throw CorsikaIOException(err);
411  }
412 
413  for (mapping = mapping.GetFirstChild(); mapping; mapping = mapping.GetNextSibling()) {
414 
415  // determine pattern(s) data
416  const string name = mapping.GetName();
417  string path = mapping.Get<string>();
418 
419  for (unsigned int i = 0; i < patterns.size(); ++i) {
420  ostringstream marker;
421  marker << '(' << i+1 << ')';
422  boost::replace_all(path, marker.str(), patterns[i]);
423  }
424 
425  {
426  ostringstream inf;
427  inf << "Read " << name << " from file " << path;
428  INFO(inf);
429  }
430 
431  if (name == "ProfileData") {
432 
433  fReadLongFile = true;
434  fLongFile = path;
435  fOpenedAnything = true;
436 
437  } else if (name == "GroundData") {
438 
439  ScanGroundFile(path, fRawFile, fPositionToRaw, fPositionOfEventTrailer, !foundShowerDefinition);
440  foundShowerDefinition = true;
441  fOpenedAnything = true;
442 
443  } else if (name == "ShowerINFO") { // .lst files
444 
445  foundShowerDefinition = true;
446  fOpenedAnything = true;
447 
448  ifstream info(path);
449  string line;
450  while (getline(info, line)) {
451  if (line.empty())
452  continue;
453  if (line.find("energy_prim") == 0)
454  fShowerEnergy.push_back(stod(line.substr(13, line.length() - 13)));
455  if (line.find("theta_prim") == 0)
456  fShowerTheta.push_back(stod(line.substr(12, line.length() - 12)));
457  if (line.find("phi_prim") == 0)
458  fShowerPhi.push_back(stod(line.substr(11, line.length() - 11)));
459  }
460 
461  const string err = "this is not yet completely implemented";
462  ERROR(err);
463  throw CorsikaIOException(err);
464 
465  } else if (name == "ShowerDBASE") {
466 
467  foundShowerDefinition = true;
468  fOpenedAnything = true;
469 
470  ifstream dbase(path);
471  string line;
472  while (getline(dbase, line)) {
473  if (line.empty())
474  continue;
475  if (line.find("#energy_prim") == 0) {
476  vector<string> strs;
477  boost::split(strs, line, boost::is_any_of("#"));
478  fShowerEnergy.push_back(stod(strs[1]));
479  fShowerTheta.push_back(stod(strs[3]));
480  fShowerPhi.push_back(stod(strs[5]));
481  }
482  }
483  dbase.close();
484 
485  const string err = "this is not yet completely implemented";
486  ERROR(err);
487  throw CorsikaIOException(err);
488 
489  } else if (name == "TelescopeData") {
490 
491  fOpenedAnything = true;
492 
493  const AttributeMap am = mapping.GetAttributes();
494  if (fCherenkovFile.count(am)) {
495  ostringstream err;
496  err << "Already defined TelescopeData entry for attributes: ";
497  if (am.empty()) {
498  err << "[none]";
499  } else {
500  string comma = "";
501  for (const auto& iatt : am) {
502  err << comma << '"' << iatt.first << "\"=\"" << iatt.second << '"';
503  comma = ", ";
504  }
505  }
506  ERROR(err);
507  throw CorsikaIOException(err.str());
508  }
509  ScanGroundFile(path, fCherenkovFile[am], fPositionToCherenkovRaw[am], fPositionOfCherenkovEventTrailer[am], !foundShowerDefinition);
510  foundShowerDefinition = true;
511 
512  } else {
513  ERROR("unknown input mapping");
514  throw CorsikaIOException("unknown input mapping" );
515  }
516  }
517  }
518 
519  if (!fOpenedAnything) {
520  const string err = "Did not open anything! Stop here.";
521  WARNING(err);
522  throw CorsikaIOException(err);
523  }
524 
525  if (!foundShowerDefinition) {
526  const string err = "No suited input file with primary shower data found. Cannot produce full MC evnets.";
527  WARNING(err);
528  }
529  GotoPosition(0);
530  }
531 
532 
533  // Helper function to set runheader data for both thinned/unthinned
534  void
536  {
537  if (first) {
538  fRunNumber = int(header.fRunNumber);
539  // Prior to this version, the h parameters were not saved.
540  if (header.fVersion >= 6.9) {
541  for (int i = 0; i < 5; ++i)
542  fAtmPars.fHLAY[i] = header.fConstHLAY[i] * cm;
543  if (fAtmPars.fHLAY[0] < 0)
544  fAtmPars.fHLAY[0] = 0;
545  } else {
546  ostringstream msg;
547  msg << "The current CORSIKA file was created using version " << header.fVersion << ". "
548  "The atmospheric layers are not defined! Using the values from US std. atmosphere.";
549  WARNING(msg);
550  fAtmPars.fHLAY[0] = 0*km;
551  fAtmPars.fHLAY[1] = 4*km;
552  fAtmPars.fHLAY[2] = 10*km;
553  fAtmPars.fHLAY[3] = 40*km;
554  fAtmPars.fHLAY[4] = 100*km;
555  }
556  for (int i = 0; i < 5; ++i) {
557  fAtmPars.fAATM[i] = header.fConstAATM[i] * g/cm2;
558  fAtmPars.fBATM[i] = header.fConstBATM[i] * g/cm2;
559  fAtmPars.fCATM[i] = header.fConstCATM[i] * cm;
560  fAtmPars.fZLAY[i] = GetDepthVsHeight(fAtmPars.fHLAY[i]);
561  }
562  INFO("Loaded atmosphere parameters:");
563  TabularStream tab(" l | l | l | l ");
564  tab << "h[km]" << endc << "a[g/cm2]" << endc << "b[g/cm2]" << endc << "c[km]" << endr
565  << hline;
566  for (int i = 0; i < 5; ++i) {
567  if (i == 4)
568  tab << hline;
569  tab << fAtmPars.fHLAY[i] / km << endc
570  << fAtmPars.fAATM[i] / (g/cm2) << endc
571  << fAtmPars.fBATM[i] / (g/cm2) << endc
572  << fAtmPars.fCATM[i] / km << endr;
573  }
574  tab << delr;
575  cerr << tab << endl;
576  } else {
577  // if NOT first -> consistency check
578  if (fRunNumber != int(header.fRunNumber)) {
579  const string msg = "Inconsistent RunNumber found";
580  ERROR(msg);
581  throw CorsikaIOException(msg);
582  }
583  // Prior to this version, the h parameters were not saved.
584  if (header.fVersion >= 6.9) {
585  for (int i = 0; i < 5; ++i) {
586  if (fAtmPars.fHLAY[i] != header.fConstHLAY[i] * cm) {
587  const string msg = "Inconsistent Atmosphere HLAY found";
588  ERROR(msg);
589  throw CorsikaIOException(msg);
590  }
591  }
592  }
593  for (int i = 0; i < 5; ++i) {
594  if (fAtmPars.fAATM[i] != header.fConstAATM[i] * g/cm2) {
595  const string msg = "Inconsistent Atmosphere AATM found";
596  ERROR(msg);
597  throw CorsikaIOException(msg);
598  }
599  if (fAtmPars.fBATM[i] != header.fConstBATM[i] * g/cm2) {
600  const string msg = "Inconsistent Atmosphere BATM found";
601  ERROR(msg);
602  throw CorsikaIOException(msg);
603  }
604  if (fAtmPars.fCATM[i] != header.fConstCATM[i] * cm) {
605  const string msg = "Inconsistent Atmosphere CATM found";
606  ERROR(msg);
607  throw CorsikaIOException(msg);
608  }
609  /*if (fAtmPars.fZLAY[i] != GetDepthVsHeight(fAtmPars.fHLAY[i])) {
610  const string msg = "Inconsistent Atmosphere found";
611  ERROR(msg);
612  throw CorsikaIOException(msg);
613  }*/
614  }
615  }
616  }
617 
618 
619  // Helper function to set runheader data for both thinned/unthinned
620  void
621  CorsikaShowerFile::SetEventHeader(const bool first, const unsigned int eventsSoFar, const io::Corsika::EventHeader& eh)
622  {
623  if (first) { // first read and record
624  fCoordinateRotation = eh.fArrayRotation * radian;
625  fIdToPosition[int(eh.fEventNumber)] = eventsSoFar;
627  fIsSlant = chf.IsSLANT();
628  if (chf.IsCHERENKOV()) {
629  if (chf.IsCEFFIC()) { // || chf.IsCURVED() ||
630  //blockUnth.AsEventHeader().fCerenkovOutputFlag only support option 1 !!
631  ostringstream msg;
632  msg << "NOTE: Offline does currently not support to read Cherenkov photons from ground particle files with optinos: "
633  "CEFFIC = " << chf.IsCEFFIC() << ", "
634  "CURVED = " << chf.IsCURVED() << ", "
635  "OUTPUT = " << eh.fCerenkovOutputFlag;
636  ERROR(msg);
637  throw CorsikaIOException(msg.str());
638  } else {
639  fCherenkovBandwidthMin = eh.fCerenkovBandwidthMin * nanometer;
640  fCherenkovBandwidthMax = eh.fCerenkovBandwidthMax * nanometer;
641  ostringstream inf;
642  inf << "Cherenkov option is available: "
643  "CHERENKOV = " << chf.IsCHERENKOV() << ", "
644  "CEFFIC = " << chf.IsCEFFIC() << ", "
645  "CURVED = " << chf.IsCURVED() << ", "
646  "Ch-Output = " << eh.fCerenkovOutputFlag << ", "
647  "lambda = [" << fCherenkovBandwidthMin / nanometer << ", "
648  << fCherenkovBandwidthMax / nanometer << "]";
649  INFO(inf);
650  }
651  }
652  } else {
653  // if NOT first -> check consistency
654  if (fCoordinateRotation != eh.fArrayRotation*radian) {
655  const string msg = "Inconsistent AARANG found";
656  ERROR(msg);
657  throw CorsikaIOException(msg);
658  }
659  if (fIdToPosition[int(eh.fEventNumber)] != eventsSoFar) {
660  const string msg = "Inconsistent EventID found";
661  ERROR(msg);
662  throw CorsikaIOException(msg);
663  }
665  if (fIsSlant != chf.IsSLANT()) {
666  const string msg = "Inconsistent SLANT";
667  ERROR(msg);
668  throw CorsikaIOException(msg);
669  }
670  if (chf.IsCHERENKOV()) {
671  if (chf.IsCEFFIC()) { // || chf.IsCURVED() ||
672  ;
673  } else {
674  if (fCherenkovBandwidthMin != eh.fCerenkovBandwidthMin * nanometer) {
675  const string msg = "Inconsistent CherWLmin found";
676  ERROR(msg);
677  throw CorsikaIOException(msg);
678  }
679  if (fCherenkovBandwidthMax != eh.fCerenkovBandwidthMax * nanometer) {
680  const string msg = "Inconsistent CherWLmax found";
681  ERROR(msg);
682  throw CorsikaIOException(msg);
683  }
684  }
685  }
686  }
687  };
688 
689 
690  Status
691  CorsikaShowerFile::ScanGroundFile(const std::string& name,
693  PositionVector& positionToRaw,
694  PositionVector& positionOfEventTrailer,
695  const bool first,
696  const bool noException)
697  {
698  if (!boost::filesystem::exists(name))
699  return eFail;
700 
701  if (!raw.Open(name, noException))
702  return eFail;
703 
704  raw.SeekTo(0, true); // reset in order to start fresh for sure
705  unsigned int eventsSoFar = 0;
706 
707  unsigned int blockIndex = 0;
708  bool foundEventHeader = false;
709  bool foundRunHeader = false;
710  fIsSlant = true;
711  fIsThinned = true;
712 
713  Corsika::BlockUnthinned blockUnth;
714  while (raw.GetNextBlockUnthinned(blockUnth) && !blockUnth.IsRunTrailer()) {
715  ++blockIndex;
716  if (blockIndex == 1 && !blockUnth.IsRunHeader()) // check if this is not un-thinned
717  break;
718  if (blockIndex == 2 && !blockUnth.IsEventHeader()) // check if this is not un-thinned
719  break;
720  const Corsika::RawFile::PositionType rawPosition = raw.GetNextPosition();
721  if (blockUnth.IsEventHeader()) {
722  fIsThinned = false;
723  foundEventHeader = true;
724  positionToRaw.push_back(rawPosition - 1);
725  const Corsika::EventHeader& eh = blockUnth.AsEventHeader();
726  SetEventHeader(first, eventsSoFar, eh);
727  ++eventsSoFar;
728  } else {
729  if (blockUnth.IsEventTrailer())
730  positionOfEventTrailer.push_back(rawPosition - 1);
731  else if (blockUnth.IsRunHeader()) {
732  const Corsika::RunHeader& RUNH = blockUnth.AsRunHeader();
733  SetRunHeader(first, RUNH);
734  foundRunHeader = true;
735  } // end runheader
736  }
737  if (blockIndex > 400 && !foundRunHeader) {
738  const string msg = "Error scanning Corsika ground file: could not find run header";
739  ERROR(msg);
740  throw CorsikaIOException(msg);
741  }
742  if (blockIndex > 400 && !foundEventHeader)
743  break;
744  }
745 
746  if (!foundEventHeader) { // now try thinned reading
747 
748  eventsSoFar = 0;
749  blockIndex = 0;
750  raw.SeekTo(0, true); // also "reset" in order to cleanly go from unthinned to thinned mode
751 
752  Corsika::Block blockTh;
753  while (raw.GetNextBlock(blockTh) && !blockTh.IsRunTrailer()) {
754  ++blockIndex;
755  if (blockIndex == 1 && !blockTh.IsRunHeader()) // this is not un-thinned
756  break;
757  if (blockIndex == 2 && !blockTh.IsEventHeader()) // this is not un-thinned
758  break;
759  const Corsika::RawFile::PositionType rawPosition = raw.GetNextPosition();
760  if (blockTh.IsEventHeader()) {
761  foundEventHeader = true;
762  fIsThinned = true;
763  positionToRaw.push_back(rawPosition - 1);
764  const Corsika::EventHeader& eh = blockTh.AsEventHeader();
765  SetEventHeader(first, eventsSoFar, eh);
766  ++eventsSoFar;
767  } else if (blockTh.IsEventTrailer())
768  positionOfEventTrailer.push_back(rawPosition - 1);
769  else if (blockTh.IsRunHeader()) {
770  const Corsika::RunHeader& rh = blockTh.AsRunHeader();
771  if (!foundRunHeader)
772  SetRunHeader(first, rh);
773  foundRunHeader = true;
774  }
775  if (blockIndex > 400) {
776  if (!foundRunHeader) {
777  const string err = "Error scanning thinned Corsika ground file: "
778  "could not find run header";
779  ERROR(err);
780  throw CorsikaIOException(err);
781  }
782  if (!foundEventHeader) {
783  const string err = "Error scanning Corsika ground file: "
784  "could not find Event header";
785  ERROR(err);
786  throw CorsikaIOException(err);
787  }
788  }
789  }
790 
791  if (!blockTh.IsRunTrailer()) {
792  const string err = "Error scanning Corsika ground file: could not find run end";
793  ERROR(err);
794  throw CorsikaIOException(err);
795  }
796  }
797 
798  if (positionToRaw.size() != positionOfEventTrailer.size()) {
799  const string err = "Found different number of event-headers and -trailers";
800  ERROR(err);
801  throw CorsikaIOException(err);
802  }
803 
804  if (!first) {
805  // consistency checking mode: .cher files vs. DAT file
806  if (eventsSoFar != fPositionToRaw.size()) {
807  ostringstream err;
808  err << "Found different number of events in Cherenkov file (" << eventsSoFar << ") "
809  "compared to ground particle file (" << fPositionToRaw.size() << ")";
810  ERROR(err);
811  throw CorsikaIOException(err.str());
812  }
813  }
814 
815  fNumberOfEvents = positionToRaw.size();
816 
817  return eSuccess;
818  }
819 
820 
821  void
823  {
824  if (fRawFile.IsOpen())
825  fRawFile.Close();
826 
827  fPositionToRaw.clear();
828  fIdToPosition.clear();
829 
830  delete fParticleIterator;
831  fParticleIterator = nullptr;
832  }
833 
834 
835  void
837  GaisserHillas6Parameter& gh, bool& haveGH, double& zenith,
838  const Corsika::EventHeader& header, const Corsika::EventTrailer& trailer)
839  {
840  // if fUseRealisticMagneticDeclination is true the realistic declination is optained by a model
841  // inside CorsikaShowerFileGeometryProducer
842  const double magneticNorthDeclination = fUseRealisticMagneticDeclination ? 0 : fMagneticDeclination;
843  const auto refCoordinateSystem = fwk::CoordinateSystemRegistry::Get(fRefCoordinateSystemName);
844  const bool multiCore = header.fUsesOfCerenkovEvent > 0;
845  event.MakeSimShower(
847  header.fTheta*radian, header.fPhi*radian + fCoordinateRotation,
848  header.fObservationHeight[fObservationLevel-1]*cm,
849  multiCore,
850  refCoordinateSystem,
851  fCoordinateRotation + magneticNorthDeclination,
852  fUseRealisticMagneticDeclination
853  )
854  );
855  auto& shower = event.GetSimShower();
856 
857  zenith = header.fTheta * radian;
858  const double cosZenith = cos(zenith);
859  const double xmax = trailer.fLongitudinalPar[2] * g/cm2;
860  const double x0 = trailer.fLongitudinalPar[1] * g/cm2;
861  const double nmax = trailer.fLongitudinalPar[0];
862  const double rMin = header.fRMaxThinning * cm; // max radius for radial thinning
863 
864  const bool hasValidGHfit = (trailer.fChi2 > 0 && trailer.fChi2 < 1e13) && (nmax || xmax || x0);
865 
866  shower.SetPrimaryParticle(Corsika::CorsikaToPDG(int(header.fParticleId)));
867  shower.SetEnergy(header.fEnergy * GeV);
868  shower.SetMuonNumber(trailer.fMuons);
869  shower.SetGroundParticleCoordinateSystemZenith(zenith);
870  // phi always relative to magnetic north in CORSIKA. This is the definition we also use (JUST) here!
871  shower.SetGroundParticleCoordinateSystemAzimuth(
872  header.fPhi*radian
873  + 180*degree // incoming vs. outgoing
874  - fCoordinateRotation // eventual array rotation
875  );
876  shower.SetMinRadiusCut(rMin);
877 
878  shower.SetShowerNumber(int(header.fEventNumber));
879  shower.SetShowerRunId(boost::lexical_cast<string>(header.fRunNumber));
880 
881  shower.SetEnergyCutoff(header.fCutoffElectrons * GeV, ShowerSimData::eElectron);
882  shower.SetEnergyCutoff(header.fCutoffMuons * GeV, ShowerSimData::eMuon);
883 
884  const double bx = header.fBx * micro*tesla;
885  const double bz = header.fBz * micro*tesla;
886  shower.SetMagneticFieldInclination(atan2(bz, bx));
887  shower.SetMagneticFieldStrength(sqrt(bx * bx + bz * bz));
888 
889  shower.SetMinCherenkovWavelength(header.fCerenkovBandwidthMin*nanometer);
890  shower.SetMaxCherenkovWavelength(header.fCerenkovBandwidthMax*nanometer);
891 
892  // Corsika starts at the top of the atmosphere, not
893  const double heightObsLevel = header.fObservationHeight[fObservationLevel - 1] * cm;
894  const double heightFirstInt = fabs(header.fZFirst) * cm;
895 
896  const double hAtmBoundary = 112.8292*km;
897  // for the SLANT and CURVED options, clock starts at the margin of
898  // the atmosphere. This is indicated by fZFirst < 0
899  double hReference = (header.fZFirst < 0) ? hAtmBoundary : heightFirstInt;
900 
901  // calculating slant depth of first interaction point
902  if (cosZenith > 0) {
903  shower.SetXFirst(header.fFlagCurved ? GetSlantDepthCurved(heightObsLevel, zenith, heightFirstInt) :
904  GetDepthVsHeight(heightFirstInt) / fabs(cosZenith));
905  } else if (cosZenith < 0) {
906  // for up-going showers atmosphere starts at the bottom and ends at the top.amount of
907  // atmosphere at 1.4 km = 875.539 g/cm2 -> set to 875.6
908  shower.SetXFirst(header.fFlagCurved ? GetSlantDepthCurved(heightObsLevel, zenith, heightFirstInt) :
909  (875.6*(g/cm2) - GetDepthVsHeight(heightFirstInt)) / fabs(cosZenith));
910  }
911  {
912  ostringstream info;
914  info << "Primary = " << pid.get()->GetName() << ", "
915  "Energy = " << header.fEnergy* GeV / eV << " eV, "
916  "Height of first interaction = " << heightFirstInt / km << " km, "
917  "X_1 = " << shower.GetXFirst() / (g/cm2) << " g/cm2, "
918  "Zenith = " << zenith / deg << " deg, "
919  "Azimuth = " << (header.fPhi + 180*deg)/ deg << " deg, "
920  "cutoff_e = " << header.fCutoffElectrons << " GeV, "
921  "cutoff_mu = " << header.fCutoffMuons << " GeV, "
922  "r_Min^thin = " << rMin / cm << " cm";
923  INFO(info);
925  }
926 
927  if (fCoordinateRotation) {
928  ostringstream info;
929  info << "CORSIKA output is rotated by " << fCoordinateRotation / deg << " deg with respect to CORSIKA USER GUIDE";
930  INFO(info);
931  }
932 
933  if (header.fCurvedObsLevel) {
934  ostringstream info;
935  info << "CORSIKA output is on curved surface" << endl;
936  }
937 
938  for (int iCore = 0; iCore < header.fUsesOfCerenkovEvent; ++iCore) {
939  ostringstream info;
940  info << "Found simulated multi-core #" << iCore+1 << " "
941  "x=" << header.fCoreX[iCore] / 100. << " m, "
942  "y=" << header.fCoreY[iCore] / 100. << " m, "
943  "height=" << heightObsLevel / m << " m ";
944  if (fMultiCoreSelection.empty() || // in this case all cores are selected
945  find(fMultiCoreSelection.begin(), fMultiCoreSelection.end(), iCore+1) != fMultiCoreSelection.end()) {
946  info << " [selected]";
947  const UTMPoint utm(Point(0,0,0, refCoordinateSystem), ReferenceEllipsoid::eWGS84);
948  const double origin_height = utm.GetHeight();
949  const Point mcCore(header.fCoreX[iCore]*cm, header.fCoreY[iCore]*cm, heightObsLevel-origin_height, refCoordinateSystem);
950  shower.AddSimCore(mcCore);
951  } else {
952  info << " [ignore]";
953  }
954  INFO(info);
955  }
956 
957  ostringstream info;
958  if (header.fFlagCurved) {
959 
960  info << "CURVED version: ";
961 
962  // value taken from CORSIKA
963  const double kREarth = 6.371315e6*m; // radius of the earth
964 
965  timeShift = (Sqr((kREarth + heightObsLevel) * cosZenith) +
966  Sqr(hReference - heightObsLevel) +
967  2 * (kREarth + heightObsLevel) * (hReference - heightObsLevel));
968  timeShift = sqrt(timeShift);
969  timeShift -= (kREarth + heightObsLevel) * cosZenith;
970  timeShift /= kSpeedOfLight;
971 
972  } else {
973  timeShift = (hReference - heightObsLevel) / (cosZenith * kSpeedOfLight);
974  }
975  info << "TimeShift to core: " << timeShift / ns << " ns";
976  INFO(info);
977 
978  if (fObservationLevel > header.fObservationLevels) {
979  ostringstream info;
980  info << "The requested observation level: " << fObservationLevel
981  << " does not exist (max obs. level: "
982  << header.fObservationLevels << "), "
983  "switching to level 1.";
984  fObservationLevel = 1;
985  INFO(info);
986  }
987 
988  if (fIsThinned && header.fWMaxEM / header.fWMaxHadronic < 100) {
989  ostringstream msg;
990  msg << "The ratio of EM to hadron maximum weight is " << header.fWMaxEM / header.fWMaxHadronic << ". "
991  "This is smaller than required to separate the EM contribution from local hadrons and "
992  "the rest of the cascade.";
993  WARNING(msg);
994  }
995 
996  if (header.fVersion >= 6.9 && !shower.HasAtmosphereParameters()) {
997  shower.MakeAtmosphereParameters(
998  AtmosphereParameters(fAtmPars.fHLAY, fAtmPars.fAATM, fAtmPars.fBATM, fAtmPars.fCATM)
999  );
1000  }
1001 
1002  if (!haveGH) {
1003  haveGH = true;
1004  const double a = trailer.fLongitudinalPar[3] * g/cm2;
1005  const double b = trailer.fLongitudinalPar[4];
1006  const double c = trailer.fLongitudinalPar[5] / (g/cm2);
1007  if (hasValidGHfit) {
1008  if (fIsSlant) {
1009  gh.SetXMax(xmax, 0);
1010  gh.SetNMax(nmax, 0);
1011  gh.SetXZero(x0, 0);
1012  } else {
1013  gh.SetXMax(xmax / fabs(cosZenith), 0); //cosZenith<0 for Zenith>90
1014  gh.SetNMax(nmax, 0);
1015  gh.SetXZero(x0 / cosZenith, 0);
1016  }
1017  gh.SetA(a, 0);
1018  gh.SetB(b, 0);
1019  gh.SetC(c, 0);
1020  gh.SetChiSquare(trailer.fChi2, 1);
1021  } else {
1022  // FS: Changed ERROR to WARNING, the GH Fit is read out from the .long file in case invalid values are detected
1023  // For parallel corsika simulations the GH Fit will be always invalid in the DAT* file
1024  ostringstream warn;
1025  warn << "CORISKA shower with invalid GH fit: Xmax=" << xmax / cosZenith / (g/cm2) << " g/cm2, "
1026  "Nmax=" << nmax << ", X0=" << x0 / cosZenith / (g/cm2) << " g/cm2, "
1027  "chi2/ndf=" << trailer.fChi2;
1028  WARNING(warn);
1029  }
1030  }
1031  }
1032 
1033 
1034  Status
1036  {
1037  if (event.HasSimShower()) {
1038  ERROR("Event not cleared - has SimShower. Cannot read CORSIKA.");
1039  return eFail;
1040  }
1041 
1042  bool haveGH = false;
1044  bool setSimShowerAlready = false;
1045  double timeShift = 0;
1046  double zenith = 0;
1047 
1048  if (fRawFile.IsOpen()) { // if DAT file open
1049 
1050  if (fCurrentPosition >= fPositionToRaw.size()) {
1051  INFO("End of file");
1052  return eEOF;
1053  }
1054  fRawFile.SeekTo(fPositionToRaw[fCurrentPosition]);
1055 
1056  Corsika::EventHeader header;
1057 
1058  if (fIsThinned) {
1059  Corsika::Block headerBlock;
1060  if (!fRawFile.GetNextBlock(headerBlock)) {
1061  ostringstream err;
1062  err << "Cannot read CORSIKA shower header for position "
1063  << fCurrentPosition;
1064  FATAL(err);
1065  return eFail;
1066  }
1067  if (!headerBlock.IsEventHeader()) {
1068  ostringstream err;
1069  err << "First block at position " << fCurrentPosition
1070  << " is not event header";
1071  FATAL(err);
1072  return eFail;
1073  }
1074  header = headerBlock.AsEventHeader();
1075 
1076  fRawFile.SeekTo(fPositionOfEventTrailer[fCurrentPosition]);
1077  Corsika::Block trailerBlock;
1078  if (!fRawFile.GetNextBlock(trailerBlock)) {
1079  ostringstream err;
1080  err << "Cannot read CORSIKA shower trailer for position "
1081  << fCurrentPosition;
1082  FATAL(err);
1083  return eFail;
1084  }
1085  if (!trailerBlock.IsEventTrailer()) {
1086  ostringstream err;
1087  err << "Block at position " << fCurrentPosition
1088  << " is not event trailer";
1089  FATAL(err);
1090  return eFail;
1091  }
1092  const Corsika::EventTrailer& trailer = trailerBlock.AsEventTrailer();
1093 
1094  if (!setSimShowerAlready) {
1095  SetHeaderTrailer(event, timeShift, gh, haveGH, zenith, header, trailer);
1096  setSimShowerAlready = true;
1097  }
1098 
1099  } else { // switch to UN-THINNED version of DAT files
1100 
1101  Corsika::BlockUnthinned headerBlock;
1102  if (!fRawFile.GetNextBlockUnthinned(headerBlock)) {
1103  ostringstream msg;
1104  msg << "Cannot read CORSIKA shower header for position "
1105  << fCurrentPosition;
1106  FATAL(msg);
1107  return eFail;
1108  }
1109  if (!headerBlock.IsEventHeader()) {
1110  ostringstream msg;
1111  msg << "First block at position " << fCurrentPosition
1112  << " is not event header";
1113  FATAL(msg);
1114  return eFail;
1115  }
1116  header = headerBlock.AsEventHeader();
1117 
1118  fRawFile.SeekTo(fPositionOfEventTrailer[fCurrentPosition]);
1119  Corsika::BlockUnthinned trailerBlock;
1120  if (!fRawFile.GetNextBlockUnthinned(trailerBlock)) {
1121  ostringstream msg;
1122  msg << "Cannot read CORSIKA shower trailer for position "
1123  << fCurrentPosition;
1124  FATAL(msg);
1125  return eFail;
1126  }
1127  if (!trailerBlock.IsEventTrailer()) {
1128  ostringstream msg;
1129  msg << "BlockUnthinned at position " << fCurrentPosition
1130  << " is not event trailer";
1131  FATAL(msg);
1132  return eFail;
1133  }
1134  const Corsika::EventTrailer& trailer = trailerBlock.AsEventTrailer();
1135 
1136  if (!setSimShowerAlready) {
1137  SetHeaderTrailer(event, timeShift, gh, haveGH, zenith, header, trailer);
1138  setSimShowerAlready = true;
1139  }
1140 
1141  } // end thinned/unthinned block
1142 
1143  delete fParticleIterator;
1144  fParticleIterator =
1145  new CorsikaShowerFileParticleIterator(fRawFile,
1146  header,
1147  fAtmPars,
1148  timeShift,
1149  fPositionToRaw[fCurrentPosition] + 1,
1150  fObservationLevel,
1151  fIsThinned,
1152  header.fWMaxEM);
1153 
1154  ShowerSimData& shower = event.GetSimShower();
1155  if (!shower.HasGroundParticles())
1156  shower.MakeGroundParticles();
1157  shower.GetGroundParticles().SetFileInterface(fParticleIterator);
1158 
1159  } // DAT file reading is finished
1160 
1161  for (auto& it : fCherenkovFile) {
1162 
1163  const AttributeMap& am = it.first;
1164  Corsika::RawFile& file = it.second;
1165 
1166  if (!file.IsOpen()) {
1167  FATAL("Try to read data from file that is not open.");
1168  return eFail;
1169  }
1170 
1171  if (fCurrentPosition >= fPositionToCherenkovRaw[am].size()) {
1172  INFO("End of file (Cherenkov)");
1173  return eEOF;
1174  }
1175  file.SeekTo(fPositionToCherenkovRaw[am][fCurrentPosition]);
1176 
1177  Corsika::EventHeader header;
1178 
1179  if (fIsThinned) { // thinning version
1180  Corsika::Block headerBlock;
1181  if (!file.GetNextBlock(headerBlock)) {
1182  ostringstream err;
1183  err << "Cannot read Cherenkov CORSIKA shower header for position "
1184  << fCurrentPosition;
1185  FATAL(err);
1186  return eFail;
1187  }
1188  if (!headerBlock.IsEventHeader()) {
1189  ostringstream err;
1190  err << "First block at position " << fCurrentPosition
1191  << " is not cherenkov event header";
1192  FATAL(err);
1193  return eFail;
1194  }
1195  header = headerBlock.AsEventHeader();
1196 
1197  file.SeekTo(fPositionOfCherenkovEventTrailer[am][fCurrentPosition]);
1198  Corsika::Block trailerBlock;
1199  if (!file.GetNextBlock(trailerBlock)) {
1200  ostringstream err;
1201  err << "Cannot read CORSIKA shower cherenkov trailer for position "
1202  << fCurrentPosition;
1203  FATAL(err);
1204  return eFail;
1205  }
1206  if (!trailerBlock.IsEventTrailer()) {
1207  ostringstream err;
1208  err << "Block at position " << fCurrentPosition
1209  << " is not cherenkov event trailer";
1210  FATAL(err);
1211  return eFail;
1212  }
1213  const Corsika::EventTrailer& trailer = trailerBlock.AsEventTrailer();
1214 
1215  if (!setSimShowerAlready) {
1216  SetHeaderTrailer(event, timeShift, gh, haveGH, zenith, header, trailer);
1217  setSimShowerAlready = true;
1218  }
1219 
1220  } else { // switch to UN-THINNED version of Cherenkov files
1221 
1222  Corsika::BlockUnthinned headerBlock;
1223  if (!file.GetNextBlockUnthinned(headerBlock)) {
1224  ostringstream err;
1225  err << "Cannot read Cherenkov CORSIKA shower header for position "
1226  << fCurrentPosition;
1227  FATAL(err);
1228  return eFail;
1229  }
1230  if (!headerBlock.IsEventHeader()) {
1231  ostringstream err;
1232  err << "First block at position " << fCurrentPosition
1233  << " is not chernkov event header";
1234  FATAL(err);
1235  return eFail;
1236  }
1237  header = headerBlock.AsEventHeader();
1238 
1239  file.SeekTo(fPositionOfEventTrailer[fCurrentPosition]);
1240 
1241  Corsika::BlockUnthinned trailerBlock;
1242  if (!file.GetNextBlockUnthinned(trailerBlock)) {
1243  ostringstream err;
1244  err << "Cannot read CORSIKA shower cherenkov trailer for position "
1245  << fCurrentPosition;
1246  FATAL(err);
1247  return eFail;
1248  }
1249  if (!trailerBlock.IsEventTrailer()) {
1250  ostringstream err;
1251  err << "Block at position " << fCurrentPosition
1252  << " is not cherenkov event trailer";
1253  FATAL(err);
1254  return eFail;
1255  }
1256  const Corsika::EventTrailer& trailer = trailerBlock.AsEventTrailer();
1257 
1258  if (!setSimShowerAlready) {
1259  SetHeaderTrailer(event, timeShift, gh, haveGH, zenith, header, trailer);
1260  setSimShowerAlready = true;
1261  }
1262 
1263  }
1264 
1265  if (fCherenkovIterator.count(am))
1266  delete fCherenkovIterator[am];
1267 
1268  fCherenkovIterator[am] =
1270  header,
1271  fAtmPars,
1272  timeShift,
1273  fPositionToCherenkovRaw[am][fCurrentPosition] + 1,
1274  1,
1275  fIsThinned,
1276  header.fWMaxEM,
1277  true, fCherenkovBandwidthMin, fCherenkovBandwidthMax);
1278  ShowerSimData& shower = event.GetSimShower();
1279  if (!shower.HasGroundCherenkov(am))
1280  shower.MakeGroundCherenkov(am);
1281  shower.GetGroundCherenkov(am).SetFileInterface(fCherenkovIterator[am]);
1282 
1283  } // end loop Cherenkov files
1284 
1285  // check if ShowerSimData is already defined (from header/trailer from binary files)
1286  if (!setSimShowerAlready) {
1287  if (fReadSteerFile) {
1288  setSimShowerAlready = true;
1289  const Status status = ReadSteerFile(event);
1290  if (status != eSuccess) {
1291  INFO("Problem reading steering input");
1292  return status;
1293  }
1294  }
1295  }
1296 
1297  // if still no shower-sim-data: --> Default geometry producer
1298  // BUT this is not 100% filled. If the user does not use special module-sequence this will produce crab...
1299  if (!setSimShowerAlready) {
1300  WARNING("No geometry information found while reading CORSIKA. ShowerSimData is filled only partially!");
1301  //const double field_declination = 0; // don't know better here
1302  const double fieldDeclination = fUseRealisticMagneticDeclination ? 0 : fMagneticDeclination;
1303  const double corsikaRotation = Corsika::CorsikaAzimuthToAuger(fCoordinateRotation, fieldDeclination);
1304  event.MakeSimShower(DefaultShowerGeometryProducer(corsikaRotation, fUseRealisticMagneticDeclination));
1305  }
1306 
1307  ShowerSimData& shower = event.GetSimShower();
1308 
1309  if (fReadLongFile) {
1310  const Status status = ReadProfile(shower, zenith); // only needed for non-SLANT: avoid
1311  if (status != eSuccess) {
1312  INFO("End of file (profile)");
1313  return status;
1314  }
1315  }
1316 
1317  if (!shower.HasGHParameters()) {
1318  if (!haveGH) {
1319  ERROR("Found no (valid) GH fit in DAT file! No profile data available!");
1320  } else {
1321 
1322  const double xmax = gh.GetXMax();
1323  const double nmax = gh.GetNMax();
1324  const double x0 = gh.GetXZero();
1325 
1326  ostringstream msg;
1327  /*msg << "Take GH parameters from CORSIKA event trailer!\n"
1328  "CORSIKA SLANT option cannot be recognized! Use .long file if you use SLANT showers!";
1329  WARNING(msg);
1330  msg.str("");*/
1331  msg << "Nmax = " << nmax << ", "
1332  "Xmax = " << xmax / (g/cm2) << " g/cm2, "
1333  "X0 = " << x0 / (g/cm2) << " g/cm2";
1334  INFO(msg);
1335  shower.MakeGHParameters(gh);
1336  }
1337  }
1338 
1339  ++fCurrentPosition;
1340  return eSuccess;
1341  }
1342 
1343 
1344  Status
1346  {
1347  ifstream corsikaSteer(fSteerFile.c_str());
1348  if (!corsikaSteer.is_open()) {
1349  ostringstream msg;
1350  msg << "Cannot open file " << fSteerFile << " - ERROR";
1351  ERROR(msg);
1352  throw io::CorsikaIOException(msg.str());
1353  }
1354 
1355  string corsikaDataFileBase;
1356  string runNo;
1357  double magneticFieldInclination = 0;
1358  double magneticFieldStrength = 0;
1359  double showerAzimuth = 0;
1360  double showerZenith = 0;
1361  int primaryParticleType = 0;
1362  double primaryEnergy = 0;
1363  double observationHeight = 0;
1364 
1365  string currLine;
1366  while (getline(corsikaSteer, currLine)) {
1367 
1368  if (currLine.empty())
1369  continue;
1370 
1371  const char firstChar = currLine.at(0);
1372  // should check for more variants of commenting out options
1373  if (firstChar == 'c' || firstChar == 'C')
1374  continue;
1375 
1376  // not a comment line, parse it
1377  stringstream ss(currLine);
1378 
1379  if (Contains(currLine, "RUNNR")) {
1380 
1381  string skip;
1382  int rn = 0;
1383  if (!(ss >> skip >> rn))
1384  throw io::CorsikaIOException("RUNNR parse failed");
1385  corsikaDataFileBase = (format("DAT%06d") % rn).str();
1386  runNo = boost::lexical_cast<string>(rn);
1387 
1388  } else if (Contains(currLine, "MAGNET")) {
1389 
1390  string skip;
1391  double bx = 0;
1392  double bz = 0;
1393  if (!(ss >> skip >> bx >> bz))
1394  throw io::CorsikaIOException("MAGNET parse failed");
1395  bx *= micro * tesla;
1396  bz *= micro * tesla;
1397  magneticFieldInclination = atan2(bz, bx);
1398  magneticFieldStrength = sqrt(bx*bx + bz*bz);
1399 
1400  } else if (Contains(currLine, "PHIP")) {
1401 
1402  // get azimuth angle range, can only be used if singular!
1403  std::string skip;
1404  double az2 = 0;
1405  if (!(ss >> skip >> showerAzimuth >> az2))
1406  throw io::CorsikaIOException("PHIP parse failed");
1407  if (showerAzimuth != az2) {
1408  const string msg =
1409  "Cannot use CORSIKA azimuth because azimuth angle range in CORSIKA parameter file is not singular";
1410  ERROR(msg);
1411  throw io::CorsikaIOException(msg);
1412  }
1413  showerAzimuth *= utl::degree;
1414 
1415  } else if (Contains(currLine, "THETAP")) {
1416 
1417  // get zenith angle range, can only be used if singular!
1418  std::string skip;
1419  double zen2 = 0;
1420  if (!(ss >> skip >> showerZenith >> zen2))
1421  throw io::CorsikaIOException("THETAP parse failed");
1422  if (showerZenith != zen2) {
1423  const string msg =
1424  "Cannot use CORSIKA zenith because zenith angle range in CORSIKA parameter file is not singular";
1425  ERROR(msg);
1426  throw io::CorsikaIOException(msg);
1427  }
1428  showerZenith *= utl::degree;
1429 
1430  } else if (Contains(currLine, "ERANGE")) {
1431 
1432  // get energy range, can only be used if singular!
1433  std::string skip;
1434  double en2 = 0;
1435  if (!(ss >> skip >> primaryEnergy >> en2))
1436  throw io::CorsikaIOException("ERANGE parse failed");
1437  if (primaryEnergy != en2) {
1438  const string msg =
1439  "Cannot use CORSIKA energy because energy angle range in CORSIKA parameter file is not singular";
1440  ERROR(msg);
1441  throw io::CorsikaIOException(msg);
1442  }
1443  primaryEnergy *= utl::GeV;
1444 
1445  } else if (Contains(currLine, "PRMPAR")) {
1446 
1447  std::string skip;
1448  if (!(ss >> skip >> primaryParticleType))
1449  throw io::CorsikaIOException("PRMPAR parse failed");
1450 
1451  } else if (Contains(currLine, "ARRANG")) {
1452 
1453  // get corsika array rotation
1454  std::string skip;
1455  double skip2 = 0;
1456  if (!(ss >> skip >> fCoordinateRotation >> skip2))
1457  throw io::CorsikaIOException("ARRANG parse failed");
1458  fCoordinateRotation *= utl::degree;
1459 
1460  } else if (Contains(currLine, "OBSLEV")) {
1461 
1462  // read out the observation level; will use last observation level specified in the steering file
1463  std::string skip;
1464  if (!(ss >> skip >> observationHeight))
1465  throw io::CorsikaIOException("OBSLEV parse failed");
1466  observationHeight *= utl::cm;
1467 
1468  }
1469 
1470  }
1471 
1472  // geometry producer from .inp steering file -> no ground particles, no multi-core, etc.
1473  const bool multiCore = false;
1474  const CoordinateSystemPtr& refCoordinateSystem = fwk::CoordinateSystemRegistry::Get(fRefCoordinateSystemName);
1475  event.MakeSimShower(
1477  showerZenith, showerAzimuth + fCoordinateRotation,
1478  observationHeight,
1479  multiCore,
1480  refCoordinateSystem,
1481  fMagneticDeclination,
1482  fUseRealisticMagneticDeclination
1483  )
1484  );
1485  /*
1486 #warning RU: check geometry here
1487  // calculate shower aziumth relative to geographic north (correct for magnetic field declination)
1488  // and normalize it to [0,2pi)
1489  showerAzimuth -= magneticfielddeclination;
1490  if (showerAzimuth < 0)
1491  showerAzimuth += 2 * kPi;
1492  // the actual setting of the shower azimuth has to be done after CORSIKA::Read() because
1493  // the CORSIKA reader has a hard-coded value for the magnetic field declination!
1494  // CORSIKA read-in is done, now set the shower azimuth
1495  */
1496 
1497  auto& shower = event.GetSimShower();
1498  shower.SetPrimaryParticle(Corsika::CorsikaToPDG(primaryParticleType));
1499  shower.SetEnergy(primaryEnergy);
1500  shower.SetShowerRunId(runNo);
1501  shower.SetMagneticFieldInclination(magneticFieldInclination);
1502  shower.SetMagneticFieldStrength(magneticFieldStrength);
1503  shower.SetGroundParticleCoordinateSystemZenith(showerZenith);
1504  shower.SetGroundParticleCoordinateSystemAzimuth(
1505  showerAzimuth
1506  + 180*degree // incoming vs. outgoing
1507  - fCoordinateRotation // eventual array rotation
1508  );
1509 
1510  return eSuccess;
1511  }
1512 
1513 
1514  // check existance
1515  Status
1517  {
1518  if (!boost::filesystem::exists(name))
1519  return eFail;
1520  return eSuccess;
1521  }
1522 
1523 
1524  // check integrity of file, get number of events
1525  Status
1527  {
1528  ifstream longFile(name);
1529  if (!longFile.is_open())
1530  return eFail;
1531 
1532  fNumberOfEvents = 0;
1533 
1534  string line;
1535  while (getline(longFile, line))
1536  if (Contains(line, "LONGITUDINAL DISTRIBUTION IN"))
1537  ++fNumberOfEvents;
1538 
1539  return eSuccess;
1540  }
1541 
1542 
1543  Status
1545  const double zenith) // only needed for non-SLANT: avoid
1546  {
1547  //const utl::CoordinateSystemPtr localCS = shower.GetLocalCoordinateSystem(); this is not YET DEFINED here !
1548  //const double cosZenith = (-shower.GetDirection()).GetCosTheta(localCS); // this is only needed if not SLANT -> always use SLANT !
1549  const double cosZenith = cos(zenith);
1550 
1551  vector<string> lines;
1552  {
1553  // Read CORSIKA profile if available
1554  ifstream longFile(fLongFile.c_str());
1555 
1556  if (!longFile.is_open()) {
1557  const string info = "Cannot read longitudinal-profile file '" + fLongFile + "'.";
1558  ERROR(info);
1559  throw CorsikaIOException(info);
1560  }
1561 
1562  unsigned int showerN = 0;
1563  string line;
1564  while (getline(longFile, line)) {
1565  if (line.empty())
1566  continue;
1567  if (Contains(line, "LONGITUDINAL DISTRIBUTION IN")) {
1568  ++showerN;
1569  if (showerN > fCurrentPosition+1)
1570  break;
1571  }
1572  if (showerN == fCurrentPosition+1)
1573  lines.push_back(line);
1574  }
1575  }
1576  const unsigned int nLines = lines.size();
1577 
1578  if (nLines < 3) {
1579  // this is not worth an extra output
1580  //ostringstream err;
1581  //err << "Cannot find position " << fCurrentPosition << " in the longitudinal file '" << fLongFile << "'. EOF.";
1582  //INFO(err);
1583  return eEOF;
1584  } else {
1585  ostringstream info;
1586  info << "Read " << nLines << " lines from '" << fLongFile << "' at position " << fCurrentPosition;
1587  INFO(info);
1588  }
1589 
1590  typedef unsigned int (*ToUIntType)(const string&);
1591  const ToUIntType ToUInt = &boost::lexical_cast<unsigned int>;
1592  typedef double (*ToDType)(const string&);
1593  const ToDType ToD = &boost::lexical_cast<double>;;
1594 
1595  bool isProfileSlant = false;
1596  unsigned int profileLength = 0;
1597  unsigned int i = 0;
1598  // particle distributions
1599  {
1600  const vector<string> s1 = String::Split(lines.at(i));
1601  const vector<string> s2 = String::Split(lines.at(i + 1));
1602  if (!(s1.size() == 12 && s1[0] == "LONGITUDINAL" && s1[1] == "DISTRIBUTION" &&
1603  s2.size() == 10 && s2[0] == "DEPTH" && s2[1] == "GAMMAS")) {
1604  const string err = "Mismatch in particle distribution profile headers.";
1605  ERROR(err);
1606  throw CorsikaIOException(err);
1607  } else {
1608  const unsigned int n = ToUInt(s1[3]);
1609  profileLength = n;
1610  if (n < 1) {
1611  const string msg = "Particle profiles are too short.";
1612  ERROR(msg);
1613  throw CorsikaIOException(msg);
1614  }
1615  const string& type = s1[4];
1616  if (type != "SLANT" && type != "VERTICAL") {
1617  const string err = "Unknown profile type '" + type + "', (only 'VERTICAL' and 'SLANT' supported)";
1618  ERROR(err);
1619  throw CorsikaIOException(err);
1620  }
1621  isProfileSlant = (type == "SLANT");
1622  const double slantFac = isProfileSlant ? 1 : 1 / fabs(cosZenith); // cosZenith < 0 for Zenith > 90
1623  const double dX = slantFac * ToD(s1[7]) * (g/cm2);
1624  i += 2;
1625  vector<double> particleDistributionDepth;
1626  vector<double> particleDistributionCharge;
1627  vector<double> particleDistributionMuons;
1628  vector<double> particleDistributionGammas;
1629  vector<double> particleDistributionElectrons;
1630  vector<double> particleProductionCherenkov;
1631  for (unsigned int j = 0; j < n; ++j) {
1632  // the Fortran formats for writing the longitudina energy deposits depend on the options
1633  // With the SLANT option the format is FORMAT(' ', F7.1, 1P, 9(E12.5), 0P)
1634  // For the standard output without SLANT option the format is FORMAT(' ', F5.0, 1P, 9(E12.5), 0P)
1635  vector<string> s = String::Split(lines.at(i));
1636  if (s.size() != 10) {
1637  WARNING("Corrupted longitudinal particle distribution table detected; will try to fix it.");
1638  s = FixedColumnWidthSplit(lines[i], 10, isProfileSlant ? 8 : 6, 12);
1639  ostringstream warn;
1640  warn << "Fixed line: ";
1641  string comma = "";
1642  for (const auto& sss : s) {
1643  warn << comma << sss;
1644  comma = ", ";
1645  }
1646  WARNING(warn);
1647  }
1648  particleDistributionDepth.push_back(slantFac * ToD(s.at(0)) * (g / cm2));
1649  const double ch = ToD(s.at(7));
1650  particleDistributionCharge.push_back(ch);
1651  const double mu = ToD(s.at(4)) + ToD(s.at(5));
1652  particleDistributionMuons.push_back(mu);
1653  particleDistributionGammas.push_back(ToD(s.at(1)));
1654  const double e = ToD(s.at(2)) + ToD(s.at(3));
1655  particleDistributionElectrons.push_back(e);
1656  {
1657  // this is "production per bin", thus need to normalize per bin-width
1658  particleProductionCherenkov.push_back(double(ToD(s.at(9)) / dX));
1659  }
1660  ++i;
1661  }
1662  const double normDepth = particleDistributionDepth.back();
1663  const double normN = shower.HasGHParameters() ? shower.GetGHParameters().Eval(normDepth) : 1;
1664  const double normCharge = particleDistributionCharge.back();
1665  const double normMuons = particleDistributionMuons.back();
1666  const double normGammas = particleDistributionGammas.back();
1667  const double normElectrons = particleDistributionElectrons.back();
1668  const double normCherenkov = particleProductionCherenkov.back();
1669  const double lastDepth = particleDistributionDepth.back();
1670 
1671  // prolongate profiles by some bins
1672  if (type == "SLANT" || zenith < 90*deg) {
1673  // do not do for VERTICAL depth in the case of up-going events.
1674  for (int j = 0; j < 2; ++j) {
1675  const double addDepth = lastDepth + dX * (j + 1);
1676  const double nch = shower.HasGHParameters() ? shower.GetGHParameters().Eval(addDepth) : 1;
1677  const double fac = nch / normN;
1678  const double addCharge = normCharge * fac;
1679  const double addMuons = normMuons * fac;
1680  const double addGammas = normGammas * fac;
1681  const double addElectrons = normElectrons * fac;
1682  const double addCherenkov = normCherenkov * fac;
1683  particleDistributionDepth.push_back(addDepth);
1684  particleDistributionCharge.push_back(addCharge);
1685  particleDistributionMuons.push_back(addMuons);
1686  particleDistributionGammas.push_back(addGammas);
1687  particleDistributionElectrons.push_back(addElectrons);
1688  particleProductionCherenkov.push_back(addCherenkov);
1689  }
1690  }
1691  if (type == "VERTICAL" && zenith > 90*deg) {
1692  // the vertical depth in the .long file goes from top of the
1693  // atmosphere(min value) to the bottom of the atmosphere(max value)
1694  // for UPWARD events, the min value of X_slant corresponds to the
1695  // bottom of the atmosphere, and max value to the top of the
1696  // atmosphere. So the vertical profile must be reversed for upgoing
1697  // events
1698  reverse(particleDistributionCharge.begin(), particleDistributionCharge.end());
1699  reverse(particleDistributionMuons.begin(), particleDistributionMuons.end());
1700  reverse(particleDistributionGammas.begin(), particleDistributionGammas.end());
1701  reverse(particleDistributionElectrons.begin(), particleDistributionElectrons.end());
1702  reverse(particleProductionCherenkov.begin(), particleProductionCherenkov.end());
1703  }
1704  TabulatedFunction muonProfile;
1705  TabulatedFunction gammaProfile;
1706  TabulatedFunction electronProfile;
1707  TabulatedFunction chargeProfile; // charged
1708  TabulatedFunction cherenkovProfile;
1709  for (unsigned int j = 0; j < n; ++j) {
1710  const double depth = particleDistributionDepth[j];
1711  chargeProfile.PushBack(depth, particleDistributionCharge[j]);
1712  muonProfile.PushBack(depth, particleDistributionMuons[j]);
1713  gammaProfile.PushBack(depth, particleDistributionGammas[j]);
1714  electronProfile.PushBack(depth, particleDistributionElectrons[j]);
1715  cherenkovProfile.PushBack(depth, particleProductionCherenkov[j]);
1716  }
1717  if (!shower.HasLongitudinalProfile())
1718  shower.MakeLongitudinalProfile(chargeProfile);
1719  // RU Thu May 12 10:03:23 CEST 2005 (added moun profile)
1721  shower.MakeLongitudinalProfile(muonProfile, ShowerSimData::eMuon);
1723  shower.MakeLongitudinalProfile(gammaProfile, ShowerSimData::ePhoton);
1725  shower.MakeLongitudinalProfile(electronProfile, ShowerSimData::eElectron);
1726  // RU Mo 26. Sep 23:06:39 CEST 2016 (added differential Cherenkov production profile)
1727  if (!shower.HasLongitudinalProfile(ShowerSimData::eCherenkov))
1728  shower.MakeLongitudinalProfile(cherenkovProfile, ShowerSimData::eCherenkov);
1729 
1730  }
1731  }
1732  // energy deposit
1733  {
1734  const vector<string> s1 = String::Split(lines.at(i));
1735  const vector<string> s2 = String::Split(lines.at(i + 1));
1736  if (!(s1.size() == 13 && s1[0] == "LONGITUDINAL" && s1[1] == "ENERGY" &&
1737  s2.size() == 16 && s2[0] == "DEPTH" && s2[1] == "GAMMA")) {
1738  const string err = "Mismatch in energy deposit profile headers.";
1739  ERROR(err);
1740  throw CorsikaIOException(err);
1741  } else {
1742  const unsigned int n = ToUInt(s1[4]);
1743  if (n < 3) {
1744  const string msg = "Energy deposit profile is too short.";
1745  ERROR(msg);
1746  throw CorsikaIOException(msg);
1747  }
1748  const string& type = s1[5];
1749  if (type != "SLANT" && type != "VERTICAL") {
1750  const string err = "Unknown profile type '" + type + "', (only 'VERTICAL' and 'SLANT' supported)";
1751  ERROR(err);
1752  throw CorsikaIOException(err);
1753  }
1754  if (isProfileSlant != (type == "SLANT")) {
1755  const string err = "Energy deposit and particle profiles do not have the same inclination.";
1756  ERROR(err);
1757  throw CorsikaIOException(err);
1758  }
1759  const double slantFac = isProfileSlant ? 1 : 1 / fabs(cosZenith); // cosZenith < 0 for Zenith > 90
1760  const double dX = slantFac * ToD(s1[8]) * g/cm2;
1761  i += 2;
1762  // The energy deposit of particles hitting ground is accounted for in"
1763  // the last row if not SLANT
1764  // the two last rows if SLANT (in good approximation*)
1765  // *A few (early) particles reaching ground are accounted for in previous rows (Effect seems to be very small,
1766  // Tanguy thinks that could be "corrected" by counting the last row twice for groundEnergy. This is not done yet here!)
1767  const unsigned int groundRowsAtTheEnd = (isProfileSlant) ? 2 : 1;
1768  double energyDepositSum = 0;
1769  double electromagneticEnergyDeposit = 0;
1770  vector<double> energyDepositDepth;
1771  vector<double> energyDeposit;
1772  for (unsigned int j = 0; j < n; ++j) {
1773  vector<string> s = String::Split(lines.at(i));
1774  if (s.size() != 10) {
1775  WARNING("Corrupted longitudinal energy deposit table detected; will try to fix it.");
1776  s = FixedColumnWidthSplit(lines[i], 10, isProfileSlant ? 8 : 7, 12);
1777  ostringstream warn;
1778  warn << "Fixed line: ";
1779  string comma = "";
1780  for (const auto& sss : s) {
1781  warn << comma << sss;
1782  comma = ", ";
1783  }
1784  WARNING(warn);
1785  }
1786  energyDepositDepth.push_back(slantFac * ToD(s.at(0)) * (g / cm2));
1787  const double muCut = ToD(s.at(5)) * GeV;
1788  const double hadCut = ToD(s.at(7)) * GeV;
1789  const double nu = ToD(s.at(8)) * GeV;
1790  const double sum = ToD(s.at(9)) * GeV;
1791  // Subtracting neutrino energy and take fraction for muons and hadrons
1792  // Barbosa et al., Astropart. Phys. 22 (2004) 159.
1793  const double muonFraction = 0.575;
1794  const double hadronFraction = 0.; //0.261; // <-- Tanguy thinks this should be zero
1795  const double de = sum - nu - muonFraction * muCut - hadronFraction * hadCut;
1796  energyDeposit.push_back(de / dX);
1797  if (j < n - groundRowsAtTheEnd)
1798  energyDepositSum += de;
1799  else {
1800  const double gamma = ToD(s.at(1)) * GeV;
1801  const double emIon = ToD(s.at(2)) * GeV;
1802  const double emCut = ToD(s.at(3)) * GeV;
1803  const double muIon = ToD(s.at(4)) * GeV;
1804  const double hadIon = ToD(s.at(6)) * GeV;
1805  const double hadronGroundFraction = 0.390;
1806  const double groundEnergy =
1807  (1 - hadronGroundFraction) * hadCut + hadIon + muIon + emIon + emCut + gamma;
1808  energyDepositSum += groundEnergy;
1809  }
1810 
1811  // Adding GAMMA, EM IONIZ and EM CUT over whole atmosphere including energy deposit in ground plane
1812  electromagneticEnergyDeposit += (ToD(s.at(1)) + ToD(s.at(2)) + ToD(s.at(3))) * GeV;
1813  ++i;
1814  }
1815  const double normDepth = energyDepositDepth[n - 3];
1816  const double normN = shower.HasGHParameters() ? shower.GetGHParameters().Eval(normDepth) : 1;
1817  // go three bins back, since CORSIKA sometimes has a funny second-last bin
1818  const double normdEdX = energyDeposit[n - 3];
1819  // recalculate last dedx bins
1820  for (int j = 0; j < 2; ++j) {
1821  const int bin = n - 2 + j;
1822  const double depth = energyDepositDepth[bin];
1823  energyDeposit[bin] = normdEdX / normN *
1824  (shower.HasGHParameters() ? shower.GetGHParameters().Eval(depth) : 1);
1825  }
1826  const double lastDepth = energyDepositDepth.back();
1827  // prolongate profiles by some bins
1828  if (type == "SLANT" || zenith < 90*deg) {
1829  // do not do for VERTICAL depth in the case of up-going events
1830  for (int j = 0; j < 2; ++j) {
1831  const double addDepth = lastDepth + dX * (j + 1);
1832  const double nch = shower.HasGHParameters() ? shower.GetGHParameters().Eval(addDepth) : 1;
1833  const double adddEdX = normdEdX / normN * nch;
1834  energyDepositDepth.push_back(addDepth);
1835  energyDeposit.push_back(adddEdX);
1836  }
1837  }
1838  // the vertical depth in the .long file goes from top of the
1839  // atmosphere(min value) to the bottom of the atmosphere(max value) for
1840  // UPWARD events, the min value of X_slant corresponds to the bottom of
1841  // the atmosphere, and max value to the top of the atmosphere. So the
1842  // vertical profile must be reversed for upgoing events
1843  if (type == "VERTICAL" && zenith > 90*deg)
1844  reverse(energyDeposit.begin(), energyDeposit.end());
1845  TabulatedFunction dEdX;
1846  for (unsigned int j = 0; j < n; ++j)
1847  dEdX.PushBack(energyDepositDepth[j], energyDeposit[j]);
1848 
1849  for (unsigned int i = 0; i < energyDepositDepth.size(); ++i) {
1850  GHFit::gProfile.fSlantDepth.push_back(energyDepositDepth[i]);
1851  GHFit::gProfile.fEnergyDeposit.push_back(energyDeposit[i]);
1852  }
1853 
1854  shower.SetCalorimetricEnergy(energyDepositSum);
1855  shower.SetElectromagneticEnergy(electromagneticEnergyDeposit);
1856  // BRD we need a trap here. Only MakedEdX if such profile is not null
1857  if (!energyDeposit.empty() && !shower.HasdEdX())
1858  shower.MakedEdX(dEdX);
1859  }
1860  }
1861 
1862  {
1863  const vector<string> s1 = String::Split(i < nLines ? lines[i] : "");
1864  const vector<string> s2 = String::Split(i + 1 < nLines ? lines[i + 1] : "");
1865  if (!(s1.size() > 5 && s1[0] == "FIT" && s1[1] == "OF" && s1[2] == "THE" && s1[3] == "HILLAS" &&
1866  s2.size() == 7 && s2[0] == "TO" && s2[1] == "LONGITUDINAL" && s2[2] == "DISTRIBUTION")) {
1867  WARNING("Native GH fit not found.");
1868  } else {
1869  const vector<string> s3 = String::Split(lines.at(i + 2));
1870  const vector<string> s4 = String::Split(lines.at(i + 3));
1871  //const vector<string> s5 = String::Split(lines.at(i + 4));
1872  if (s3.size() != 8 || s3[0] != "PARAMETERS" || s3[1] != "=" ||
1873  s4.size() != 3 || s4[0] != "CHI**2/DOF" || s4[1] != "=" /*||
1874  s5.size() < 3 || s5[0] != "AV."*/) {
1875  const string err = "Malformed fit of the Hillas curve.";
1876  ERROR(err);
1877  throw CorsikaIOException(err);
1878  }
1879  // do not do for VERTICAL depth in the case of up-going events
1880  // parameters in CORSIKA calculated for atmosphere starting to the top and ending to the bottom
1881  // we need the parameters correpsonding to the reversed profiles (atmo starts at the bottom and ends at top
1882  if (isProfileSlant || zenith < 90*deg) {
1883  const double slantFac = isProfileSlant ? 1 : 1 / fabs(cosZenith); // cosZenith < 0 for Zenith > 90
1884  const double nMax = ToD(s3.at(2));
1885  const double x0 = slantFac * ToD(s3.at(3)) * g / cm2;
1886  const double xMax = slantFac * ToD(s3.at(4)) * g / cm2;
1887  const double apar = ToD(s3.at(5)) * g / cm2;
1888  const double bpar = ToD(s3.at(6));
1889  const double cpar = ToD(s3.at(7)) / (g / cm2);
1890  const double chi2 = ToD(s4.at(2));
1891  if (chi2 <= 0 || chi2 * profileLength > 1e15) {
1892  ostringstream err;
1893  err << "CORISKA shower with invalid GH fit: Xmax = " << xMax / (g/cm2) << " g/cm2, "
1894  "Nmax = " << nMax << ", X0 = " << x0 / (g/cm2) << " g/cm2, chi2/ndf = " << chi2;
1895  ERROR(err);
1896  } else {
1897  const double a = slantFac * apar;
1898  const double b = slantFac * bpar;
1899  const double c = slantFac * cpar;
1901  gh.SetXMax(xMax, 0);
1902  gh.SetNMax(nMax, 0);
1903  gh.SetXZero(x0, 0);
1904  gh.SetChiSquare(chi2 * profileLength, profileLength);
1905  gh.SetA(a, 0);
1906  gh.SetB(b, 0);
1907  gh.SetC(c, 0);
1908  ostringstream info;
1909  info << "Nmax = " << nMax << ", "
1910  "Xmax = " << xMax / (g/cm2) << " g/cm2, "
1911  "X0 = " << x0 / (g/cm2) << " g/cm2, "
1912  "zenith = " << acos(cosZenith) / deg << " deg"
1913  << (isProfileSlant ? " (SLANT DEPTH)" : " (VERTICAL DEPTH)");
1914  INFO(info);
1915  if (!shower.HasGHParameters())
1916  shower.MakeGHParameters(gh);
1917  }
1918  } else if (!isProfileSlant && zenith > 90*deg) {
1919  // recalculating GHParameters for the case of up-going showers and VERTICAL atmosphere
1920  // so that atmosphere starts at the bottom and ends at the top. Using the parameters
1921  // resulted from the GH fit of N(x), as explained above
1922 
1923  TMinuit minuitdEdXGHFit(4); // GH with 4 par for fit
1924  minuitdEdXGHFit.SetFCN(GHFit::GH4ParamFitFunctiondEdxGH);
1925 
1926  double arglist[2] = { 0 };
1927  int ierflag;
1928  // get the index for the corresponding depth to dEdXmax from .long data;
1929  const double index = std::distance(
1930  GHFit::gProfile.fEnergyDeposit.begin(),
1931  max_element(GHFit::gProfile.fEnergyDeposit.begin(), GHFit::gProfile.fEnergyDeposit.end())
1932  );
1933  arglist[0] = 1;
1934  minuitdEdXGHFit.mnexcm("SET ERR", arglist, 1, ierflag);
1935  // take expectded dEdXmax from the current .long data
1936  double dEdXmaxFit = *max_element(GHFit::gProfile.fEnergyDeposit.begin(), GHFit::gProfile.fEnergyDeposit.end()) / (PeV / (g/cm2));
1937  // first guess of xmax
1938  double xMaxFit = GHFit::gProfile.fSlantDepth[index] / (g/cm2);
1939  // initial guess for X0 and lambda
1940  double x0Fit = 10;
1941  double lambdaFit = 70;
1942 
1943  minuitdEdXGHFit.mnparm(0, "dEdXmax", dEdXmaxFit, 0.1 * fabs(dEdXmaxFit), 0, 0, ierflag);
1944  minuitdEdXGHFit.mnparm(1, "X0", x0Fit, 1, -1000, GHFit::gProfile.fSlantDepth[0] / (g/cm2), ierflag);
1945  minuitdEdXGHFit.mnparm(2, "Xmax", xMaxFit, 20, 0, 0, ierflag);
1946  minuitdEdXGHFit.mnparm(3, "lambda", lambdaFit, 5, 10, 150, ierflag);
1947 
1948  arglist[0] = 1000;
1949  arglist[1] = 1;
1950 
1951  minuitdEdXGHFit.mnexcm("MIGRAD", arglist, 2, ierflag);
1952 
1953  double amin = 0;
1954  double edm = 0;
1955  double errdef = 0;
1956  int nvpar = 0;
1957  int nparx = 0;
1958  int icstat = 0;
1959  minuitdEdXGHFit.mnstat(amin, edm, errdef, nvpar, nparx, icstat);
1960 
1961  double p[4] = { 0 };
1962  minuitdEdXGHFit.GetParameter(0, dEdXmaxFit, p[0]);
1963  minuitdEdXGHFit.GetParameter(1, x0Fit, p[1]);
1964  minuitdEdXGHFit.GetParameter(2, xMaxFit, p[2]);
1965  minuitdEdXGHFit.GetParameter(3, lambdaFit, p[3]);
1966 
1967  // get Nmax using xmax and dEdXmax provided from dEdX GHfit
1968  // utl::EnergyDeposit (const double depthX, const double xMax, const double enCut) calculated in PhysicalFunctions.cc
1969  // it is really the mean ionization loss rate. Parameters are for energies in MeV / (g/cm2)
1970 
1971  const double nMaxFit =
1972  dEdXmaxFit * PeV / MeV /
1973  (utl::EnergyDeposit(xMaxFit * (g / cm2), xMaxFit * (g/cm2), GHFit::gProfile.fElectronCutoff / MeV) / (MeV / (g/cm2)));
1974 
1975  const double x0 = x0Fit * g/cm2;
1976  const double xMax = xMaxFit * g/cm2;
1977  //getting lambda from a GH4 fit. Let it with GH6Fit for consistency. However by making a =lambda,b=0, c=0 nothing changes
1978  const double apar = lambdaFit * g/cm2;
1979  // b and c param only used in GaisserHillas6Parameters.cc, Eval(depth) - l. 28:
1980  // lambda = fA + depth*fB + Sqr(depth)*fC;
1981  const double bpar = 0;
1982  const double cpar = 0;
1983  const double chi2 = minuitdEdXGHFit.fAmin;
1984  if (chi2 <= 0 || chi2 * profileLength > 1e15) {
1985  ostringstream err;
1986  err << "CORISKA shower with invalid GH fit: Xmax = " << xMax / (g/cm2) << " g/cm2, "
1987  "Nmax = " << nMaxFit << ", X0 = " << x0 / (g/cm2) << " g/cm2, chi2/ndf = " << chi2;
1988  ERROR(err);
1989  } else {
1990  const double a = apar;
1991  const double b = bpar;
1992  const double c = cpar;
1994  gh.SetXMax(xMax, 0);
1995  gh.SetNMax(nMaxFit, 0);
1996  gh.SetXZero(x0, 0);
1997  gh.SetChiSquare(chi2 * profileLength, profileLength);
1998  gh.SetA(a, 0);
1999  gh.SetB(b, 0);
2000  gh.SetC(c, 0);
2001  ostringstream info;
2002  info << "Nmax = " << nMaxFit << ", "
2003  "Xmax = " << xMax / (g/cm2) << " g/cm2, "
2004  "X0 = " << x0 / (g/cm2) << " g/cm2, "
2005  "zenith = " << acos(cosZenith) / deg << " deg"
2006  << (isProfileSlant ? " (SLANT DEPTH)" : " (VERTICAL DEPTH)");
2007  INFO(info);
2008  if (!shower.HasGHParameters())
2009  shower.MakeGHParameters(gh);
2010  }
2011  }
2012  }
2013  }
2014 
2015  // CORSIKA writes into the last bin of the dEdX profile not only
2016  // the energy deposit in the atmosphere, but also the energy hitting
2017  // the ground (observation level). For vertical event this can be
2018  // remarkable, and should not be used for FD simulations!
2019  //
2020  // - Fix this by replacing the entries in the last bin by GH fitted
2021  // extrapolations.
2022  // - Add one more bin in depth (extrapolation) to make sure
2023  // that also for a different atmospheric profile model the
2024  // shower will hit the ground, and does not just disappear
2025  // somewhere in the middle of the atmosphere!
2026 
2027  return eSuccess;
2028  }
2029 
2030 
2031  void
2033  {
2034  const string msg = "Cannot write to CorsikaShowerFile - read-only file";
2035  ERROR(msg);
2036  throw CorsikaIOException(msg);
2037  }
2038 
2039 
2040  Status
2041  CorsikaShowerFile::FindEvent(const unsigned int eventId)
2042  {
2043  const auto it = fIdToPosition.find(eventId);
2044  if (it == fIdToPosition.end())
2045  return eFail;
2046  return GotoPosition(it->second);
2047  }
2048 
2049 
2050  Status
2051  CorsikaShowerFile::GotoPosition(const unsigned int position)
2052  {
2053  if (position >= fNumberOfEvents)
2054  return eFail;
2055  fCurrentPosition = position;
2056  return eSuccess;
2057  }
2058 
2059 
2060  int
2062  {
2063  if (fOpenedAnything)
2064  return fNumberOfEvents;
2065 
2066  const string msg = "Cannot request number of events from closed file";
2067  ERROR(msg);
2068  throw CorsikaIOException(msg);
2069  }
2070 
2071 
2074  const
2075  {
2076  return fParticleIterator;
2077  }
2078 
2079 
2080  double
2082  const
2083  {
2084  for (int i = 0; i < 4; ++i)
2085  if (fAtmPars.fHLAY[i] <= height && height < fAtmPars.fHLAY[i + 1])
2086  return fAtmPars.fBATM[i] * exp(-height / fAtmPars.fCATM[i]) / fAtmPars.fCATM[i];
2087  if (height >= fAtmPars.fHLAY[4])
2088  return fAtmPars.fBATM[4] / fAtmPars.fCATM[4];
2089  return 0;
2090  }
2091 
2092 
2093  double
2095  const
2096  {
2097  //figure out in what layer of the atmosphere model the first interaction happened
2098  int section = 0;
2099  //#warning why use fabs(height)?
2100  if (fabs(height) > fAtmPars.fHLAY[4])
2101  section = 4;
2102  else if (fabs(height) > fAtmPars.fHLAY[3])
2103  section = 3;
2104  else if (fabs(height) > fAtmPars.fHLAY[2])
2105  section = 2;
2106  else if (fabs(height) > fAtmPars.fHLAY[1])
2107  section = 1;
2108 
2109  //Calculate X1 (slant depth in g/cm^2) from height (in cm) of first interaction
2110  if (section < 4)
2111  return fAtmPars.fAATM[section] + fAtmPars.fBATM[section] * exp(-height / fAtmPars.fCATM[section]);
2112  return fAtmPars.fAATM[section] - fAtmPars.fBATM[section] * (height / fAtmPars.fCATM[section]);
2113  }
2114 
2115 
2116  double
2117  CorsikaShowerFile::GetSlantDepthCurved(const double hObs, const double theta, const double h)
2118  const
2119  {
2120  using namespace Corsika;
2121  const double sinZenith = sin(theta);
2122  const double sinZenith2 = sinZenith * sinZenith;
2123 
2124  // the resolution
2125  const double dX = 0.01 * (g/cm2);
2126  double height = h;
2127  double depth = 0;
2128 
2129  while (height < hAtmBoundary && height > hObs) {
2130  height += dX * sqrt(1 - Sqr((Corsika::kEarthRadius + hObs) / (Corsika::kEarthRadius + height)) * sinZenith2) /
2131  GetDensityVsHeight(height);
2132  depth += dX;
2133  }
2134  return depth;
2135  }
2136 
2137 }
void SetHeaderTrailer(evt::Event &event, double &timeShift, evt::GaisserHillas6Parameter &gh, bool &haveGH, double &zenith, const Corsika::EventHeader &header, const Corsika::EventTrailer &trailer)
double GaisserHillas4ParameterFunction(const double x, const double dEdXmax, const double x0, const double xmax, const double lambda)
const double eV
Definition: GalacticUnits.h:35
Status Read(evt::Event &event) override
read current event advance cursor by 1
Status ReadProfile(evt::ShowerSimData &shower, const double zenith)
vector< string > FixedColumnWidthSplit(const string &line, const unsigned int nColumns, const unsigned int firstColumnWidth, const unsigned int columnWidth)
int raw
Definition: dump1090.h:270
const double degree
constexpr T Sqr(const T &x)
Point object.
Definition: Point.h:32
Status ScanGroundFile(const std::string &name, Corsika::RawFile &raw, PositionVector &posEVTH, PositionVector &posEVTE, const bool first, const bool noException=false)
Collect information about ground file.
const double tesla
Definition: GalacticUnits.h:40
const double PeV
double GetDepthVsHeight(const double h) const
Return vertical atmospheric depth as a function of height, according to the parameters stored in the ...
bool IsRunHeader() const
Definition: CorsikaBlock.h:421
void Write(const evt::Event &event) override
const evt::VGaisserHillasParameter & GetGHParameters() const
Get the Gaisser-Hillas parameters of the shower.
Class to hold and convert a point in geodetic coordinates.
Definition: UTMPoint.h:40
Class to hold collection (x,y) points and provide interpolation between them.
RWORD fObservationHeight[kMaxObservationLevels]
Definition: CorsikaBlock.h:207
event header struct for Corsika files
Definition: CorsikaBlock.h:182
constexpr double radian
Definition: AugerUnits.h:130
Status ScanSteerFile(const std::string &name)
bool HasGroundParticles() const
bool HasSimShower() const
std::map< std::string, std::string > AttributeMap
Definition: Branch.h:24
Mode
Available open modes.
Definition: IoCodes.h:16
void SetXMax(const double xMax, const double error)
utl::ShowerParticleList & GetGroundCherenkov(const utl::AttributeMap &am)
#define INFO(message)
Macro for logging informational messages.
Definition: ErrorLogger.h:161
#define FATAL(message)
Macro for logging fatal messages.
Definition: ErrorLogger.h:167
void MakeGHParameters(const evt::VGaisserHillasParameter &ghPar)
Make the Gaisser-Hillas parameters of the shower.
const RunHeader & AsRunHeader() const
Definition: CorsikaBlock.h:428
void PushBack(const double x, const double y)
Raw disk file.
Branch GetChild(const std::string &childName) const
Get child of this Branch by child name.
Definition: Branch.cc:211
boost::shared_ptr< const VParticleProperties > ParticlePropertiesPtr
double pow(const double x, const unsigned int i)
void MakedEdX(const utl::TabulatedFunction &dEdX)
Make the energy deposit of the shower.
void reverse(double arr[], int count)
Status GotoPosition(const unsigned int position) override
goto by position in the file
bool GetNextBlock(Block &block)
Read one block and advance.
const EventTrailer & AsEventTrailer() const
Definition: CorsikaBlock.h:431
const double high[npar]
Definition: UnivRec.h:78
bool IsEventHeader() const
Definition: CorsikaBlock.h:423
constexpr double nanometer
Definition: AugerUnits.h:102
void SetFileInterface(VShowerFileParticleIterator *const interface)
constexpr double deg
Definition: AugerUnits.h:140
void SetB(const double b, const double error)
constexpr double MeV
Definition: AugerUnits.h:184
Interface class to access Shower Simulated parameters.
Definition: ShowerSimData.h:49
bool IsOpen() const
Check if the file is open.
T Get() const
Definition: Branch.h:271
AttributeMap GetAttributes() const
Get a map&lt;string, string&gt; containing all the attributes of this Branch.
Definition: Branch.cc:267
Branch GetNextSibling() const
Get next sibling of this branch.
Definition: Branch.cc:284
Status ScanLongFile(const std::string &name)
Read longitudinal profile.
void GH4ParamFitFunctiondEdxGH(int &, double *, double &f, double *par, int)
boost::shared_ptr< const CoordinateTransformer > CoordinateSystemPtr
Shared pointer for coordinate systems.
char * exists
Definition: XbArray.cc:12
Class representing a document branch.
Definition: Branch.h:107
void MakeLongitudinalProfile(const utl::TabulatedFunction &lp, const ProfileType type=eCharged)
Make the longitudinal charge profile of the shower.
Status
Return code for seek operation.
Definition: IoCodes.h:24
bool HasGroundCherenkov(const utl::AttributeMap &am) const
void SetXZero(const double xZero, const double error)
void SetRunHeader(const bool first, const Corsika::RunHeader &header)
constexpr double s
Definition: AugerUnits.h:163
double GetHeight() const
Get the height.
Definition: UTMPoint.h:212
run header struct for Corsika files
Definition: CorsikaBlock.h:136
const double ns
Base for exceptions in the CORSIKA reader.
CorsikaShowerFileParticleIterator * GetCorsikaShowerFileParticleIterator() const
Get CorsikaShowerFileParticleIterator for testing.
const EndRow endr
void SetCalorimetricEnergy(const double energy)
Set the calorimetric energy of the shower.
const int tab
Definition: SdInspector.cc:35
virtual double Eval(const double depth) const =0
bool IsEventTrailer() const
Definition: CorsikaBlock.h:424
void SetC(const double c, const double error)
constexpr double degree
void SetElectromagneticEnergy(const double energy)
Set the electromagnetic energy of the shower.
const double km
constexpr double g
Definition: AugerUnits.h:200
double CorsikaAzimuthToAuger(const double corsikaAzimuth, const double magneticFieldDeclination)
Returns the azimuth rotated from Corisika&#39;s system to Auger standard.
utl::ShowerParticleList & GetGroundParticles()
Get particle list Proxy.
class to format data in tabular form
#define WARNING(message)
Macro for logging warning messages.
Definition: ErrorLogger.h:163
void GetData(bool &b) const
Overloads of the GetData member template function.
Definition: Branch.cc:644
std::string GetName() const
function to get the Branch name
Definition: Branch.cc:374
void SetChiSquare(const double chi, const unsigned int ndof)
void MakeGroundCherenkov(const utl::AttributeMap &am)
const string file
bool GetNextBlockUnthinned(BlockUnthinned &block)
Read one block and advance.
void SeekTo(const PositionType position, const bool reset=false)
Seek to a given block, the next block will be thePosition.
Implementation of the VShowerFileParticleIterator for an Corsika generated shower file...
if(dataRoot)
Definition: XXMLManager.h:1003
void SetNMax(const double nMax, const double error, const bool isEnergyDeposit=false)
bool HasdEdX() const
Check initialization of the energy deposit.
void SetA(const double a, const double error)
PositionType GetNextPosition() const
Number of the block read by the next call to GetNextBlock.
constexpr double kSpeedOfLight
void Open(const std::string &fileName, const Mode mode=eRead, utl::Branch *const b=nullptr) override
constexpr double GeV
Definition: AugerUnits.h:187
const double low[npar]
Definition: UnivRec.h:77
Status ReadSteerFile(evt::Event &event)
Read steering file.
const DeleteRow delr
bool Open(const std::string &name, const bool noException=false)
const double hAtmBoundary
bool Contains(const string &str, const string &what)
vector< double > fSlantDepth
int CorsikaToPDG(const int corsikaCode)
converters from CORSIKA to PDG particle codes
bool HasGHParameters() const
Check initialization of the Gaisser-Hillas parameters.
const EventHeader & AsEventHeader() const
Definition: CorsikaBlock.h:430
constexpr double cm
Definition: AugerUnits.h:117
double EnergyDeposit(const double age, const double enCut)
Parametrization for the average energy deposit per particle.
bool IsRunTrailer() const
Definition: CorsikaBlock.h:422
Profile gProfile
constexpr double micro
Definition: AugerUnits.h:65
utl::CoordinateSystemPtr Get(const std::string &id)
Get a well-known Coordinate System.
Branch GetFirstChild() const
Get first child of this Branch.
Definition: Branch.cc:98
Status FindEvent(const unsigned int eventId) override
seek Event id set cursor there
void SetEventHeader(const bool first, const unsigned int eventsSoFar, const Corsika::EventHeader &eh)
vector< double > fEnergyDeposit
std::vector< Corsika::RawFile::PositionType > PositionVector
#define ERROR(message)
Macro for logging error messages.
Definition: ErrorLogger.h:165
double GetDensityVsHeight(const double height) const
Return the air density as a function of height, according to the parameters stored in the file...
constexpr double m
Definition: AugerUnits.h:121
const HLine hline('-')
const EndColumn endc
Gaisser-Hillas with 6 parameters (CORSIKA)
double GetSlantDepthCurved(const double hObs, const double theta, const double h) const
Return slant atmospheric depth as a function of height, according to the parameters stored in the fil...
const double kEarthRadius
bool HasLongitudinalProfile(const ProfileType type=eCharged) const
Check initialization of the longitudinal profile.
unsigned long int PositionType
Class to hold the standard parameters used to specify an atmospheric profile.
static ObjectPtrType Create(const IdentifierType &id)
Create an object (0-argument constructor)
event trailer struct for Corsika files
Definition: CorsikaBlock.h:300
constexpr double cm2
Definition: AugerUnits.h:118
vector< string > Split(const string &str, const char *const separators)
Definition: String.cc:13

, generated on Tue Sep 26 2023.