SdReconstruction/LDFFinderKG/LDFFinder.cc
Go to the documentation of this file.
1 #include <evt/Event.h>
2 #include <evt/ShowerRecData.h>
3 #include <evt/ShowerSRecData.h>
4 #include <evt/ShowerFRecData.h>
5 #include <evt/ShowerSimData.h>
6 
7 #include <sevt/SEvent.h>
8 #include <sevt/StationRecData.h>
9 #include <sevt/EventTrigger.h>
10 #include <sevt/Station.h>
11 #include <sevt/StationConstants.h>
12 
13 #include <fevt/FEvent.h>
14 #include <fevt/Header.h>
15 #include <fevt/Eye.h>
16 #include <fevt/Telescope.h>
17 #include <fevt/EyeHeader.h>
18 #include <fevt/EyeRecData.h>
19 
20 #include <det/Detector.h>
21 
22 #include <sdet/SDetector.h>
23 #include <sdet/SDetectorConstants.h>
24 #include <sdet/Station.h>
25 #include <sdet/STimeVariance.h>
26 
27 #include <fdet/FDetector.h>
28 #include <fdet/Eye.h>
29 #include <fdet/Telescope.h>
30 
31 #include <fwk/LocalCoordinateSystem.h>
32 #include <fwk/CentralConfig.h>
33 #include <fwk/RunController.h>
34 
35 #include <utl/CoordinateSystem.h>
36 #include <utl/PhysicalConstants.h>
37 #include <utl/ErrorLogger.h>
38 #include <utl/TabulatedFunctionErrors.h>
39 #include <utl/TimeStamp.h>
40 #include <utl/AxialVector.h>
41 #include <utl/Math.h>
42 #include <utl/Reader.h>
43 #include <utl/Accumulator.h>
44 #include <utl/NumericalErrorPropagator.h>
45 #include <utl/String.h>
46 #include <utl/Is.h>
47 
48 #include <Minuit2/MnMinimize.h>
49 #include <Minuit2/MnHesse.h>
50 #include <Minuit2/FCNBase.h>
51 #include <Minuit2/FunctionMinimum.h>
52 #include <Minuit2/MnUserParameters.h>
53 #include <Minuit2/MnUserCovariance.h>
54 #include <Minuit2/MnPrint.h>
55 
56 #include "LDFFinder.h"
57 #include "ROptFitter.h"
58 
59 #include <boost/range/iterator_range.hpp>
60 #include <cmath>
61 
62 using namespace LDFFinderKG;
63 using namespace sevt;
64 using namespace fevt;
65 using namespace evt;
66 using namespace fwk;
67 using namespace std;
68 using namespace utl;
69 
70 using CLHEP::HepRotation;
71 
72 
73 #define OUT(_x_) if ((_x_) <= fInfoLevel) cerr
74 
75 
76 namespace LDFFinderKG {
77 
78  // local helpers
79 
80  template<typename T>
81  inline
82  bool
83  HasNaN(const T& result)
84  {
85  const auto npar = result.fPar.size();
86  for (size_t i = 0; i < npar; ++i) {
87  if (std::isnan(result.fPar[i]))
88  return true;
89  for (size_t j = i; j < npar; ++j)
90  if (std::isnan(result.fCov(i, j)))
91  return true;
92  }
93  return false;
94  }
95 
96 
97  inline
98  string
100  {
101  ostringstream label;
102  const int refdist = round(config.fLDF.GetReferenceDistance());
103  label << "S" << refdist/meter;
104  if (refdist < 1000*meter)
105  label << ' ';
106  return label.str();
107  }
108 
109 
110  inline
111  const char*
112  GetShapeLabel(const int iShape)
113  {
114  static const char* const labels[] = { "beta", "gamma", "mu", "tau" };
115  return Is(iShape).InRange(0, 3) ? labels[iShape] : "UNKNOWN COMPONENT!!!";
116  }
117 
118 
120  public:
121  CoreShiftPropagator(const double coreX,
122  const double coreY,
123  const vector<double>& axisParameters) :
124  fCore(coreX, coreY, 0, gBaryCS),
125  fAxisParameters(axisParameters)
126  { }
127 
128  void
129  Transform(vector<double>& output, const vector<double>& input)
130  const
131  {
132  const double& u = fAxisParameters[0];
133  const double& v = fAxisParameters[1];
134  const double& rCore = fAxisParameters[2];
135  const double& ctCore = fAxisParameters[3];
136  const double& newCoreX = input[0];
137  const double& newCoreY = input[1];
138 
139  const double w = sqrt(1 - u*u - v*v);
140 
141  const Point origin = fCore + rCore*Vector(u, v, w, gBaryCS);
142  const Point newCore = Point(newCoreX, newCoreY, 0, gBaryCS);
143  const Vector newAxis = origin - newCore;
144  const double newRCore = newAxis.GetMag();
145 
146  if (output.size() != 4)
147  output = vector<double>(4);
148 
149  output[LDFFinder::eU] = newAxis.GetX(gBaryCS)/newRCore;
150  output[LDFFinder::eV] = newAxis.GetY(gBaryCS)/newRCore;
151  output[LDFFinder::eRCore] = newRCore;
152  output[LDFFinder::eCTCore] = ctCore + newRCore - rCore;
153  }
154 
155  protected:
156  const Point fCore;
157  const vector<double> fAxisParameters;
158  };
159 
160 
161  inline
162  double
163  GetTimeVariance(const LDFFitConfig& fconf, const sdet::STimeVariance& timeVariance,
164  const double signal, const StationRecData& sRec,
165  const double cosTheta, const double rho)
166  {
167  double var = timeVariance.GetTimeSigma2(signal, sRec.GetT50(), cosTheta, rho);
169  var += sRec.GetGPSTimeVariance();
170  return var;
171  }
172 
173 }
174 
175 
176 void
178  const
179 {
180  const ShowerSRecData& recShower = event.GetRecShower().GetSRecShower();
181 
182  OUT(eFinal)
183  << "***** Final reconstructed shower data *****\n"
184  " LDF stage = " << recShower.GetLDFRecStage() << "\n"
185  " " << GetShowerSizeLabel(fLDFFitConfig) << " = "
186  << recShower.GetShowerSize() << " +/- " << recShower.GetShowerSizeError() << " +/- "
187  << recShower.GetShowerSizeSystematics() << " (sys.) VEM\n"
188  " core = ("
189  << String::Make(recShower.GetCorePosition(), det::Detector::GetInstance().GetSiteCoordinateSystem(), meter, ", ") << ") m (site)\n"
190  " = (" << String::Make(recShower.GetCorePosition(), gBaryCS, meter, ", ") << ") +/- "
191  "(" << String::Make(recShower.GetCoreError(), gBaryCS, meter, ", ") << ") m (bary)\n"
192  " x-y correlation = " << recShower.GetCorrelationXY() << "\n"
193  " beta = " << recShower.GetBeta() << " +/- " << recShower.GetBetaError() << "\n"
194  " gamma = " << recShower.GetGamma() << " +/- " << recShower.GetGammaError() << "\n"
195  " energy = " << recShower.GetEnergy()/EeV << " +/- " << recShower.GetEnergyError()/EeV << " EeV\n";
196  const double curv = recShower.GetCurvature();
197  if (curv) {
198  const Vector& axis = recShower.GetAxis();
199  const double rc = 1 / curv;
200  OUT(eFinal)
201  << " axis = (" << String::Make(axis, gBaryCS, 1, ", ") << ") (bary)\n"
202  " theta = " << axis.GetTheta(gBaryCS)/degree << " +/- " << recShower.GetThetaError()/degree << " deg (bary)\n"
203  " phi = " << axis.GetPhi(gBaryCS)/degree << " +/- " << recShower.GetPhiError()/degree << " deg\n"
204  " theta-phi correlation = " << recShower.GetCorrelationThetaPhi() << "\n"
205  " Rc = " << rc/km << " +- " << recShower.GetCurvatureError()*rc*rc/km << " km\n"
206  " time chi2 = " << recShower.GetAngleChi2() << " / " << recShower.GetAngleNdof() << "\n"
207  " time residual = " << recShower.GetTimeResidualMean()/nanosecond << " +/- "
208  << recShower.GetTimeResidualSpread()/nanosecond << " ns\n";
209  }
210  const double rOpt = recShower.GetLDFOptimumDistance();
211  if (rOpt)
212  OUT(eFinal)
213  << " Ropt = " << rOpt/meter << " m\n";
214 
215  ++RunController::GetInstance().GetRunData().GetNamedCounters()[
216  "LDFFinder/" + (boost::format("%|.5|") % recShower.GetLDFRecStage()).str()
217  ];
218 }
219 
220 
223 {
224  auto topB = CentralConfig::GetInstance()->GetTopBranch("LDFFinderKG");
225 
226  topB.GetChild("infoLevel").GetData(fInfoLevel);
227 
228  {
229  const auto arrayType = topB.GetChild("arrayType").Get<string>();
230  if (arrayType == "SD1500")
231  fArrayType = sdet::Array::e1500;
232  else if (arrayType == "SD750")
233  fArrayType = sdet::Array::e750;
234  else if (arrayType == "SD433")
235  fArrayType = sdet::Array::e433;
236  else {
237  ERROR("Unknown array type");
238  return eFailure;
239  }
240  }
241 
242  {
243  const auto sigVariance = topB.GetChild("signalVariance").Get<string>();
244  if (sigVariance == "eGAP2012_012")
245  fLDFFitConfig.fSignalVarianceModel = wcd::eGAP2012_012;
246  else if (sigVariance == "eGAP2014_035")
247  fLDFFitConfig.fSignalVarianceModel = wcd::eGAP2014_035;
248  else {
249  ERROR("Unknown signal variance");
250  return eFailure;
251  }
252 
253  fLDFFitConfig.fUseAdditionalGPSTimeVariance =
254  topB.GetChild("useAdditionalGPSTimeVariance").Get<bool>();
255 
256  // setup LDF
257  const auto ldfType = topB.GetChild("ldfType").Get<string>();
258 
259  const double ldfReferenceDistance = topB.GetChild("ldfReferenceDistance").Get<double>();
260 
261  const auto ldfParameters = topB.GetChild("ldfParameters").Get<vector<double>>();
262 
263  const auto ldfUncertaintyParameters =
264  topB.GetChild("ldfUncertaintyParameters").Get<vector<double>>();
265 
266  fLDFFitConfig.fLDF = LDF(ldfType, ldfReferenceDistance, ldfParameters, ldfUncertaintyParameters);
267  fLDFFitConfig.fLDFShapeTreatment.resize(fLDFFitConfig.fLDF.GetNShapeParameters(), eModeled);
268  }
269 
270  {
271  // setup energy calibration
272  auto energyBranch = topB.GetChild("energyCalibration");
273 
274  fEnergyConversion.fCicReferenceAngle = energyBranch.GetChild("referenceAngle").Get<double>();
275  fEnergyConversion.fCicReferenceS38 = energyBranch.GetChild("referenceS38").Get<double>();
276  energyBranch.GetChild("s38Range").GetData(fEnergyConversion.fCicS38Range);
277  const auto& r = fEnergyConversion.fCicS38Range;
278  if (r.first <= 0 || r.second <= 0 || r.first > r.second) {
279  ERROR("Invalid <s38Range>!");
280  return eFailure;
281  }
282  fEnergyConversion.SetCICParameters(
283  energyBranch.GetChild("attenuationPar1").Get<vector<double>>(),
284  energyBranch.GetChild("attenuationPar2").Get<vector<double>>(),
285  energyBranch.GetChild("attenuationPar3").Get<vector<double>>()
286  );
287 
288  fEnergyConversion.fEnergyConstant = energyBranch.GetChild("energyS38Const").Get<double>();
289  fEnergyConversion.fEnergySlope = energyBranch.GetChild("energyS38Slope").Get<double>();
290  }
291 
292  auto silentsBranch = topB.GetChild("silentStations");
293  silentsBranch.GetChild("maxLDFValue").GetData(fSilentsVars.fMaxLDFValue);
294  silentsBranch.GetChild("maxDistance").GetData(fSilentsVars.fMaxDistance);
295  silentsBranch.GetChild("minNumberOfStations").GetData(fSilentsVars.fMinNumberOfStations);
296 
297  {
298  const auto coreType = topB.GetChild("coreType").Get<string>();
299  if (coreType == "MC")
300  fCoreType = eMC;
301  else if (coreType == "Hybrid")
302  fCoreType = eHybrid;
303  else if (coreType == "Fit")
304  fCoreType = eFit;
305  else {
306  ERROR("Unknown core type");
307  return eFailure;
308  }
309  }
310 
311  fLDFFitConfig.fFixCore = (fCoreType != eFit);
312 
313  topB.GetChild("fixedAxis").GetData(fFixedAxis);
314 
315  topB.GetChild("globalFit").GetData(fGlobalFit);
316 
317  topB.GetChild("maxTheta").GetData(fMaxTheta);
318 
319  const auto minimizationMethod = topB.GetChild("minimizationMethod").Get<string>();
320  fUseMaxLikelihood = (minimizationMethod == "MaxLike");
321 
322  topB.GetChild("maxChi2").GetData(fMaxChi2);
323 
324  topB.GetChild("useSilentStations").GetData(fLDFFitConfig.fUseSilentStations);
325 
326  topB.GetChild("useSilentStationsFromOtherGrids").GetData(fUseSilentStationsFromOtherGrids);
327 
328  topB.GetChild("silentMaxRadius").GetData(fLDFFitConfig.fSilentMaxRadius);
329 
330  topB.GetChild("silentRadiusTransition").GetData(fLDFFitConfig.fSilentRadiusTransition);
331 
332  topB.GetChild("silentSignalThreshold").GetData(fLDFFitConfig.fSilentSignalThreshold);
333 
334  topB.GetChild("useSaturationRecovery").GetData(fLDFFitConfig.fUseSaturationRecovery);
335 
336  topB.GetChild("recoveryThreshold").GetData(fLDFFitConfig.fRecoveryThreshold);
337 
338  topB.GetChild("maxBaryToCoreDistance").GetData(fMaxBaryToCoreDistance);
339  if (fMaxBaryToCoreDistance <= 0) {
340  ERROR("<maxBaryToCoreDistance> must be > 0");
341  return eFailure;
342  }
343 
344  topB.GetChild("RoptN").GetData(fRoptN);
345  if (fRoptN < 2)
346  fRoptN = 0;
347 
348  auto stage0B = topB.GetChild("stage0");
349  stage0B.GetChild("minNumberOfStations").GetData(fStage0.fMinNumberOfStations);
350  stage0B.GetChild("useCorePosition").GetData(fStage0.fUseCorePosition);
351 
352  auto stage1B = topB.GetChild("stage1");
353  stage1B.GetChild("minNumberOfStations").GetData(fStage1.fMinNumberOfStations);
354 
355  auto stage2B = topB.GetChild("stage2");
356  stage2B.GetChild("minNumberRelaxBeta").GetData(fStage2.fMinNumberRelaxBeta);
357  stage2B.GetChild("fixBetaRange").GetData(fStage2.fFixBetaRange);
358  stage2B.GetChild("fixBetaLeverArmRange").GetData(fStage2.fFixBetaLeverArmRange);
359  stage2B.GetChild("minNumberRelaxGamma").GetData(fStage2.fMinNumberRelaxGamma);
360  stage2B.GetChild("fixGammaRange").GetData(fStage2.fFixGammaRange);
361  stage2B.GetChild("fixGammaLeverArmRange").GetData(fStage2.fFixGammaLeverArmRange);
362 
363  auto stage3B = topB.GetChild("stage3");
364  stage3B.GetChild("minNumberOfStations").GetData(fStage3.fMinNumberOfStations);
365  stage3B.GetChild("minNumberForFullCurvatureFit").GetData(fStage3.fMinNumberForFullCurvatureFit);
366  stage3B.GetChild("maxAxisAngleDifference").GetData(fStage3.fMaxAxisAngleDifference);
367  stage3B.GetChild("outliersRejection").GetData(fStage3.fOutliersRejection);
368 
369  {
370  ostringstream info;
371  const auto& p = fEnergyConversion.fCicParameters;
372  info << "\n"
373  " LDF type: " << fLDFFitConfig.fLDF.GetType() << "\n"
374  " Shower size estimator: " << GetShowerSizeLabel(fLDFFitConfig) << "\n"
375  " Minimization method: " << (fUseMaxLikelihood ? "Maximum-Likelihood" : "Chi2") << "\n"
376  " S38 conversion: " << "S38 = " << GetShowerSizeLabel(fLDFFitConfig) << " / CIC(x), "
377  " CIC(x) = ";
378  for (unsigned int ix = 0, nx = Length(p)+1; ix < nx; ++ix) {
379  if (!ix)
380  info << 1;
381  else {
382  info << " + (";
383  for (unsigned int iy = 0; iy < 3; ++iy) {
384  if (!iy)
385  info << p[ix-1][iy];
386  else {
387  info << " + " << p[ix-1][iy] << "*y";
388  if (iy > 1)
389  info << '^' << iy;
390  }
391  }
392  info << ") * x";
393  if (ix > 1)
394  info << '^' << ix;
395  }
396  }
397  info << " where "
398  "x = -(sin^2(theta) - sin^2(38deg)), "
399  "y = lg(s / S38_ref), "
400  "s = min(max(S38, " << fEnergyConversion.fCicS38Range.first << "), "
401  << fEnergyConversion.fCicS38Range.second << "), "
402  "S38_ref = " << fEnergyConversion.fCicReferenceS38 << " VEM\n"
403  " energy conversion: E = " << fEnergyConversion.fEnergyConstant << " eV * "
404  "pow(S38 / VEM, " << fEnergyConversion.fEnergySlope << ")\n"
405  " Ropt fitting: " << (fRoptN < 2 ? "off" : "on");
406  INFO(info);
407  }
408 
409  return eSuccess;
410 }
411 
412 
415 {
416  // nothing to do if there is no SEvent
417  if (!(event.HasSEvent() && event.HasRecShower() &&
418  event.GetRecShower().HasSRecShower()))
419  return eContinueLoop;
420 
421  const auto& sEvent = event.GetSEvent();
422 
423  auto& showerRec = event.GetRecShower().GetSRecShower();
424 
425  int nStations = 0;
426  {
427  int nSaturated = 0;
428  int nSilent = 0;
429  for (const auto& station : sEvent.StationsRange())
430  if (station.IsCandidate()) {
431  ++nStations;
432  if (station.IsLowGainSaturation())
433  ++nSaturated;
434  } else if (station.IsSilent())
435  ++nSilent;
436 
437  OUT(eIntermediate)
438  << "# candidate stations = " << nStations
439  << " (" << nSaturated << " saturated), " << nSilent << " silent\n";
440  }
441 
442  // not worth it
443  if (nStations < fStage0.fMinNumberOfStations) {
444  OUT(eFinal)
445  << "Not enough stations.\n";
446  return eContinueLoop;
447  }
448 
449  // barycenter is done by the SdPlaneFitOG module
450  fBarycenter = showerRec.GetBarycenter();
451  fBaryTime = showerRec.GetBarytime();
452  gBaryCS = showerRec.GetBarycenterCoordinateSystem();
453 
454  if (!fBaryTime)
455  throw utl::NonExistentComponentException("needs barycenter!");
456 
457  LDFFitConfig config = fLDFFitConfig;
458  LDFFitResult best(config);
459  CurvFitResult cbest;
460 
461  // setup initial values
462  {
463  Point core;
464  TimeStamp coreTime;
465  Vector showerAxis;
466 
467  if (!FixCore(core, coreTime, showerAxis, event))
468  return eContinueLoop;
469 
470  if (fCoreType != eFit) {
471  showerRec.SetAxis(showerAxis);
472  showerRec.SetCorePosition(core);
473  showerRec.SetCoreTime(coreTime, TimeInterval(0));
474  showerRec.SetAngleErrors(showerAxis.GetCoordinates(gBaryCS), Vector::Triple(0,0,0));
475  showerRec.SetCurvature(0, 0);
476  }
477 
478  const double theta = showerAxis.GetTheta(gBaryCS);
479  const double phi = showerAxis.GetPhi(gBaryCS);
480  if (theta > fMaxTheta) {
481  OUT(eFinal)
482  << " theta = " << theta/degree << " is > than limit "
483  << fMaxTheta/degree << " [deg], skipping...\n";
484  return eContinueLoop;
485  }
486 
487  const auto& siteCS = det::Detector::GetInstance().GetSiteCoordinateSystem();
488  const auto& eventTime = sEvent.GetTrigger().GetTime();
489 
490  OUT(eIntermediate)
491  << " barycenter = (" << String::Make(fBarycenter, siteCS, meter, ", ") << ") m (site)\n"
492  " bary time = " << (fBaryTime - eventTime).GetInterval()/nanosecond << " ns (to event time)\n"
493  " axis = (" << String::Make(showerAxis, siteCS, 1, ", ") << ") (site)\n"
494  " = (" << String::Make(showerAxis, gBaryCS, 1, ", ") << ") (bary)\n"
495  " theta = " << theta/degree << " deg (bary)\n"
496  " phi = " << phi/degree << " deg (bary)\n";
497 
498  OUT(eObscure)
499  << " local/site zenith angle diff = "
500  << Angle(Vector(0,0,1, gBaryCS), Vector(0,0,1, siteCS))/degree << " deg\n";
501 
502  best.fPar[eCoreX] = core.GetX(gBaryCS);
503  best.fPar[eCoreY] = core.GetY(gBaryCS);
504 
505  cbest.fPar[eU] = sin(theta) * cos(phi);
506  cbest.fPar[eV] = sin(theta) * sin(phi);
507  cbest.fPar[eRCore] = 0;
508  cbest.fPar[eCTCore] = 0;
509  }
510 
511  auto stationFitData = MakeStationFitData(sEvent);
512  bool hasSaturatedStation = false;
513  for (const auto& station : stationFitData) {
514  if (station.fIsSaturated) {
515  hasSaturatedStation = true;
516  break;
517  }
518  }
519 
520  // --- Stage: fit shower size, LDF shape & core fixed --------------------
521 
522  best.fRecStage = ShowerSRecData::kLDFEstimate;
523  OUT(eIntermediate)
524  << "Stage " << best.fRecStage << ": fit " << GetShowerSizeLabel(config)
525  << " - LDF shape & core fixed\n";
526 
527  FitLDFSimplified(best, config, cbest.GetAxis(), stationFitData);
528 
529  if (nStations < fStage1.fMinNumberOfStations) {
530  SetRecData(event, best, cbest);
531  OutputResults(event);
532  return eSuccess;
533  }
534 
535  // try with silents and without if first fit fails
536  for (int useSilents = fLDFFitConfig.fUseSilentStations; useSilents >= 0; --useSilents) {
537 
538  config.fUseSilentStations = bool(useSilents);
539 
540  const string stationType = useSilents ? "candidate+silents" : "candidates";
541 
542  // --- Stage: fit shower size & core, LDF shape fixed --------------
543  {
544  LDFFitResult result = best;
545 
546  for (size_t i = 0; i < config.fLDF.GetNShapeParameters(); ++i)
547  config.fLDFShapeTreatment[i] = eModeled;
548 
549  OUT(eIntermediate)
550  << "Stage " << result.fRecStage << ": fit "
551  << GetShowerSizeLabel(config) << ", core"
552  << " - LDF shape fixed [" << stationType << "]\n";
553 
554  if (useSilents)
555  fBadSilents = CleanSilentStation(result, config, cbest.GetAxis(), stationFitData);
556 
557  if (!FitLDF(result, config, cbest.GetAxis(), stationFitData))
558  continue;
559 
560  result.fRecStage = useSilents ? ShowerSRecData::kLDFSilents : ShowerSRecData::kLDF;
561  best = result;
562 
563  } // stage
564 
565  // --- Stage: shower size, core, shape fitting (optional) ----------
566  {
567  LDFFitResult result = best;
568 
569  config.fLDFShapeTreatment[0] =
570  (nStations < fStage2.fMinNumberRelaxBeta ||
571  FixBeta(result, cbest.GetAxis(), stationFitData)) ? eModeled : eFitted;
572  config.fLDFShapeTreatment[1] =
573  (nStations < fStage2.fMinNumberRelaxGamma ||
574  FixGamma(result, cbest.GetAxis(), stationFitData)) ? eModeled : eFitted;
575 
576  if (config.fLDFShapeTreatment[0] == eFitted ||
577  config.fLDFShapeTreatment[1] == eFitted) {
578  OUT(eIntermediate)
579  << "Stage " << result.fRecStage << ": fit " << stationType
580  << " for shower size, core"
581  << (config.fLDFShapeTreatment[0] == eFitted ? ", beta" : "")
582  << (config.fLDFShapeTreatment[1] == eFitted ? ", gamma\n" : "\n");
583 
584  if (FitLDF(result, config, cbest.GetAxis(), stationFitData)) {
585  const bool fixBeta = FixBeta(result, cbest.GetAxis(), stationFitData);
586  const bool fixGamma = FixGamma(result, cbest.GetAxis(), stationFitData);
587  if ((config.fLDFShapeTreatment[0] == eModeled) == fixBeta &&
588  (config.fLDFShapeTreatment[1] == eModeled) == fixGamma) {
589  result.fRecStage = (useSilents ? ShowerSRecData::kLDFSilents : ShowerSRecData::kLDF) +
590  (config.fLDFShapeTreatment[0] == eFitted) * ShowerSRecData::kLDFBeta +
591  (config.fLDFShapeTreatment[1] == eFitted) * ShowerSRecData::kLDFGamma +
592  (hasSaturatedStation && !config.fUseSaturationRecovery) * ShowerSRecData::kLDFLowerLimit;
593  best = result;
594  break;
595  } else {
596  if (fixBeta)
597  config.fLDFShapeTreatment[0] = eModeled;
598  if (fixGamma)
599  config.fLDFShapeTreatment[1] = eModeled;
600 
601  if (FitLDF(result, config, cbest.GetAxis(), stationFitData)) {
602  result.fRecStage = (useSilents ? ShowerSRecData::kLDFSilents : ShowerSRecData::kLDF) +
603  (config.fLDFShapeTreatment[0] == eFitted) * ShowerSRecData::kLDFBeta +
604  (config.fLDFShapeTreatment[1] == eFitted) * ShowerSRecData::kLDFGamma +
605  (hasSaturatedStation && !config.fUseSaturationRecovery) * ShowerSRecData::kLDFLowerLimit;
606  best = result;
607  break;
608  }
609  }
610  }
611  }
612 
613  }
614 
615  // if we arrive here: there was no shape fit
616  // but the initial fit with fixed shape was ok
617  break;
618  } // fit LDF with/without silents
619 
620  // --- Stage: shower size, core, beta (gamma), curvature fitting -----
621  if (nStations < fStage3.fMinNumberOfStations) {
622  SetRecData(event, best, cbest);
623  OutputResults(event);
624  return eSuccess;
625  }
626 
627  {
628  const CurvFitResult cinit = cbest;
629  CurvFitResult cres = cbest;
630  const double rCoreModel =
631  ParameterizedRc(cres.GetAxis().GetCosTheta(gBaryCS),
632  best.fPar[eShowerSize]);
633 
634  OUT(eIntermediate)
635  << "Fit for axis with fixed curvature radius = " << rCoreModel/kilometer << " km\n";
636 
637  fFixedCurvature = true;
638  cres.fPar[eRCore] = rCoreModel;
639  if (FitCurvature(cres, best.GetCore(), stationFitData)) {
640  cbest = cres;
641 
642  if (nStations >= fStage3.fMinNumberForFullCurvatureFit) {
643  OUT(eIntermediate)
644  << "Fit for" << (!fFixedAxis ? " axis and ":" ") << "shower-front curvature.\n";
645 
646  fFixedCurvature = false;
647  cres.fPar[eRCore] = rCoreModel;
648  if (FitCurvature(cres, best.GetCore(), stationFitData))
649  cbest = cres;
650  else
651  OUT(eIntermediate) << " Curvature fit failed.\n";
652  }
653  }
654 
655  if (fStage3.fOutliersRejection &&
656  nStations > fStage3.fMinNumberForFullCurvatureFit) {
657 
658  OUT(eIntermediate)
659  << " Performing outlier search and rejection\n";
660 
661  for (const auto& station1 : stationFitData) {
662 
663  if (!station1.fSignal)
664  continue;
665 
666  vector<StationFitData> stationData;
667 
668  for (const auto& station2 : stationFitData) {
669  if (!station2.fSignal || station1.fId == station2.fId)
670  continue;
671  stationData.push_back(station2);
672  }
673 
674  cres.fPar[eRCore] = rCoreModel;
675  if (FitCurvature(cres, best.GetCore(), stationData)) {
676 
677  const double newChi2Probability = utl::Chi2Probability(cres.fChi2, cres.fNdof);
678  const double prevChi2Probability = utl::Chi2Probability(cbest.fChi2, cbest.fNdof);
679 
680  if (prevChi2Probability < 1e-100 ||
681  (newChi2Probability > 1e-100 && newChi2Probability/prevChi2Probability > 2)) {
682 
683  OUT(eIntermediate)
684  << " Removed one outlier:\n"
685  " previous chi2 probability = " << prevChi2Probability << "\n"
686  " previous RCore = " << cbest.fPar[eRCore]/kilometer << " +/- " << cbest.fCov.Std(eRCore)/kilometer << " km\n"
687  " previous CTCore = " << cbest.fPar[eCTCore]/meter << " +/- " << cbest.fCov.Std(eCTCore)/meter << " m\n"
688  " new chi2 probability = " << newChi2Probability << "\n"
689  " new RCore = " << cres.fPar[eRCore]/kilometer << " +/- " << cres.fCov.Std(eRCore)/kilometer << " km\n"
690  " new CTCore = " << cres.fPar[eCTCore]/meter << " +/- " << cres.fCov.Std(eCTCore)/meter << " m\n";
691 
692  cbest = cres;
693  }
694  }
695  }
696  }
697 
698  const double openingAngle = Angle(cbest.GetAxis(), cinit.GetAxis());
699 
700  OUT(eIntermediate)
701  << " axis = (" << String::Make(cbest.GetAxis(), gBaryCS, meter, ", ") << ") m (bary)\n"
702  " axis diff = " << openingAngle/degree << " deg (to plain-front fit)\n"
703  " RCore = " << cbest.fPar[eRCore]/kilometer << " +/- " << cbest.fCov.Std(eRCore)/kilometer << " km\n"
704  " CTCore = " << cbest.fPar[eCTCore]/meter << " +/- " << cbest.fCov.Std(eCTCore)/meter << " m\n";
705 
706  if (openingAngle > fStage3.fMaxAxisAngleDifference || std::isnan(openingAngle)) {
707  OUT(eIntermediate)
708  << " Opening-angle axis difference is above the limit.\n";
709  cbest = cinit;
710  }
711 
712  const double newTheta = cbest.GetTheta();
713  if (newTheta > fMaxTheta) {
714  OUT(eFinal)
715  << " theta = " << newTheta/degree << " deg is > than limit " << fMaxTheta/degree << " deg, skipping...\n";
716  return eContinueLoop;
717  }
718  }
719 
720  if (cbest.fPar[eRCore] > 0) {
721  // redo LDF fit after the axis change
722 
723  const CurvFitResult cinit = cbest;
724 
725  LDFFitResult lres = best;
726 
727  const bool useSilents = config.fUseSilentStations;
728  const string stationType = (useSilents ? "candidate+silents" : "candidates");
729 
730  lres.fRecStage =
731  (useSilents ? ShowerSRecData::kLDFSilents : ShowerSRecData::kLDF) +
732  (config.fLDFShapeTreatment[0] == eFitted) * ShowerSRecData::kLDFBeta +
733  (config.fLDFShapeTreatment[1] == eFitted) * ShowerSRecData::kLDFGamma +
734  (hasSaturatedStation && !config.fUseSaturationRecovery) * ShowerSRecData::kLDFLowerLimit +
735  0.5;
736 
737  OUT(eIntermediate)
738  << "Redo Stage " << best.fRecStage << ": fit " << GetShowerSizeLabel(config) << ", core"
739  << (config.fLDFShapeTreatment[0] == eFitted ? ", beta" : "")
740  << (config.fLDFShapeTreatment[1] == eFitted ? ", gamma" : "")
741  << " [" << stationType << "]\n";
742 
743  if (FitLDF(lres, config, cbest.GetAxis(), stationFitData)) {
744  CoreShiftPropagator propagator(best.fPar[eCoreX], best.fPar[eCoreY], cbest.fPar);
745  const vector<double> input({lres.fPar[eCoreX], lres.fPar[eCoreY]});
746 
747  CovarianceMatrix inputCov(2);
748  inputCov[0] = lres.fCov[eCoreX];
749  inputCov[1] = lres.fCov[eCoreY];
750  inputCov(0, 1) = lres.fCov(eCoreX, eCoreY);
751 
752  CovarianceMatrix outputCov(4);
753  propagator(cbest.fPar, outputCov, input, inputCov);
754 
755  // if some axis properties were not fitted, do not propagate uncertainty
756  for (int i = 0; i < 4; ++i)
757  for (int j = i; j < 4; ++j)
758  if (cbest.fCov(i, j) > 0)
759  cbest.fCov(i, j) += outputCov(i, j);
760 
761  const double openingAngle = Angle(cbest.GetAxis(), cinit.GetAxis());
762 
763  // AS: Check again if new axis is reasonable after redone fit
764  // this fails in some rare cases (example: infill 201322403641)
765  // in this case an inclined event is wrongly reconstructed as a vertical event!
766  if (openingAngle > fStage3.fMaxAxisAngleDifference || std::isnan(openingAngle)) {
767  OUT(eIntermediate)
768  << " Opening-angle axis difference is above the limit.\n";
769  cbest = cinit;
770  } else
771  best = lres;
772 
773  if (fGlobalFit) {
774  lres.fRecStage =
775  ShowerSRecData::kLDFGlobalFit +
776  (config.fLDFShapeTreatment[0] == eFitted) * ShowerSRecData::kLDFBeta +
777  (config.fLDFShapeTreatment[1] == eFitted) * ShowerSRecData::kLDFGamma +
778  (hasSaturatedStation && !config.fUseSaturationRecovery) * ShowerSRecData::kLDFLowerLimit;
779 
780  OUT(eIntermediate)
781  << "Stage " << lres.fRecStage << ": global fit [" << stationType << "]\n";
782 
783  CurvFitResult cres = cbest;
784  if (FitGlobal(cres, lres, config, stationFitData)) {
785  cbest = cres;
786  best = lres;
787  }
788  }
789  } // if LDF fit fails, curvature fit is also rejected
790  }
791 
792  // calculation of Ropt, see GAP-2006-045
793  if (fRoptN >= 2) {
794  OUT(eIntermediate)
795  << "Ropt determination:\n";
796 
797  ROptFitter rOptFitter(config.fLDF, best.GetLDFShapeParameters(), fInfoLevel >= eMinuit);
798 
799  const double finalBeta = best.fPar[eShapeParameters + 0];
800  // beta spread parameterization from Talianna
801  const double betaFixSigma = config.fLDF.BetaUncertainty(best.fPar[eShowerSize]);
802 
803  const double startp = NormalCDF(-1);
804  const double stopp = NormalCDF(1);
805  const double deltap = (stopp - startp) / (fRoptN - 1);
806 
807  LDFFitConfig thisConfig = config;
808  for (size_t i = 0, n = config.fLDF.GetNShapeParameters(); i < n; ++i)
809  thisConfig.fLDFShapeTreatment[i] = eFixed;
810 
811  for (int i = 0; i < fRoptN; ++i) {
812  LDFFitResult result = best;
813  {
814  double& beta = result.fPar[eShapeParameters + 0];
815  const double p = startp + i*deltap;
816  beta = finalBeta + InverseNormalCDF(p, betaFixSigma);
817  }
818  FitLDF(result, thisConfig, cbest.GetAxis(), stationFitData);
819  const double beta = result.fPar[eShapeParameters + 0];
820  rOptFitter.PushBack(beta, result.fPar[eShowerSize]);
821  }
822 
823  // simple numerical derivative (finite difference)
824  const auto halfN = fRoptN / 2;
825  const double s1 = rOptFitter.GetShowerSizeVector()[halfN-1];
826  const double s2 = rOptFitter.GetShowerSizeVector()[halfN];
827  const double b1 = rOptFitter.GetBetaVector()[halfN-1];
828  const double b2 = rOptFitter.GetBetaVector()[halfN];
829  const double dS1000_dBeta = (s2 - s1) / (b2 - b1);
830 
831  auto& shRec = event.GetRecShower().GetSRecShower();
832  shRec.SetBetaSystematics(betaFixSigma);
833  shRec.SetShowerSizeSystematics(fabs(betaFixSigma * dS1000_dBeta));
834 
835  double rOpt = 0;
836  if (rOptFitter.GetResult(rOpt))
837  shRec.SetLDFOptimumDistance(rOpt);
838  }
839 
840  SetRecData(event, best, cbest);
841  OutputResults(event);
842 
843  return eSuccess;
844 }
845 
846 
847 bool
848 LDFFinder::FixCore(Point& core, TimeStamp& refTime, Vector& axis,
849  const evt::Event& event)
850  const
851 {
852  const Vector localNormal(0,0,1, gBaryCS);
853  Point coreReference;
854 
855  switch (fCoreType) {
856  case eFit:
857  {
858  core = fBarycenter;
859  refTime = fBaryTime;
860  const auto& previousAxis = event.GetRecShower().GetSRecShower().GetAxis();
861  if (!previousAxis) {
862  ERROR("No previous axis found.");
863  return false;
864  }
865  axis = previousAxis;
866  }
867  break;
868  case eMC:
869  {
870  if (!event.HasSimShower()) {
871  ERROR("The event is not a simulated shower.");
872  return false;
873  }
874  const auto& simShower = event.GetSimShower();
875  axis = -simShower.GetDirection();
876  coreReference = simShower.GetPosition();
877  refTime = simShower.GetTimeStamp();
878  }
879  break;
880  case eHybrid:
881  {
882  if (!event.HasFEvent()) {
883  ERROR("The event has no FD shower.");
884  return false;
885  }
886 
887  const auto& fEvent = event.GetFEvent();
888  // remember area of FD core error ellipse to choose best FD eye
889  double bestErrorEllipse = 0;
890  const evt::ShowerFRecData* showerFRec = nullptr;
891 
892  for (const auto& eye :
893  boost::make_iterator_range(fEvent.EyesBegin(ComponentSelector::eHasData),
894  fEvent.EyesEnd(ComponentSelector::eHasData))) {
895 
896  // skip eyes without reconstruction
897  if (!eye.HasRecData())
898  continue;
899 
900  const auto& eyeRec = eye.GetRecData();
901  if (eyeRec.GetSDPFitNDof() <= 0 || eyeRec.GetTimeFitNDof() <= 0)
902  continue;
903 
904  if (!eyeRec.HasFRecShower())
905  continue;
906  showerFRec = &eyeRec.GetFRecShower();
907 
908  const auto& dEye = det::Detector::GetInstance().GetFDetector().GetEye(eye);
909  const auto& eyeCS = dEye.GetEyeCoordinateSystem();
910  const auto eyePos = dEye.GetPosition();
911  const auto vecEyeCore = eyePos - showerFRec->GetCorePosition();
912 
913  const double rp = eyeRec.GetRp();
914  const double rpErr = eyeRec.GetRpError();
915  const double t0 = eyeRec.GetTZero();
916  const double chi0 = eyeRec.GetChiZero();
917  const double chi0Err = eyeRec.GetChiZeroError();
918  const double sdpPhiErr = eyeRec.GetSDPPhiError();
919  const double rhoChi0Rp = eyeRec.GetRpChi0Correlation();
920  const double distEyeCore = vecEyeCore.GetMag();
921 
922  // calculation of core error in SDP
923  // rp error projected on ground+
924  // chi0 error+ correlation of errors in rp and chi0
925  const double sinChi0 = sin(chi0);
926  const double errorInSDP = sqrt(
927  Sqr(rpErr / sinChi0) +
928  Sqr((chi0Err * distEyeCore) / (cos(chi0) * sinChi0)) +
929  rhoChi0Rp * rpErr * chi0Err
930  );
931 
932  // error due to SDP azimuth uncertainty
933  const double errorPerpSDP = distEyeCore * sin(sdpPhiErr);
934 
935  // area of core error ellipse of this FD event
936  const double coreErrorEllipse = errorInSDP * errorPerpSDP * kPi;
937 
938  // if error ellipse set AND error ellipse larger than previous eye(s)
939  // don't update core with actual eye
940  if (bestErrorEllipse && bestErrorEllipse < coreErrorEllipse)
941  continue;
942 
943  bestErrorEllipse = coreErrorEllipse;
944  axis = showerFRec->GetAxis();
945  coreReference = showerFRec->GetCorePosition();
946 
947  const Vector vertical(0,0,1, eyeCS);
948  const double alpha = 0.5*kPi - Angle(vertical, vecEyeCore);
949  const double cosBeta = CosAngle(axis, vecEyeCore);
950  // 1e5ns have to be subtracted to jump from the end to the
951  // start of the pixel trace (don't even think of removing the
952  // comment here)
953  refTime = eye.GetHeader().GetTimeStamp() +
954  TimeInterval(-1e5*ns + t0 + (rp*tan(alpha) + distEyeCore*cosBeta)/kSpeedOfLight);
955  } // loop over eyes
956 
957  if (!showerFRec)
958  return false;
959  }
960  break;
961  }
962 
963  if (fCoreType != eFit) {
964  // shift core along shower axis into local (barycenter) ground plane
965  core = coreReference -
966  (localNormal * (coreReference - fBarycenter)) / (localNormal*axis) * axis;
967 
968  const double d = (core - fBarycenter).GetMag();
969  if (d > fMaxBaryToCoreDistance) {
970  ostringstream err;
971  err << "Bary-centric distance to core " << d/kilometer << " km is above the limit.";
972  ERROR(err);
973  return false;
974  }
975 
976  OUT(eIntermediate)
977  << " using fixed core = (" << String::Make(core, gBaryCS, meter, ", ") << ") m (bary)\n";
978  } else
979  OUT(eIntermediate)
980  << " using barycenter as initial core\n";
981 
982  return true;
983 }
984 
985 
986 // From Pierre Billoir Thoughts....
987 // - if there are enough stations (especially at distances around 1000 m), a fit
988 // with a variable LDF slope is attempted (B is set as adjustable in (3)); the
989 // conditions are:
990 // * at least 5 stations, with one of the following requirements:
991 // * at least 2 within (400, 1600), with a difference at least 900 in between
992 // * at least 3 within (400, 1600), with a maximal difference at least 800
993 // * at least 4 within (400, 1600), with a maximal difference at least 700
994 //
995 // New version much stricter
996 //
997 // Adjustments made for the SD750 and SD433
998 //
999 bool
1001  const Vector& showerAxis,
1002  const vector<StationFitData>& data)
1003  const
1004 {
1005  size_t nStations = 0;
1006  for (const auto& station : data) {
1007  if (!station.fSignal || (station.fIsSaturated && !station.fRecoveredSignalErr))
1008  continue;
1009  ++nStations;
1010  }
1011 
1012  if (nStations < 5) {
1013  OUT(eIntermediate)
1014  << "FixBeta = 1: nStations = " << nStations << '\n';
1015  return true;
1016  }
1017 
1018  int nStationsInRange = 0;
1019  double maxDistance = 0;
1020 
1021  const auto core = result.GetCore();
1022 
1023  for (const auto& station : data) {
1024 
1025  if (!station.fSignal || (station.fIsSaturated && !station.fRecoveredSignalErr))
1026  continue;
1027 
1028  const double r = RPerp(showerAxis, station.fPos - core);
1029 
1030  if (fStage2.fFixBetaRange[0] < r && r < fStage2.fFixBetaRange[1]) {
1031  ++nStationsInRange;
1032 
1033  for (const auto& s : data) {
1034  const double rr = RPerp(showerAxis, s.fPos - core);
1035 
1036  if (fStage2.fFixBetaRange[0] < rr && rr < fStage2.fFixBetaRange[1]) {
1037  const double distance = fabs(r - rr);
1038  if (distance > maxDistance)
1039  maxDistance = distance;
1040  }
1041  }
1042  }
1043  }
1044 
1045  const bool fixBeta = !(
1046  (nStationsInRange > 1 && maxDistance > fStage2.fFixBetaLeverArmRange[0]) ||
1047  (nStationsInRange > 2 && maxDistance > fStage2.fFixBetaLeverArmRange[1]) ||
1048  (nStationsInRange > 3 && maxDistance > fStage2.fFixBetaLeverArmRange[2])
1049  );
1050 
1051  OUT(eIntermediate)
1052  << "FixBeta = " << fixBeta << ": "
1053  "nStations (InRange) = " << nStations << " (" << nStationsInRange << "), "
1054  "maxDistance = " << maxDistance/meter << " m\n";
1055 
1056  return fixBeta;
1057 }
1058 
1059 
1061 bool
1063  const Vector& showerAxis,
1064  const vector<StationFitData>& data)
1065  const
1066 {
1067  size_t nStations = 0;
1068  for (const auto& station : data) {
1069  if (!station.fSignal || (station.fIsSaturated && !station.fRecoveredSignalErr))
1070  continue;
1071  ++nStations;
1072  }
1073 
1074  if (nStations < 5) {
1075  OUT(eIntermediate)
1076  << "FixGamma = 1: nStations = " << nStations << '\n';
1077  return true;
1078  }
1079 
1080  int nStationsInRange = 0;
1081  double maxDistance = 0;
1082  const auto core = result.GetCore();
1083  for (const auto& station : data) {
1084  if (!station.fSignal || (station.fIsSaturated && !station.fRecoveredSignalErr))
1085  continue;
1086 
1087  const double r = RPerp(showerAxis, station.fPos - core);
1088 
1089  if (fStage2.fFixGammaRange[0] < r && r < fStage2.fFixGammaRange[1]) {
1090 
1091  ++nStationsInRange;
1092 
1093  for (const auto& s : data) {
1094 
1095  const double rr = RPerp(showerAxis, s.fPos - core);
1096 
1097  if (fStage2.fFixGammaRange[0] < rr && rr < fStage2.fFixGammaRange[1]) {
1098  const double distance = fabs(r - rr);
1099  if (distance > maxDistance)
1100  maxDistance = distance;
1101  }
1102  }
1103  }
1104  }
1105 
1106  const bool fixGamma = !(
1107  (nStationsInRange > 1 && maxDistance > fStage2.fFixGammaLeverArmRange[0]) ||
1108  (nStationsInRange > 2 && maxDistance > fStage2.fFixGammaLeverArmRange[1]) ||
1109  (nStationsInRange > 3 && maxDistance > fStage2.fFixGammaLeverArmRange[2])
1110  );
1111 
1112  OUT(eIntermediate)
1113  << "FixGamma = " << fixGamma << ": "
1114  "nStations (InRange) = " << nStations << " (" << nStationsInRange << "), "
1115  "maxDistance = " << maxDistance/meter << " m\n";
1116 
1117  return fixGamma;
1118 }
1119 
1120 
1121 void
1123  const LDFFitResult& lres,
1124  const CurvFitResult& cres)
1125  const
1126 {
1127  auto& recShower = event.GetRecShower().GetSRecShower();
1128 
1129  const auto& sDetector = det::Detector::GetInstance().GetSDetector();
1130 
1131  const auto& ldf = fLDFFitConfig.fLDF;
1132  const double showerSize = lres.fPar[eShowerSize];
1133  const auto& ldfShapePars = lres.GetLDFShapeParameters();
1134 
1135  const auto& core = lres.GetCore();
1136  const auto& coreCS = LocalCoordinateSystem::Create(core);
1137  const auto& axis = cres.GetAxis();
1138  const double cosTheta = axis.GetCosTheta(coreCS);
1139 
1140  auto& sEvent = event.GetSEvent();
1141 
1142  const double rCurv = cres.fPar[eRCore];
1143  const bool haveCurvature = (rCurv != 0);
1144  const Point showerOrigin = core + rCurv * axis;
1145  const double cTCore = cres.fPar[eCTCore];
1146 
1147  const double theta = axis.GetTheta(coreCS);
1148  const double phi = axis.GetPhi(coreCS);
1149  const CoordinateSystemPtr showerPlaneCS(
1150  CoordinateSystem::RotationY(
1151  theta,
1152  CoordinateSystem::RotationZ(
1153  phi,
1154  coreCS
1155  )
1156  )
1157  );
1158 
1159  double coreCovSP[2][2]; // core covariance in shower plane coordinates
1160  {
1161  // uncertainty propagation for trafo rSP = RM r (RM = Rotation Matrix)
1162  // see Documentation/ReferenceGuide/SdReconstruction
1163  HepRotation r;
1164  r.rotateZ(-phi);
1165  r.rotateY(-theta);
1166 
1167  for (int i : { 0, 1 }) {
1168  const auto& r_i = r[i];
1169  auto& cc_i = coreCovSP[i];
1170  for (int j : { 0, 1 }) {
1171  const auto& r_j = r[j];
1172  auto& cc_ij = cc_i[j];
1173  cc_ij = 0;
1174  for (int k : { 0, 1 }) {
1175  const auto& r_ik = r_i[k];
1176  for (int m : { 0, 1 })
1177  cc_ij += r_ik * r_j[m] * lres.fCov(1+k, 1+m);
1178  }
1179  }
1180  }
1181  }
1182 
1183  const auto& timeVariance = sdet::STimeVariance::GetInstance();
1184  Accumulator::MinMax<double> minMaxRho(1000*kilometer, 0);
1185  Accumulator::SampleStandardDeviation timeStat;
1186 
1187  for (auto& station : sEvent.StationsRange()) {
1188  if (!station.HasRecData())
1189  continue;
1190 
1191  const auto& sPos = sDetector.GetStation(station).GetPosition();
1192  const double x = sPos.GetX(showerPlaneCS);
1193  const double y = sPos.GetY(showerPlaneCS);
1194  const double rho = std::sqrt(x*x + y*y);
1195 
1196  if (station.IsCandidate())
1197  minMaxRho(rho);
1198 
1199  const double rhoErr =
1200  std::sqrt(Sqr(x) * coreCovSP[0][0] + Sqr(y) * coreCovSP[1][1] + 2*x*y * coreCovSP[0][1]) / rho;
1201 
1202  auto& sRec = station.GetRecData();
1203  const double signal = sRec.GetTotalSignal();
1204  const double funcval = showerSize * ldf(rho, ldfShapePars);
1205  const double sigma = utl::wcd::SignalUncertainty(fLDFFitConfig.fSignalVarianceModel, cosTheta, funcval);
1206  sRec.SetAzimuthShowerPlane(sPos.GetPhi(showerPlaneCS));
1207  sRec.SetTotalSignal(signal, sigma);
1208  sRec.SetLDFResidual((signal - funcval)/sigma);
1209  sRec.SetSPDistance(rho, rhoErr);
1210 
1211  if (haveCurvature) {
1212  const double measuredTime = (sRec.GetSignalStartTime() - fBaryTime).GetInterval();
1213  const Vector stationToOrigin = showerOrigin - sPos;
1214  const double rStation = stationToOrigin.GetMag();
1215  const double predictedTime = (rStation + cTCore - rCurv) / kSpeedOfLight;
1216  const double cosThetaStation = stationToOrigin.GetZ(coreCS) / rStation;
1217  double sigma2 = GetTimeVariance(fLDFFitConfig, timeVariance, signal, sRec, cosThetaStation, rho);
1218  if (fLDFFitConfig.fUseAdditionalGPSTimeVariance)
1219  sigma2 += sRec.GetGPSTimeVariance();
1220  const double residual = (measuredTime - predictedTime) / std::sqrt(sigma2);
1221  sRec.SetResidual(residual);
1222  if (station.IsCandidate())
1223  timeStat(residual);
1224  }
1225 
1226  } // loop over stations with RecData
1227 
1228  if (!recShower.HasLDF())
1229  recShower.MakeLDF();
1230  auto& tabulatedLDF = recShower.GetLDF();
1231  tabulatedLDF.Clear();
1232 
1233  {
1234  const double x0 = log(1);
1235  const double x1 = log(10000);
1236  const int nLDFPoints = 20;
1237 
1238  for (int i = 0; i < nLDFPoints; ++i) {
1239  const double z = double(i) / (nLDFPoints - 1);
1240  const double rho = exp((1 - z)*x0 + z*x1);
1241  const double funcval = showerSize * ldf(rho, ldfShapePars);
1242  const double sigma = utl::wcd::SignalUncertainty(fLDFFitConfig.fSignalVarianceModel, cosTheta, funcval);
1243  tabulatedLDF.PushBack(rho, 0, funcval, sigma);
1244  }
1245  }
1246 
1247  // set the reconstruction data
1248  if (haveCurvature) {
1249  const double rCurvErr = cres.fCov.Std(eRCore);
1250  const double cTOffsetErr = cres.fCov.Std(eCTCore);
1251  const double cTOffset = cres.fPar[eCTCore];
1252  recShower.SetCurvature(1/rCurv, rCurvErr/Sqr(rCurv));
1253  recShower.SetCurvatureTimeOffset(cTOffset, cTOffsetErr);
1254  recShower.SetCoreTime(fBaryTime + TimeInterval(cTCore/kSpeedOfLight),
1255  TimeInterval(cTOffsetErr/kSpeedOfLight));
1256 
1257  const double timeMean = timeStat.GetAverage();
1258  const double timeSpread = timeStat.GetStandardDeviation();
1259  recShower.SetTimeResidualMean(timeMean);
1260  recShower.SetTimeResidualSpread(timeSpread);
1261 
1262  recShower.SetAngleChi2(cres.fChi2, cres.fNdof);
1263  const double u = cres.fPar[eU];
1264  const double v = cres.fPar[eV];
1265  const double w = sqrt(1 - u*u - v*v);
1266  const double suu = cres.fCov[eU];
1267  const double suv = cres.fCov(eU, eV);
1268  const double svv = cres.fCov[eV];
1269 
1270  const double suw = -(suu*u + suv*v) / w;
1271  const double svw = -(suv*u + svv*v) / w;
1272  const double sww = (u*u*suu + 2*u*v*suv + v*v*svv) / (w*w);
1273 
1274  recShower.SetParameter(eShowerAxisX, u);
1275  recShower.SetParameter(eShowerAxisY, v);
1276  recShower.SetParameter(eShowerAxisZ, w);
1277  recShower.SetParameterCovariance(eShowerAxisX, eShowerAxisX, suu);
1278  recShower.SetParameterCovariance(eShowerAxisY, eShowerAxisY, svv);
1279  recShower.SetParameterCovariance(eShowerAxisZ, eShowerAxisZ, sww);
1280  recShower.SetParameterCovariance(eShowerAxisX, eShowerAxisY, suv);
1281  recShower.SetParameterCovariance(eShowerAxisX, eShowerAxisZ, suw);
1282  recShower.SetParameterCovariance(eShowerAxisY, eShowerAxisZ, svw);
1283 
1284  recShower.SetAxis(axis);
1285  recShower.SetAngleErrors(Vector::Triple(u, v, w), Vector::Triple(suu, suv, svv));
1286 
1287  } else {
1288  // we have moved the core but have axis from plane fit, recalc t0
1289  const TimeInterval cdiff = recShower.GetAxis() * (recShower.GetCorePosition() - core);
1290  const TimeStamp newCoreTime = recShower.GetCoreTime() + cdiff/kSpeedOfLight;
1291  recShower.SetCurvature(0, 0);
1292  recShower.SetCoreTime(newCoreTime, recShower.GetCoreTimeError());
1293  }
1294 
1295  {
1296  const double showerSizeErr = lres.fCov.Std(eShowerSize);
1297  vector<double> ldfShapeParsErr;
1298  for (size_t i = 0, n = fLDFFitConfig.fLDF.GetNShapeParameters(); i < n; ++i)
1299  ldfShapeParsErr.push_back(lres.fCov.Std(eShapeParameters + i));
1300  recShower.SetNumberOfActiveStations(timeStat.GetN());
1301 
1302  switch (fArrayType) {
1303  case sdet::Array::e1500:
1304  recShower.SetShowerSizeType(ShowerSRecData::eS1000);
1305  break;
1306  case sdet::Array::e750:
1307  recShower.SetShowerSizeType(ShowerSRecData::eS450);
1308  break;
1309  case sdet::Array::e433:
1310  recShower.SetShowerSizeType(ShowerSRecData::eS300);
1311  break;
1312  default:
1313  recShower.SetShowerSizeType(ShowerSRecData::eUndefined);
1314  }
1315 
1316  recShower.SetShowerSize(showerSize, showerSizeErr);
1317  recShower.SetCorePosition(core);
1318  const double coreXErr = lres.fCov.Std(eCoreX);
1319  const double coreYErr = lres.fCov.Std(eCoreY);
1320  recShower.SetCoreError(Vector(coreXErr, coreYErr, 0, gBaryCS));
1321  recShower.SetCorrelationXY((coreXErr > 0 && coreYErr > 0) ?
1322  lres.fCov(eCoreX,eCoreY)/(coreXErr*coreYErr) : 0.);
1323  recShower.SetBeta(ldfShapePars[0], ldfShapeParsErr[0]);
1324  if (ldfShapePars.size() > 1)
1325  recShower.SetGamma(ldfShapePars[1], ldfShapeParsErr[1]);
1326  else
1327  recShower.SetGamma(0, 0);
1328  if (fLDFFitConfig.fLDF.GetType() == "NKGFermi")
1329  recShower.SetNKGFermiParameters(ldfShapePars[2], ldfShapePars[3]);
1330  recShower.SetLDFChi2(lres.fChi2, lres.fNdof);
1331  recShower.SetLDFLikelihood(lres.fLogLikelihood);
1332 
1333  recShower.SetCicRefAngle(fEnergyConversion.fCicReferenceAngle);
1334  recShower.SetS38(fEnergyConversion.GetS38(showerSize, cosTheta));
1335 
1336  double energy = 0;
1337  double energyErr = 0;
1338  double energySys = 0;
1339  fEnergyConversion.GetEnergy(cosTheta, showerSize, showerSizeErr,
1340  recShower.GetShowerSizeSystematics(),
1341  energy, energyErr, energySys);
1342 
1343  if (!energy)
1344  OUT(eFinal)
1345  << " theta = " << theta/degree << " is outside the range of the CIC. The energy is not valid.\n";
1346 
1347  recShower.SetEnergy(energy, energyErr);
1348  recShower.SetEnergyLDFSystematics(energySys);
1349  recShower.SetLDFRecStage(lres.fRecStage);
1350 
1351  for (const auto sId : fBadSilents) {
1352  if (sEvent.HasStation(sId))
1353  sEvent.GetStation(sId).SetRejected(StationConstants::eBadSilent);
1354  }
1355 
1356  }
1357 }
1358 
1359 
1361 vector<StationFitData>
1363  const
1364 {
1365  vector<StationFitData> sFitData;
1366 
1367  const auto& sDetector = det::Detector::GetInstance().GetSDetector();
1368 
1369  for (const auto& station : sEvent.StationsRange()) {
1370  StationFitData current;
1371  if (station.IsCandidate()) {
1372  current.fId = station.GetId();
1373  current.fPos = sDetector.GetStation(station).GetPosition();
1374  current.fIsSaturated = station.IsLowGainSaturation();
1375  const auto& sRec = station.GetRecData();
1376  current.fSignal = sRec.GetTotalSignal();
1377  current.fRecoveredSignal = sRec.GetRecoveredSignal();
1378  current.fRecoveredSignalErr = sRec.GetRecoveredSignalError();
1379  current.fCTime = kSpeedOfLight * (sRec.GetSignalStartTime() - fBaryTime).GetInterval();
1380  current.fT50 = sRec.GetT50();
1381  current.fGPSTimeVariance = fLDFFitConfig.fUseAdditionalGPSTimeVariance ? sRec.GetGPSTimeVariance() : 0;
1382  sFitData.push_back(current);
1383  } else if (station.IsSilent() ||
1384  (fUseSilentStationsFromOtherGrids &&
1385  station.GetRejectionStatus() == StationConstants::eOffGrid &&
1386  !station.HasRecData() &&
1387  sDetector.GetStation(station).IsInGrid(sdet::SDetectorConstants::eAny))) {
1388  current.fId = station.GetId();
1389  current.fPos = sDetector.GetStation(station).GetPosition();
1390  current.fIsSaturated = false;
1391  current.fSignal = 0;
1392  current.fRecoveredSignalErr = 0;
1393  current.fCTime = 0;
1394  current.fT50 = 0;
1395  current.fGPSTimeVariance = 0;
1396  sFitData.push_back(current);
1397  }
1398  } // loop over stations
1399 
1400  return sFitData;
1401 }
1402 
1403 
1405 void
1407  LDFFitConfig& config,
1408  const Vector& showerAxis,
1409  const vector<StationFitData>& data)
1410  const
1411 {
1412  result.fCov = 0;
1413 
1414  const auto core = result.GetCore();
1415 
1416  const double cosTheta = showerAxis.GetCosTheta(gBaryCS);
1417 
1418  const auto shape = config.fLDF.ShapeModel(cosTheta, 10);
1419 
1420  LDFLikelihoodFunction likeFcn(config, showerAxis, data);
1421 
1422  LDFFitResult resultA = result;
1423  LDFFitResult resultB = result;
1424 
1425  {
1426  // estimate shower size from station closest to reference distance
1427 
1428  const double kRefDist = config.fLDF.GetReferenceDistance();
1429  double minDistance = numeric_limits<double>::max();
1430  double rhoClosest = kRefDist;
1431  double signalClosest = 1;
1432 
1433  // loop over normal stations, ignore silent/saturated stations
1434  for (const auto& station : data) {
1435  if (!station.fSignal)
1436  continue;
1437 
1438  const double rho = RPerp(showerAxis, station.fPos - core);
1439  const double distance = fabs(rho - kRefDist);
1440 
1441  if (distance < minDistance) {
1442  minDistance = distance;
1443  rhoClosest = rho;
1444  signalClosest = station.fSignal;
1445  }
1446  }
1447 
1448  resultA.fPar[eShowerSize] = signalClosest / config.fLDF(rhoClosest, shape);
1449  resultA.fLogLikelihood = likeFcn(resultA.fPar);
1450 
1451  OUT(eIntermediate)
1452  << " " << GetShowerSizeLabel(config) << " [estimate A] = "
1453  << resultA.fPar[eShowerSize] << " VEM (logLike = " << resultA.fLogLikelihood << ")\n";
1454  }
1455 
1456  {
1457  /*
1458  If core and shape of the LDF are fixed, the least-squares
1459  estimate of shower size S0 can be calculated analytically.
1460 
1461  S_i ... data
1462  S(r_i) = S0 f(r_i) ... model
1463  sigma^2(S) = S ... simplest signal uncertainty model
1464 
1465  chi^2 = sum_i { (S_i - S(r_i))^2 / S(r_i) }
1466 
1467  With maximum condition d chi^2/d S0 = 0 one obtains
1468  S0^2 = sum_i { S_i^2 / f(r_i)) } / sum_i { f(r_i) }
1469  */
1470 
1471  double sumA = 0;
1472  double sumB = 0;
1473  for (const auto& station : data) {
1474  const double s = station.fSignal;
1475 
1476  if (!s)
1477  continue;
1478 
1479  const double rho = RPerp(showerAxis, station.fPos - core);
1480  const double f = config.fLDF(rho, shape);
1481 
1482  sumA += s*s / f;
1483  sumB += f;
1484  }
1485 
1486  resultB.fPar[eShowerSize] = sqrt(sumA / sumB);
1487  resultB.fLogLikelihood = likeFcn(resultB.fPar);
1488 
1489  OUT(eIntermediate)
1490  << " " << GetShowerSizeLabel(config) << " [estimate B] = "
1491  << resultB.fPar[eShowerSize] << " VEM (logLike = " << resultB.fLogLikelihood << ")\n";
1492  }
1493 
1494  // use better estimate of the two
1495  result = resultA.fLogLikelihood < resultB.fLogLikelihood ? resultA : resultB;
1496 
1497  // Finally, perform full-featured fit with fixed core
1498  LDFFitConfig thisConfig = config;
1499  thisConfig.fFixCore = true;
1500  for (size_t i = 0, n = thisConfig.fLDF.GetNShapeParameters(); i < n; ++i)
1501  thisConfig.fLDFShapeTreatment[i] = eModeled;
1502  FitLDF(result, thisConfig, showerAxis, data);
1503  config.fUseSaturationRecovery = thisConfig.fUseSaturationRecovery; // remember to possibly ignore sat. rec.
1504 }
1505 
1506 
1507 bool
1509  LDFFitConfig& config /* needs to be mutable */,
1510  const Vector& showerAxis,
1511  const vector<StationFitData>& data)
1512  const
1513 {
1514  using namespace ROOT::Minuit2;
1515 
1516  MnUserParameters pars;
1517 
1518  pars.Add("showerSize", result.fPar[0], 1.0);
1519  pars.SetLowerLimit("showerSize", 0);
1520  pars.Add("coreX", result.fPar[1], 200*meter);
1521  pars.Add("coreY", result.fPar[2], 200*meter);
1522 
1523  for (size_t i = 0, n = config.fLDF.GetNShapeParameters(); i < n; ++i) {
1524  pars.Add(GetShapeLabel(i), result.fPar[3+i], 0.1);
1525  if (config.fLDFShapeTreatment[i] != eFitted)
1526  pars.Fix(GetShapeLabel(i));
1527  }
1528 
1529  if (config.fFixCore) {
1530  pars.Fix("coreX");
1531  pars.Fix("coreY");
1532  }
1533 
1534  LDFChi2Function chi2Fcn(config, showerAxis, data);
1535  LDFLikelihoodFunction likeFcn(config, showerAxis, data);
1536 
1537  MnMinimize m(fUseMaxLikelihood ?
1538  static_cast<const FCNBase&>(likeFcn) :
1539  static_cast<const FCNBase&>(chi2Fcn), pars, 1);
1540 
1541  FunctionMinimum fmin = m(10000);
1542 
1543  if (fmin.IsAboveMaxEdm() || fmin.HasReachedCallLimit() || !fmin.IsValid()) {
1544  OUT(eIntermediate)
1545  << "\n**************** Minimize FAILED ****************\n"
1546  << fmin
1547  << "**************** Minimize FAILED ****************\n\n";
1548  return false;
1549  }
1550 
1551  {
1552  ostringstream msg;
1553  msg << "Found minimum after " << fmin.NFcn() << " evaluations";
1554  INFO(msg);
1555  }
1556 
1557  if (config.fUseSaturationRecovery) {
1558  // was use of saturation recovery ok? fit again without if
1559  // a) second derivative of LDF at sat. station > threshold,
1560  // but only for events with more than three candidates
1561  // b) signal from LDF < (unrecovered signal - 5 sigma) of sat. station,
1562  // such a fit would be inconsistent
1563  size_t nCandidates = 0;
1564  for (const auto& station : data) {
1565  if (station.fSignal > 0)
1566  ++nCandidates;
1567  }
1568 
1569  const auto& params = fmin.UserParameters().Params();
1570  const Point core(params[eCoreX], params[eCoreY], 0, gBaryCS);
1571  auto shape = config.fLDF.ShapeModel(showerAxis.GetCosTheta(gBaryCS), params[eShowerSize]);
1572  for (size_t i = 0, n = config.fLDF.GetNShapeParameters(); i < n; ++i)
1573  if (config.fLDFShapeTreatment[i] != eModeled)
1574  shape[i] = params[eShapeParameters + i];
1575 
1576  for (const auto& station : data) {
1577  if (station.fIsSaturated) {
1578  if (station.fRecoveredSignal > 0 && station.fRecoveredSignalErr > 0) {
1579  const Vector coreToStation = station.fPos - core;
1580  const double rho = RPerp(showerAxis, coreToStation);
1581 
1582  const double secDerivative = config.fLDF.SecondDerivative(rho, shape);
1583  const double prediction = params[eShowerSize]*config.fLDF(rho, shape);
1584  if ((nCandidates > 3 && secDerivative > config.fRecoveryThreshold) ||
1585  prediction < (station.fSignal - 5*sqrt(prediction))) {
1586  ostringstream msg;
1587  msg << "Saturation recovery rejected - fit again without\n"
1588  " 2nd derivative of LDF = " << secDerivative << " [Max: " << config.fRecoveryThreshold << "]\n"
1589  " LDF prediction = " << prediction << " VEM [Min: " << (station.fSignal - 5*sqrt(prediction)) << " VEM]\n"
1590  " " << GetShowerSizeLabel(config) << " from previous minimization = " << params[eShowerSize] << " VEM";
1591  INFO(msg);
1592 
1593  config.fUseSaturationRecovery = false;
1594 
1595  fmin = m(10000);
1596 
1597  msg.str("");
1598  msg << "Found minimum after " << fmin.NFcn() << " evaluations";
1599  INFO(msg);
1600 
1601  break;
1602  }
1603  } else
1604  // remember for later that no saturation recovery was available
1605  config.fUseSaturationRecovery = false;
1606  }
1607  }
1608  }
1609 
1610  MnHesse hesse(1);
1611  hesse(m.Fcnbase(), fmin, 10000);
1612 
1613  if (!fmin.IsValid() || fmin.HesseFailed()) {
1614  OUT(eIntermediate)
1615  << "\n**************** Hesse FAILED ****************\n"
1616  << fmin
1617  << "**************** Hesse FAILED ****************\n\n";
1618  return false;
1619  }
1620 
1621  if (fInfoLevel >= eMinuit)
1622  OUT(eIntermediate)
1623  << "\n**************** Minuit Output ****************\n"
1624  << fmin
1625  << "**************** Minuit Output ****************\n\n";
1626 
1627  // get fit results
1628  result.fPar = fmin.UserParameters().Params();
1629 
1630  const auto modeledShape =
1631  config.fLDF.ShapeModel(showerAxis.GetCosTheta(gBaryCS), result.fPar[0]);
1632  for (size_t i = 0, n = config.fLDF.GetNShapeParameters(); i < n; ++i)
1633  if (config.fLDFShapeTreatment[i] == eModeled)
1634  result.fPar[3+i] = modeledShape[i];
1635 
1636  const auto nVar = pars.VariableParameters();
1637 
1638  // get covariance
1639  result.fCov = 0;
1640 
1641  for (size_t i = 0; i < nVar; ++i)
1642  for (size_t j = 0; j < nVar; ++j)
1643  result.fCov(m.ExtOfInt(i), m.ExtOfInt(j)) = fmin.UserCovariance()(i,j);
1644 
1645  const double minValue = fmin.Fval();
1646  if (fUseMaxLikelihood) {
1647  result.fLogLikelihood = minValue;
1648  bool save = config.fUseSilentStations;
1649  config.fUseSilentStations = false;
1650  result.fChi2 = chi2Fcn(result.fPar);
1651  config.fUseSilentStations = save;
1652  } else {
1653  result.fLogLikelihood = likeFcn(result.fPar);
1654  result.fChi2 = minValue;
1655  }
1656 
1657  result.fNdof = 0;
1658  for (const auto& station : data)
1659  if (station.fSignal > 0)
1660  ++result.fNdof;
1661  result.fNdof -= nVar;
1662 
1663  const double reducedChi2 = (result.fNdof > 0) ? result.fChi2/result.fNdof : 0;
1664 
1665  const auto core = result.GetCore();
1666 
1667  const utl::Point coreErr(result.fCov.Std(eCoreX), result.fCov.Std(eCoreY), 0, gBaryCS);
1668  OUT(eIntermediate)
1669  << " chi2 = " << result.fChi2 << " / " << result.fNdof << "\n"
1670  " " << GetShowerSizeLabel(config) << " = "
1671  << result.fPar[eShowerSize] << " +/- " << result.fCov.Std(eShowerSize) << " VEM\n"
1672  " core = (" << String::Make(core, gBaryCS, meter, ", ") << ") +/- (" << String::Make(coreErr, gBaryCS, meter, ", ") << ") m (bary)";
1673  for (size_t i = 0, n = config.fLDF.GetNShapeParameters(); i < n; ++i)
1674  OUT(eIntermediate) << "\n"
1675  " " << GetShapeLabel(i) << " = " << result.fPar[eShapeParameters + i] << " +/- " << result.fCov.Std(eShapeParameters + i);
1676  OUT(eIntermediate) << '\n';
1677 
1678  if (reducedChi2 > fMaxChi2) {
1679  OUT(eIntermediate)
1680  << " chi2 / Ndof = " << reducedChi2 << " exceeds limit " << fMaxChi2 << '\n';
1681  return false;
1682  }
1683 
1684  const double d = (core - fBarycenter).GetMag();
1685  if (d > fMaxBaryToCoreDistance) {
1686  OUT(eIntermediate)
1687  << " Distance to core " << d/km << " km exceeds limit.\n";
1688  return false;
1689  }
1690 
1691  return !HasNaN(result);
1692 }
1693 
1694 
1695 double
1696 LDFFinder::ParameterizedRc(const double cosTheta, const double showerSize)
1697  const
1698 {
1699  // TODO: move the parametrization of the radius of curvature to the configuration files
1700  // TODO: implement a parametrization of the radius of curvature for the SD433
1701 
1702  const double sec1 = 1/cosTheta - 1;
1703 //#warning: IM Curvature parametrisation does not work with very inclined showers
1704 
1705  if (fArrayType == sdet::Array::e750 || fArrayType == sdet::Array::e433) {
1706  const double x = Sqr(cosTheta)- Sqr(0.81932);
1707  const double attenuationFactor = 1 + x*(1.55 - 1.14*x); // Alex attenuation (Feb 2012)
1708  const double s35 = (attenuationFactor > 0) ? showerSize/attenuationFactor : showerSize;
1709  const double s = log(s35);
1710  const double eta = 0.1963; //+-0.002
1711  const double mu = -0.0135; //+-0.0007
1712  const double zeta = 0.0002866; //+- 7.6e-05
1713  const double sDependence = eta + s*(mu + zeta*s);
1714 
1715  const double a = -1.068; //+-0.006
1716  const double b = 0.5059; //+-0.007
1717  const double thetaDependence = 1 + sec1*(a + b*sec1);
1718 
1719  const double curvature = sDependence * thetaDependence / kilometer;
1720 
1721  return curvature ? 1/curvature : 0;
1722  } else {
1723  const double x = Sqr(cosTheta) - Sqr(0.78821);
1724  const double attenuationFactor = 1 + x*(0.87 - 1.49*x); // ICRC2011 attenuation
1725  const double s38 = (attenuationFactor > 0) ? showerSize/attenuationFactor : showerSize;
1726  const double s = log(s38);
1727  const double eta = 0.1785; //+- 0.001408
1728  const double mu = -0.02149; //+- 0.0009256
1729  const double zeta = 0.0016; //+- 0.000149
1730  const double sDependence = eta + s*(mu + zeta*s);
1731 
1732  const double a = -1.047; //+- 0.006598
1733  const double b = 0.473; //+- 0.007029
1734  const double thetaDependence = 1 + sec1*(a + b*sec1);
1735 
1736  const double curvature = sDependence * thetaDependence / kilometer;
1737 
1738  return curvature ? 1/curvature : 0;
1739 
1740  /* old parametrization
1741  const double offset = 9.03 - 3.08*s + 1.08*s2;
1742  const double slope = 7.28 + 4.03*s - 1.01*s2;
1743 
1744  const double s = log10(showerSize);
1745  const double s2 = Sqr(s);
1746  const double offset = 7.418 - 3.0613*s + 2.5262*s2;
1747  const double slope = 3.9779 + 3.2245*s + 4.5705*s2;
1748 
1749  return (offset + slope*sec1) * kilometer;*/
1750  }
1751 }
1752 
1753 
1754 double
1756  const Point& core,
1757  const vector<StationFitData>& data)
1758  const
1759 {
1760  using namespace ROOT::Minuit2;
1761 
1762  MnUserParameters pars;
1763 
1764  // MINUIT requires unbounded variables, but
1765  // u and v are bounded, so I define new variables
1766  // pu and pv here that are unbounded.
1767  // The mapping is one-on-one and has the property
1768  // pu ~ u and pv ~ v for small u and v, thus the
1769  // non-linear properties appear only for very inclined events.
1770  const double u = result.fPar[eU];
1771  const double v = result.fPar[eV];
1772  const double w = sqrt(1 - u*u - v*v);
1773  pars.Add("pu", u/w, 0.01);
1774  pars.Add("pv", v/w, 0.01);
1775  pars.Add("rCore", result.fPar[eRCore], 10*meter);
1776  pars.SetLowerLimit("rCore", 0);
1777  pars.Add("cTCore", result.fPar[eCTCore], 10*meter);
1778 
1779  if (fFixedAxis) {
1780  pars.Fix("pu");
1781  pars.Fix("pv");
1782  }
1783 
1784  if (fFixedCurvature)
1785  pars.Fix("rCore");
1786 
1787  CurvatureChi2Function chi2Fcn(core, data);
1788 
1789  MnMinimize m(chi2Fcn, pars, 1);
1790 
1791  FunctionMinimum fmin = m(10000);
1792 
1793  if (fmin.IsAboveMaxEdm() || fmin.HasReachedCallLimit() || !fmin.IsValid()) {
1794  OUT(eIntermediate)
1795  << "\n**************** Minimize FAILED ****************\n"
1796  << fmin
1797  << "**************** Minimize FAILED ****************\n\n";
1798  return false;
1799  }
1800 
1801  {
1802  ostringstream msg;
1803  msg << "Found minimum after " << fmin.NFcn() << " evaluations";
1804  INFO(msg);
1805  }
1806 
1807  MnHesse hesse(1);
1808  hesse(m.Fcnbase(), fmin, 10000);
1809 
1810  if (!fmin.IsValid() || fmin.HesseFailed()) {
1811  OUT(eIntermediate)
1812  << "\n**************** Hesse FAILED ****************\n"
1813  << fmin
1814  << "**************** Hesse FAILED ****************\n\n";
1815  return false;
1816  }
1817 
1818  if (fInfoLevel >= eMinuit)
1819  OUT(eIntermediate)
1820  << "\n**************** Minuit Output ****************\n"
1821  << fmin
1822  << "**************** Minuit Output ****************\n\n";
1823 
1824  // get fit results
1825  const auto& params = fmin.UserParameters().Params();
1826  const double pu = params[0];
1827  const double pv = params[1];
1828  const double pw = sqrt(1 + pu*pu + pv*pv);
1829  result.fPar[eU] = pu/pw;
1830  result.fPar[eV] = pv/pw;
1831  result.fPar[eRCore] = params[eRCore];
1832  result.fPar[eCTCore] = params[eCTCore];
1833 
1834  if (!fFixedCurvature && result.fPar[eRCore] > 100*kilometer) {
1835  OUT(eIntermediate)
1836  << " curvature radius too large: " << result.fPar[eRCore]/kilometer << " km\n";
1837  return false;
1838  }
1839 
1840  if (!fFixedCurvature && result.fPar[eRCore] < 1*kilometer) {
1841  OUT(eIntermediate)
1842  << " curvature radius too small: " << result.fPar[eRCore]/kilometer << " km\n";
1843  return false;
1844  }
1845 
1846  const auto nVar = pars.VariableParameters();
1847 
1848  // get covariance, propagate from (pu,pv) to (u,v)
1849  CovarianceMatrix cov(4);
1850 
1851  for (size_t i = 0; i < nVar; ++i)
1852  for (size_t j = 0; j < nVar; ++j)
1853  cov(m.ExtOfInt(i), m.ExtOfInt(j)) = fmin.UserCovariance()(i, j);
1854 
1855  const double pupv = pu*pv;
1856  const double pwi3 = 1/(pw*pw*pw);
1857  const double jacobian[4][4] = {
1858  { (1 + pv*pv)*pwi3, -pupv*pwi3, 0, 0 },
1859  { -pupv*pwi3, (1 + pu*pu)*pwi3, 0, 0 },
1860  { 0, 0, 1, 0 },
1861  { 0, 0, 0, 1 }
1862  };
1863 
1864  for (size_t i = 0; i < 4; ++i) {
1865  const auto& jac_i = jacobian[i];
1866  for (size_t j = i; j < 4; ++j) {
1867  const auto& jac_j = jacobian[j];
1868  auto& c_ij = result.fCov(i, j);
1869  c_ij = 0;
1870  for (size_t k = 0; k < 4; ++k) {
1871  const auto& jac_ik = jac_i[k];
1872  for (size_t m = 0; m < 4; ++m)
1873  c_ij += jac_ik * jac_j[m] * cov(k, m);
1874  }
1875  }
1876  }
1877 
1878  result.fChi2 = fmin.Fval();
1879 
1880  result.fNdof = 0;
1881  for (const auto& sf : data)
1882  if (sf.fSignal > 0)
1883  ++result.fNdof;
1884  result.fNdof -= nVar;
1885 
1886  return !HasNaN(result);
1887 }
1888 
1889 
1890 vector<int>
1892  const LDFFitConfig& config,
1893  const utl::Vector& axis,
1894  std::vector<StationFitData>& data)
1895  const
1896 {
1897  vector<int> badStations;
1898  const utl::Point core(ldfres.fPar[1], ldfres.fPar[2], 0, gBaryCS);
1899  int nrCandidates = 0;
1900  for (const auto& station : data) {
1901  if (station.fSignal > 0)
1902  ++nrCandidates;
1903  }
1904  if (nrCandidates < fSilentsVars.fMinNumberOfStations)
1905  return badStations;
1906 
1907  const std::vector<double> shape(ldfres.fPar.begin()+3, ldfres.fPar.end());
1908 
1909  for (auto sIt = data.begin(); sIt != data.end(); ) {
1910  if (!sIt->fSignal) {
1911  const Vector coreToStation = sIt->fPos - core;
1912  const double rho = RPerp(axis, coreToStation);
1913  const double ldfValue = ldfres.fPar[0] * config.fLDF(rho, shape);
1914  if (rho < fSilentsVars.fMaxDistance &&
1915  ldfValue > fSilentsVars.fMaxLDFValue) {
1916  badStations.push_back(sIt->fId);
1917  sIt = data.erase(sIt);
1918  continue;
1919  }
1920  }
1921  ++sIt;
1922  }
1923  return badStations;
1924 }
1925 
1926 
1927 bool
1929  const LDFFitConfig& config,
1930  const vector<StationFitData>& data)
1931  const
1932 {
1933  using namespace ROOT::Minuit2;
1934 
1935  MnUserParameters pars;
1936 
1937  pars.Add("u", cres.fPar[0], 0.01);
1938  pars.Add("v", cres.fPar[1], 0.01);
1939  pars.Add("rCore", cres.fPar[2], 10*meter);
1940  pars.SetLowerLimit("rCore", 0);
1941  pars.Add("cTCore", cres.fPar[3], 10*meter);
1942 
1943  if (fFixedAxis) {
1944  pars.Fix("u");
1945  pars.Fix("v");
1946  }
1947 
1948  if (fFixedCurvature)
1949  pars.Fix("rCore");
1950 
1951  pars.Add("showerSize", lres.fPar[0], 1.0);
1952  pars.SetLowerLimit("showerSize", 0);
1953  pars.Add("coreX", lres.fPar[1], 200*meter);
1954  pars.Add("coreY", lres.fPar[2], 200*meter);
1955  for (size_t i = 0, n = config.fLDF.GetNShapeParameters(); i < n; ++i) {
1956  pars.Add(GetShapeLabel(i), lres.fPar[3+i], 0.1);
1957  if (config.fLDFShapeTreatment[i] != eFitted)
1958  pars.Fix(GetShapeLabel(i));
1959  }
1960 
1961  if (config.fFixCore) {
1962  pars.Fix("coreX");
1963  pars.Fix("coreY");
1964  }
1965 
1966  GlobalFitFunction likeFcn(config, data);
1967 
1968  MnMinimize m(likeFcn, pars, 2);
1969 
1970  FunctionMinimum fmin = m();
1971 
1972  if (fmin.IsAboveMaxEdm() || fmin.HasReachedCallLimit() || !fmin.IsValid()) {
1973  OUT(eIntermediate)
1974  << "\n**************** Minimize FAILED ****************\n"
1975  << fmin
1976  << "**************** Minimize FAILED ****************\n\n";
1977  return false;
1978  }
1979 
1980  {
1981  ostringstream msg;
1982  msg << "Found minimum after " << fmin.NFcn() << " evaluations";
1983  INFO(msg);
1984  }
1985 
1986  MnHesse hesse(2);
1987  hesse(m.Fcnbase(), fmin);
1988 
1989  if (!fmin.IsValid() || fmin.HesseFailed() || !fmin.HasAccurateCovar()) {
1990  OUT(eIntermediate)
1991  << "\n**************** Hesse FAILED ****************\n"
1992  << fmin
1993  << "**************** Hesse FAILED ****************\n\n";
1994  return false;
1995  }
1996 
1997  if (fInfoLevel >= eMinuit)
1998  OUT(eIntermediate)
1999  << "\n**************** Minuit Output ****************\n"
2000  << fmin
2001  << "**************** Minuit Output ****************\n\n";
2002 
2003  // get fit results
2004  const auto nPar = fmin.UserParameters().Params().size();
2005  for (size_t i = 0; i < nPar; ++i) {
2006  if (i < 4)
2007  cres.fPar[i] = fmin.UserParameters().Params()[i];
2008  else
2009  lres.fPar[i-4] = fmin.UserParameters().Params()[i];
2010  }
2011 
2012  const auto modeledShape = config.fLDF.ShapeModel(cres.GetAxis().GetCosTheta(gBaryCS), lres.fPar[0]);
2013  for (size_t i = 0, n = config.fLDF.GetNShapeParameters(); i < n; ++i)
2014  if (config.fLDFShapeTreatment[i] == eModeled)
2015  lres.fPar[3+i] = modeledShape[i];
2016 
2017  if (!fFixedCurvature && cres.fPar[eRCore] > 100*kilometer) {
2018  OUT(eIntermediate)
2019  << " curvature radius too large.\n";
2020  return false;
2021  }
2022 
2023  const auto core = lres.GetCore();
2024  const double d = (core - fBarycenter).GetMag();
2025  if (d > fMaxBaryToCoreDistance) {
2026  OUT(eIntermediate)
2027  << " Distance to core " << d/km << " km exceeds limit.\n";
2028  return false;
2029  }
2030 
2031  const auto nVar = pars.VariableParameters();
2032 
2033  // get covariance - not all measured covariances are stored for now
2034  cres.fCov = 0;
2035  lres.fCov = 0;
2036 
2037  for (size_t i = 0; i < nVar; ++i)
2038  for (size_t j = 0; j < nVar; ++j) {
2039  const auto iext = m.ExtOfInt(i);
2040  const auto jext = m.ExtOfInt(j);
2041  if (iext < 4 && jext < 4)
2042  cres.fCov(iext, jext) = fmin.UserCovariance()(i, j);
2043  if (iext >= 4 && jext >= 4)
2044  lres.fCov(iext-4, jext-4) = fmin.UserCovariance()(i, j);
2045  }
2046 
2047  {
2048  LDFLikelihoodFunction likeFcn(config, cres.GetAxis(), data);
2049  lres.fLogLikelihood = likeFcn(lres.fPar);
2050 
2051  LDFFitConfig chi2Config = config;
2052  chi2Config.fUseSilentStations = false;
2053  LDFChi2Function chi2Fcn(chi2Config, cres.GetAxis(), data);
2054  lres.fChi2 = chi2Fcn(lres.fPar);
2055 
2056  lres.fNdof = 0;
2057  for (const auto& station : data)
2058  if (station.fSignal > 0)
2059  ++lres.fNdof;
2060 
2061  for (size_t i = 4; i < nPar; ++i)
2062  if (!pars.Parameter(i).IsFixed())
2063  --lres.fNdof;
2064 
2065  CurvatureChi2Function curvChi2Fcn(lres.GetCore(), data);
2066  cres.fChi2 = curvChi2Fcn(cres.fPar);
2067 
2068  cres.fNdof = 0;
2069  for (const auto& station : data)
2070  if (station.fSignal > 0)
2071  ++cres.fNdof;
2072 
2073  for (size_t i = 0; i < 4; ++i)
2074  if (!pars.Parameter(i).IsFixed())
2075  --cres.fNdof;
2076  }
2077 
2078  const double reducedChi2 = (lres.fNdof > 0) ? lres.fChi2/lres.fNdof : 0;
2079 
2080  if (reducedChi2 > fMaxChi2) {
2081  OUT(eIntermediate)
2082  << " ldf chi2 / Ndof = " << reducedChi2 << " exceeds limit " << fMaxChi2 << '\n';
2083  return false;
2084  }
2085 
2086  const utl::Point coreErr(lres.fCov.Std(eCoreX), lres.fCov.Std(eCoreY), 0, gBaryCS);
2087  OUT(eIntermediate)
2088  << " curv Chi2 = " << cres.fChi2 << " / " << cres.fNdof << "\n"
2089  " axis = (" << String::Make(cres.GetAxis(), gBaryCS, 1, ", ") << ") (bary)\n"
2090  " RCore = " << cres.fPar[eRCore]/kilometer << " +/- " << cres.fCov.Std(eRCore)/kilometer << " km\n"
2091  " CTCore = " << cres.fPar[eCTCore]/meter << " +/- " << cres.fCov.Std(eCTCore)/meter << " m\n"
2092  " ldf chi2 = " << lres.fChi2 << " / " << lres.fNdof << "\n"
2093  " " << GetShowerSizeLabel(config) << " = " << lres.fPar[eShowerSize] << " +/- " << lres.fCov.Std(eShowerSize) << " VEM\n"
2094  " core = (" << String::Make(core, gBaryCS, meter, ", ") << ") +/- (" << String::Make(coreErr, gBaryCS, meter, ", ") << ") m (bary)";
2095  for (size_t i = 0, n = config.fLDF.GetNShapeParameters(); i < n; ++i)
2096  OUT(eIntermediate) << "\n"
2097  " " << GetShapeLabel(i) << " = " << lres.fPar[eShapeParameters + i] << " +/- " << lres.fCov.Std(eShapeParameters + i);
2098  OUT(eIntermediate) << '\n';
2099 
2100  return !HasNaN(cres) && !HasNaN(lres);
2101 }
Class to access station level reconstructed data.
TMatrixD jacobian(TVectorD(*f)(TVectorD, const modeltype), TVectorD x, TVectorD dx, const modeltype mtype)
Definition: Statistics.icc:10
std::string Make(const Geo &g, const CS &cs, const double unit=1, const std::string &sep=" ")
Definition: String.h:190
double GetCorrelationXY() const
const double eV
Definition: GalacticUnits.h:35
void OutputResults(const evt::Event &event) const
double GetCurvatureError() const
bool FixBeta(const LDFFitResult &result, const utl::Vector &showerAxis, const std::vector< StationFitData > &data) const
const double degree
constexpr T Sqr(const T &x)
Point object.
Definition: Point.h:32
double Angle(const double theta1, const double phi1, const double theta2, const double phi2)
double GetAngleNdof() const
double GetPhi(const CoordinateSystemPtr &coordinateSystem) const
azimuth (phi) angle in spherical and cylindrical coordinates
Definition: BasicVector.h:254
double GetCurvature() const
gaussian curvature = 1 / Rc
IsProxy< T > Is(const T &x)
Definition: Is.h:46
Interface class to access to the SD Reconstruction of a Shower.
bool HasFEvent() const
std::vector< StationFitData > MakeStationFitData(const sevt::SEvent &sEvent) const
Init station data used by LDF fit.
Interface class to access to the SD part of an event.
Definition: SEvent.h:39
double GetBetaError() const
double GetTheta(const CoordinateSystemPtr &coordinateSystem) const
zenith (theta) angle in spherical coordinates
Definition: BasicVector.h:248
bool FitLDF(LDFFitResult &result, LDFFitConfig &config, const utl::Vector &showerAxis, const std::vector< StationFitData > &data) const
bool FixGamma(const LDFFitResult &result, const utl::Vector &showerAxis, const std::vector< StationFitData > &data) const
Criterion if gamma can be fitted analogue to beta must be checked/updated.
double RPerp(const utl::Vector &axis, const utl::Vector &station)
void FitLDFSimplified(LDFFitResult &result, LDFFitConfig &config, const utl::Vector &showerAxis, const std::vector< StationFitData > &data) const
Early estimate of shower size.
double GetAngleChi2() const
double GetCosTheta(const CoordinateSystemPtr &coordinateSystem) const
cos of zenith (theta) angle
Definition: BasicVector.h:251
bool HasSimShower() const
void SetRecData(evt::Event &event, const LDFFitResult &lresult, const CurvFitResult &cresult) const
const double meter
Definition: GalacticUnits.h:29
const utl::Vector & GetCoreError() const
#define INFO(message)
Macro for logging informational messages.
Definition: ErrorLogger.h:161
const unsigned int npar
Definition: UnivRec.h:75
double GetShowerSize() const
Base class for exceptions trying to access non-existing components.
double BetaUncertainty(const double showerSize) const
double InverseNormalCDF(const double p)
Inverse of the comulative normal distribution. Taken from http://home.online.no/~pjacklam/notes/invno...
const double EeV
Definition: GalacticUnits.h:34
double GetMag() const
Definition: Vector.h:58
std::vector< double > ShapeModel(const double cosTheta, const double showerSize) const
double NormalCDF(const double x)
double GetEnergyError() const
A TimeStamp holds GPS second and nanosecond for some event.
Definition: TimeStamp.h:110
utl::CoordinateSystemPtr gBaryCS
double GetBeta() const
boost::shared_ptr< const CoordinateTransformer > CoordinateSystemPtr
Shared pointer for coordinate systems.
const char * GetShapeLabel(const int iShape)
fwk::VModule::ResultFlag Run(evt::Event &event) override
Run: invoked once per event.
#define max(a, b)
boost::tuple< double, double, double > Triple
Coordinate triple for easy getting or setting of coordinates.
Definition: Triple.h:15
constexpr double s
Definition: AugerUnits.h:163
double GetX(const CoordinateSystemPtr &coordinateSystem) const
Definition: BasicVector.h:206
const double ns
constexpr double kPi
Definition: MathConstants.h:24
constexpr double nanosecond
Definition: AugerUnits.h:143
const Data result[]
double GetGPSTimeVariance() const
double GetTimeResidualSpread() const
double GetLDFRecStage() const
double SecondDerivative(const double r, const std::vector< double > &shape) const
double GetTimeVariance(const LDFFitConfig &fconf, const sdet::STimeVariance &timeVariance, const double signal, const StationRecData &sRec, const double cosTheta, const double rho)
bool FitGlobal(CurvFitResult &cres, LDFFitResult &lres, const LDFFitConfig &config, const std::vector< StationFitData > &data) const
const double km
double GetThetaError() const
const utl::Vector & GetAxis() const
string GetShowerSizeLabel(const LDFFitConfig &config)
double GetPhiError() const
double FitCurvature(CurvFitResult &result, const utl::Point &core, const std::vector< StationFitData > &data) const
if(dataRoot)
Definition: XXMLManager.h:1003
double GetEnergy() const
bool FixCore(utl::Point &core, utl::TimeStamp &coreTime, utl::Vector &showerAxis, const evt::Event &event) const
fwk::VModule::ResultFlag Init() override
Initialize: invoked at beginning of run (NOT beginning of event)
std::vector< ParameterTreatment > fLDFShapeTreatment
double GetY(const CoordinateSystemPtr &coordinateSystem) const
Definition: BasicVector.h:209
constexpr double kSpeedOfLight
double GetGammaError() const
A TimeInterval is used to represent time elapsed between two events.
Definition: TimeInterval.h:43
ResultFlag
Flag returned by module methods to the RunController.
Definition: VModule.h:60
double GetLDFOptimumDistance() const
double SignalUncertainty(const ESignalVarianceModel model, const double cosTheta, const double signal)
double GetCorrelationThetaPhi() const
double GetReferenceDistance() const
uint16_t * data
Definition: dump1090.h:228
constexpr double kilometer
Definition: AugerUnits.h:93
Vector object.
Definition: Vector.h:30
Interface class to access to Fluorescence reconstruction of a Shower.
double GetShowerSizeError() const
double CosAngle(const Vector &l, const Vector &r)
Definition: OperationsVV.h:71
const utl::Point & GetCorePosition() const
unsigned int GetNShapeParameters() const
double GetZ(const CoordinateSystemPtr &coordinateSystem) const
Definition: BasicVector.h:212
const int nPar
Definition: GeomAsym.h:37
double Chi2Probability(const double chi2, const double ndof)
#define ERROR(message)
Macro for logging error messages.
Definition: ErrorLogger.h:165
constexpr double m
Definition: AugerUnits.h:121
double GetTimeResidualMean() const
const utl::Point & GetCorePosition() const
Shower core as reconstructed by the FD or FD eye.
bool HasSEvent() const
double GetShowerSizeSystematics() const
double GetGamma() const
std::vector< int > CleanSilentStation(const LDFFitResult &ldfres, const LDFFitConfig &config, const utl::Vector &showerAxis, std::vector< StationFitData > &stationFitData) const
double ParameterizedRc(const double cosTheta, const double showerSize) const
CoreShiftPropagator(const double coreX, const double coreY, const vector< double > &axisParameters)
const utl::Vector & GetAxis() const
Shower Axis as reconstructed by the FD or FD eye.
double GetTimeSigma2(const double signal, const double t50, const double cosTheta, const double distance=0) const

, generated on Tue Sep 26 2023.