SdPMTSimulatorOG/SdPMTSimulator.cc
Go to the documentation of this file.
1 #include <evt/Event.h>
2 #include <sevt/SEvent.h>
3 #include <sevt/PMT.h>
4 #include <sevt/PMTSimData.h>
5 #include <utl/ErrorLogger.h>
6 #include <utl/RandomEngine.h>
7 #include <fwk/CentralConfig.h>
8 #include <fwk/RandomEngineRegistry.h>
9 #include <utl/Reader.h>
10 #include <utl/Accumulator.h>
11 #include <utl/SaveCurrentTDirectory.h>
12 #include <utl/config.h>
13 
14 #include <sdet/SDetector.h>
15 #include <sdet/Station.h>
16 #include <sdet/PMT.h>
17 #include <sdet/PMTConstants.h>
18 
19 #include "SdPMTSimulator.h"
20 
21 using namespace SdPMTSimulatorOG;
22 using namespace std;
23 using namespace utl;
24 using namespace fwk;
25 using namespace sevt;
26 
27 using CLHEP::RandGeneral;
28 
29 
32 {
33  auto topB = CentralConfig::GetInstance()->GetTopBranch("SdPMTSimulator");
34  vector<int> components;
35  if (topB.GetChild("fundamentalComponents"))
36  topB.GetChild("fundamentalComponents").GetData(components);
37  for (const auto c : components) {
39  fFundamentalComponents.insert(StationConstants::SignalComponent(c));
40  else {
41  ostringstream err;
42  err << "Invalid signal component: " << c;
43  ERROR(err);
44  throw InvalidConfigurationException(err.str());
45  }
46  }
47  if (fFundamentalComponents.empty())
48  INFO("Will not use any fundamental components.");
49  else {
50  ostringstream info;
51  info << "Will use " << fFundamentalComponents.size() << " fundamental components:";
52  for (const auto c : fFundamentalComponents)
53  info << ' ' << c << "='" << GetSignalComponentName(c) << '\'';
54  INFO(info);
55  }
56 
57  auto elecB = topB.GetChild("electronicsParameters");
58 
59  elecB.GetChild("kFactor").GetData(fKFactor);
60 
61  fRandomEngine = &RandomEngineRegistry::GetInstance().Get(RandomEngineRegistry::eDetector);
62 
63  // Read the voltage values for PMT saturation
64  const AttributeMap useAtt = { { "use", "yes" } };
65 
66  auto satB = elecB.GetChild("saturationCurve", useAtt);
67  if (satB) {
68  auto vIn = satB.GetChild("inputVoltage").Get<vector<double>>();
69  auto vOut = satB.GetChild("outputVoltage").Get<vector<double>>();
70  satB.GetChild("Current2VoltageMultiplier").GetData(fCurrent2VoltageMultiplier);
71  if (vIn.size() != vOut.size()) {
72  ERROR("Lists of input/output voltages for saturation curve must have same size");
73  return eFailure;
74  }
75  ostringstream info;
76  info << "saturation curve [V]: ";
77  for (unsigned int i = 0, n = vIn.size(); i < n; ++i)
78  info << vIn[i] / volt << " -> " << vOut[i] / volt << " ";
79  INFO(info);
80  // convert voltages to "current" (i.e. smeared&amplified pe per ns bin):
81  const double v2c = 1 / fCurrent2VoltageMultiplier;
82  for (unsigned int i = 0, n = vIn.size(); i < n; ++i) {
83  vIn[i] *= v2c;
84  vOut[i] *= v2c;
85  }
86  // input/output of this function is "current" (i.e. pe per ns bin)
87  fSaturationFunction = new TabulatedFunction(vIn, vOut);
88  } else
89  INFO("No PMT saturation simulation");
90 
91  return eSuccess;
92 }
93 
94 
95 void
97 {
98  const unsigned int n = rawPulse.GetNPoints();
99  if (n < 2)
100  return;
101 
102  /* The raw, measured pulse (accessed through SDetector PMT) is
103  normalized and time=0 is set to its start. This process is
104  performed by iterating over the raw pulse. No assumptions are
105  made as to whether the measurement was performed in equally
106  spaced time intervals or as to the polarity (negative or
107  positive pulse).
108  During the iteration, we find the smallest interval of
109  time between measurements. We will use one order of magnitude
110  lower than this for binning during integration. */
111 
112  const double start = rawPulse.XFront();
113  const double stop = rawPulse.XBack();
114 
115  utl::Accumulator::Min<double> minTimeDifference(stop - start);
116  for (unsigned int i = 1; i < n; ++i)
117  minTimeDifference(rawPulse[i].X() - rawPulse[i-1].X());
118 
119  const double timeBinning = minTimeDifference.GetMin() / 10;
120 
121  double sum = 0;
122  for (double t = start; t < stop; t += timeBinning)
123  sum += rawPulse.InterpolateY(t, 1);
124 
125  const double norm = sum * timeBinning;
126 
127  // During the third iteration, we perform the normalization and
128  // appropriately adjust the times as we fill fMeanPulseShape.
129  fMeanPulseShape.Clear();
130  for (const auto& xy : rawPulse)
131  fMeanPulseShape.PushBack(xy.X() - start, xy.Y() / norm);
132 
133  // We iterate over the newly filled tabulated function once to
134  // save the point at which the pulse is at a maximumum.
135  double currentMax = 0;
136  for (const auto& th : fMeanPulseShape) {
137  const double h = th.Y();
138  if (h > currentMax) {
139  currentMax = h;
140  fPulseMaxTime = th.X();
141  }
142  }
143 }
144 
145 
146 
147 void
149  const TimeDistributionI& pe,
150  TimeDistributionD& base)
151 {
152  if (!pe.GetBinning())
153  return;
154 
155  const double dt = pe.GetBinning();
156  const double pulseStopTime = fMeanPulseShape.XBack();
157  vector<double> meanPulseShape;
158  meanPulseShape.reserve(int(pulseStopTime / dt) + 1);
159  double meanPulseShapeNorm = 0;
160 
161  for (double time = 0; time < pulseStopTime; time += dt) {
162  const double pulse = fMeanPulseShape.Y(time);
163  meanPulseShapeNorm += pulse;
164  meanPulseShape.push_back(pulse);
165  }
166 
167  const double normGain = fPMTGain / meanPulseShapeNorm;
168  const double scaleFactorMin = normGain * fMinCharge;
169  // DV: instead of changing mu = scaling * tn.get<1>() in the for-loop below
170  // to a Poi(mu) we opt for scale * charge (effectively via scaleFactorMax) so
171  // that no additional fluctuations are introduced to already increased
172  // fluctuations due to the station-level thinning
173  const double scaleFactorMax = normGain * fMaxCharge * scaling;
174 
175  for (const auto& tn : pe.SparseRange()) {
176 
177  double charge = 0;
178  for (int i = 0, n = tn.get<1>(); i < n; ++i)
179  charge += fChargeDistribution->fire();
180 
181  const double realCharge = scaleFactorMin + scaleFactorMax * charge;
182 
183  // note: get<0>() is a slot index while offset has to be in ns
184  const double offset = tn.get<0>() * dt - fPulseMaxTime;
185 
186  // place scaled pulse shape centered with max at current time bin
187  int i = -1;
188  for (double time = 0; time <= pulseStopTime; time += dt) {
189  // center pulse max on pe release time.
190  // (may not be the most accurate thing to do ...)
191  base.AddTime(time + offset, realCharge * meanPulseShape[++i]);
192  }
193 
194  }
195 }
196 
197 
198 void
200  const TimeDistributionD& totalBaseSignal)
201 {
202  for (const auto& ts : baseSignal.SparseRange()) {
203  const auto t = ts.get<0>();
204  auto& sig = baseSignal[t]; // op[] is the only way to get a reference
205  const double tot = totalBaseSignal.At(t);
206  const double maxDist = max(sig, tot);
207  const double scaling = (maxDist > 0) ? fSaturationFunction->Y(maxDist) / maxDist : 1;
208  sig *= scaling;
209  }
210 }
211 
212 
215 {
216  if (!event.HasSEvent()) {
217  ERROR("Event does not have an SEvent");
218  return eFailure;
219  }
220 
221  const bool haveFund = !fFundamentalComponents.empty();
222 
223  auto& sEvent = event.GetSEvent();
224 
225  for (auto& station : sEvent.StationsRange()) {
226 
227  if (!station.HasSimData())
228  continue;
229 
230  auto& sSim = station.GetSimData();
231 
232  const auto& simulatorSignature = sSim.GetSimulatorSignature();
233 
234  // fKFactor is the relative ratio of (QE*CE) for small PMT and standard PMT
235  // it must be applied to Small PMT signal in case of standard killing of photons
236  // (G4StationSimulatorFullOG)
237  // no kFactor needed for G4StationSimulatorFullTrackOG
238 
239  const double kFactor =
240  (simulatorSignature == "G4StationSimulatorFullTrackOG") ? 1 : fKFactor;
241 
242  const auto& dStation = det::Detector::GetInstance().GetSDetector().GetStation(station);
243 
244  const bool isUUB = dStation.IsUUB();
245 
246  // scale PE up if the particles underwent Station-based thinning
247  const double scaling =
248  (sSim.GetThinning() == 1) ?
249  1 : double(sSim.GetTotalSimCandidateParticleCount()) / sSim.GetTotalSimParticleCount();
250 
251  for (auto& pmt : station.PMTsRange(sdet::PMTConstants::eAnyType)) {
252 
253  if (!pmt.HasSimData())
254  continue;
255 
256  auto& pSim = pmt.GetSimData();
257  if (!pSim.HasPETimeDistribution() || pSim.HasBaseSignal())
258  continue;
259 
260  // Getting efficiencies gain pulse shape and charge measurements from SDetector PMT
261  const auto& dPMT = dStation.GetPMT(pmt.GetId());
262 
263  fPMTGain = dPMT.GetWorkingGain();
264 
265  if (isUUB && dPMT.GetType() == sdet::PMTConstants::eWaterCherenkovSmall)
266  fPMTGain *= kFactor;
267 
268  const auto& rawPulse = dPMT.GetPulseShape();
269  const auto& rawChargeProbDist = dPMT.GetChargeProbabilityDistribution();
270  fMinCharge = dPMT.GetMinCharge();
271  fMaxCharge = dPMT.GetMaxCharge();
272 
273  // Filling pulse shape and charge probability distribution
274  FillPulseShape(rawPulse);
275  delete fChargeDistribution;
276  fChargeDistribution =
277  new RandGeneral(fRandomEngine->GetEngine(), &rawChargeProbDist.front(), rawChargeProbDist.size());
278 
279  pSim.MakeBaseSignal();
280  auto& totalSignal = pSim.GetBaseSignal();
281 
282  for (const auto peComp : pSim.PETimeDistributionsRange()) {
283 
284  const auto comp = StationConstants::SignalComponent(peComp.GetLabel());
285 
286  if (comp == StationConstants::eTotalNoSaturation || // later
287  (haveFund && comp == StationConstants::eTotal))
288  continue;
289 
290  if (!pSim.HasBaseSignal(comp))
291  pSim.MakeBaseSignal(comp);
292  auto& base = pSim.GetBaseSignal(comp);
293 
294  ConvertPEToBaseSignal(scaling, peComp.GetTimeDistribution(), base);
295 
296  if (haveFund && fFundamentalComponents.count(comp))
297  totalSignal += base;
298 
299  }
300 
301  // eTotalNoSaturation made out of eTotal before saturation
302  if (!pSim.HasBaseSignal(StationConstants::eTotalNoSaturation))
303  pSim.MakeBaseSignal(StationConstants::eTotalNoSaturation);
304  auto& totalNoSaturationSignal = pSim.GetBaseSignal(StationConstants::eTotalNoSaturation);
305  totalNoSaturationSignal = totalSignal;
306 
307  if (fSaturationFunction)
308  SimulateSaturation(totalSignal, totalNoSaturationSignal);
309 
310  }
311 
312  sSim.SetUsedWeight(scaling);
313 
314  }
315 
316  return eSuccess;
317 }
318 
319 
322 {
323  delete fChargeDistribution;
324  fChargeDistribution = nullptr;
325  delete fSaturationFunction;
326  fSaturationFunction = nullptr;
327 
328  return eSuccess;
329 }
T At(const double time) const
unsigned int GetNPoints() const
ArrayConstReference XBack() const
read-only reference to back of array of X
fwk::VModule::ResultFlag Finish() override
Finish: invoked at end of the run (NOT end of the event)
Base class for exceptions arising because configuration data are not valid.
Class to hold collection (x,y) points and provide interpolation between them.
std::map< std::string, std::string > AttributeMap
Definition: Branch.h:24
Histogram class for time distributions with suppressed empty bins.
#define INFO(message)
Macro for logging informational messages.
Definition: ErrorLogger.h:161
std::string GetSignalComponentName(const SignalComponent comp)
#define max(a, b)
double GetBinning() const
Size of one slot.
total FADC trace, with no saturation applied by FADC/baseline simulator
ArrayConstReference XFront() const
read-only reference to front of array of X
fwk::VModule::ResultFlag Init() override
Initialize: invoked at beginning of run (NOT beginning of event)
fwk::VModule::ResultFlag Run(evt::Event &event) override
Run: invoked once per event.
void ConvertPEToBaseSignal(const double scaling, const utl::TimeDistributionI &pe, utl::TimeDistributionD &baseSignal)
void FillPulseShape(const utl::TabulatedFunction &rawPulse)
ResultFlag
Flag returned by module methods to the RunController.
Definition: VModule.h:60
double InterpolateY(const double x, const unsigned int polyDegree) const
Interpolate the Y value with a polyDegree polynomial.
double norm(utl::Vector x)
total (shower and background)
#define ERROR(message)
Macro for logging error messages.
Definition: ErrorLogger.h:165
void AddTime(const double time, const T weight=T(1))
Add an entry (optionally weighted) for the given time. Slot will be computed.
bool HasSEvent() const
const double volt
Definition: GalacticUnits.h:38
void SimulateSaturation(utl::TimeDistributionD &baseSignal, const utl::TimeDistributionD &totalBaseSignal)

, generated on Tue Sep 26 2023.