[DEE] Add drift times from depth calibration already in MSubModuleChargeTransport - #165
Conversation
| //! The drift time in ns since creation of event | ||
| double m_DriftTime; |
There was a problem hiding this comment.
Maybe the name m_DriftTime might not be correct.
It is essentially the time between the creation of the electron-hole pairs until the fast shaper on a non-charge-sharing main strip would peak, minus some offset accounting for the delay in peaking...
For non-charge-sharing main strips, this value should correspond to the drift time, but it's not the case for charge-sharing strips or nearest neighbors. Suggestions welcome ^^
There was a problem hiding this comment.
How about m_TimetoFastShaperPeak to make it very explicit? I agree m_DriftTime might be a little misleading for the non main strips.
There was a problem hiding this comment.
I like m_FastPeakTime, because it keeps the name reasonably short while still capturing what it describes. Thanks for this suggestion!
| while (IterLV2 != LVHits.end()) { | ||
| if (IterLV1->m_ROE == IterLV2->m_ROE) { | ||
| IterLV1->m_Energy += IterLV2->m_Energy; | ||
| IterLV1->m_DriftTime = IterLV1->m_Energy > IterLV2->m_Energy ? IterLV1->m_DriftTime : IterLV2->m_DriftTime; |
There was a problem hiding this comment.
The way we combine timing values when merging strip hits might not be too accurate. This works well for hits that were initially close together, but might be wrong for events that are spatially more separated.
If the fast-shaper direct signals would feature two separated peaks (usually more than 50ns apart), then it would depend on if the first peak would be above threshold etc. ...
Maybe this merging can only happen during MSubModuleDepthReadout, when we know what the fast threshold is?
There was a problem hiding this comment.
Good question as to where these gets combined. Looking at our current flow, I'm wondering if it then makes sense to bring MSubModuleDepthReadout immediate after MSubModuleChargeTransport? That allows you to apply the fast threshold and properly combine the m_DriftTimes without keeping the striphit drift times separate throughout all of the energy-focused modules in between? There may be an issues with having the Depth Readout come before the StripTrigger module, but it's not immediately jumping out at me...
This can also be kept as a TODO for now just to get something working for the majority of hits.
There was a problem hiding this comment.
Maybe we can even combine MSubModuleStripReadout and MSubModuleDepthReadout, and they are doing the same thing for energy and timing (smearing, inverse ecal/taccal, applying thresholds)?
There was a problem hiding this comment.
Not a bad idea. Let's keep that as something to explore when you address the proper combination, but leave this as it is for the current PR.
| // calculate σ and η, assuming t = z / v = z / (µ * E) | ||
| double Sigma = std::sqrt(2.0 * kB * Temperature * ΔZ / (ElementaryCharge * MeanElectricField)); // in cm | ||
| double Eta = std::cbrt(std::pow(InitialChargeCloudSize, 3) + 3.0 * N * ElementaryCharge * ΔZ / (4.0 * TMath::Pi() * Epsilon0 * EpsilonR * MeanElectricField)); // in cm | ||
| double Sigma = std::sqrt(2.0 * kB * Temperature * DeltaZ / (ElementaryCharge * MeanElectricField)); // in cm | ||
| double Eta = std::cbrt(std::pow(InitialChargeCloudSize, 3) + 3.0 * N * ElementaryCharge * DeltaZ / (4.0 * TMath::Pi() * Epsilon0 * EpsilonR * MeanElectricField)); // in cm |
There was a problem hiding this comment.
Here, we could also think about updating t = z / v = z / (µ * E) to using the simulated drift times. Two caveats though: the files have those simulated drift times already convolved with electronics, and this might be quite sensitive to how you apply the depth-calibration offset (e.g. adding it to the hole drift times or subtracting it from the electron hole drift times)
There was a problem hiding this comment.
Interesting idea. It's nice to think about everything being self-consistent. But I wondering if there are any hints that t = z / v = z / (µ * E) is not accurate enough? Can the simulations be run without electronics and still be self-consistent with the drift time sims you're using above? Maybe this is a nice-to-have improvement for the future...
There was a problem hiding this comment.
There are some hints that t = z / v = z / (µ * E) might not be accurate enough:
E(electric field) not being homogeneous in the detector due to the non-zero impurity density. The electric field strength increases linearly towards the HV side, which makes thistnon-linear inzµ(charge carrier mobility) is now set to a constant value, but is actually temperature dependent, dependent onE(saturates at higher electric fields), and technically also on the crystal axis orientation.
So, in that case, the t ~ z relationship might not necessarily hold.
There was a problem hiding this comment.
Okay, again, let's think of this as something to work towards, but leave it as is for now.
| MainSH.m_ROE.SetStripID(ID); | ||
| MainSH.m_OppositeStripID = OppositeStripID; | ||
| MainSH.m_Energy = MainStripEnergy; | ||
| MainSH.m_DriftTime = DriftTime - 50 * (1 - MainStripEnergy / SH.m_SimulatedEnergy); |
There was a problem hiding this comment.
This is how to account for the difference in timing with respect to having a non-charge-sharing main strip. I know that this implementation is very simplistic, but captures what we see well for most events that are not close to the HV/LV sides.
We have one of our undergrads (Isidro) looking into this is more detail.
I would push going for higher-order implementations in a future PR.
There was a problem hiding this comment.
Yes, agreed. Leave this simple case for now and add a TODO.
| if (g_Verbosity >= c_Warning) { | ||
| cout << "No depth calibration coefficients for pixel in DetID " << DetID << " HV " << (isLV ? OppositeStripID : ID) << " LV " << (isLV ? ID : OppositeStripID) << endl; | ||
| } | ||
| DriftTime = 1e10; |
There was a problem hiding this comment.
If we don't have stretch and offset available, I'm setting the drift times unreasonably high. Using a pixel-based inverse depth calibration, this can result in:
- if there is a HV strip without fast timing, all events collected on that strip would also give unreasonable drift times on the LV strips.
- If we were to use these charge drift times for diffusion/self-repulsion, this would screw the calculation up and result in gigantic amounts of charge sharing.
This would be avoided by transitioning from pixel-based to strip-based inverse depth calibration (see #158).
I'm also open to suggestions how to deal with events with no depth calibration.
There was a problem hiding this comment.
Hmm, good question. If we're sold on moving towards a strip-based inverse depth, then I guess whatever you implement here is temporary. But what about taking the average of some neighboring pixels as a placeholder for the stretch and offset? It's not perfect, but it's an okay guess. Note that once this goes through the forward pipeline, this pixel still won't have a valid depth calibration so it'll result in an error anyways, right?
There was a problem hiding this comment.
Yes, going towards strip-based inverse depth calibration (#158) was motivated exactly by this issue.
Taking an average for the offset might not be the best estimate (we've seen offsets between neighboring pixels jump, especially when the TAC cal was also jumping), but it's probably the best we can do for now + keep a TODO on revisiting this once we tackle #158.
Although the strip hits without depth calibration coefficients might get no valid depth calibration in the forward pipeline, this will also mess up the timing values for the neighboring strips, which have valid depth calibration. Let me think about this a bit more and how to tackle this.
| if (m_ApplyTimingResolutionCalibration == true) { | ||
| if (SH.m_IsGuardRing == false) { | ||
| unsigned int StripID = SH.m_ROE.GetStripID(); | ||
| if (SH.m_DriftTime > -200.0){ |
There was a problem hiding this comment.
This is an arbitrary number right now.
This number CAN be negative (if the charge drift time is shorter than 50ns, and the neighbor did not charge share, we might get e.g. 40ns for the main strip and -10ns for its nearest neighbor).
There was a problem hiding this comment.
Ah, this answers my question from above. Maybe just add a comment in the code as to why you choose 200 ns as a placeholder.
Why do we need this cut at all? Is this to filter out the unphysical large drift times when we don't have a valid depth calibrations (I thought those would be positive?)?
There was a problem hiding this comment.
When I played around with other parameterizations for the nearest neighbor timing values, there were some cases in which I was getting very negative values. But you are right: we should also safe-guard against the large positive default value (1e10) for strips without depth calibration here.
| // if (NNLeftStripEnergy > IonizationEnergy) { | ||
| MDEEStripHit NNLeftSH = SH; | ||
| NNLeftSH.m_Energy = NNLeftStripEnergy; | ||
| NNLeftSH.m_Energy = std::max(NNLeftStripEnergy, 0.0); |
There was a problem hiding this comment.
Note to myself: Looks like this std::max to avoid tiny negative energies (e.g. -1e-12) and therefore errors in the inverse ecal (TF1 could not find root in interval [0,16383]) is not needed if the InitialChargeCloudSize is set to something positive instead of 0.0 right now (e.g. 0.01 for 10µm initial charge cloud size)
In anticipation of PR cositools#165
ckierans
left a comment
There was a problem hiding this comment.
Sorry this took so long to get through, @fhagemann! Nice rework here. I think things are mostly pretty clear, but I do have a few questions. Nothing major.
| //! The drift time in ns since creation of event | ||
| double m_DriftTime; |
There was a problem hiding this comment.
How about m_TimetoFastShaperPeak to make it very explicit? I agree m_DriftTime might be a little misleading for the non main strips.
| DepthCalibration.SetCoeffsFileName(m_DepthCoefficientsFileName); | ||
| if (DepthCalibration.LoadCoeffsFile(m_DepthCoefficientsFileName) == true) { | ||
| // Copy depth calibration coefficients | ||
| m_Coeffs = DepthCalibration.GetCoeffs(); |
There was a problem hiding this comment.
Comparing with the previous MSubModuleDepthReadout.cxx, I see you've removed the m_Coeffs_Energy variable. I'm trying to track down what happened to that after PR #152. Are you still reliant on that variable? Is so, where are you calling it?
There was a problem hiding this comment.
I am still reliant on that variable but not here in MSubModuleChargeTransport, but in MSubModuleDepthReadout.
The depth coefficients file carry pixel information on stretch, offset, and timing resolution. m_CoeffsEnergy is used to scale the timing resolution to the strip hit energy, but irrelevant for stretch & offset.
In this PR, I'm only moving the stretch & offset part to MSubModuleChargeTransport, while keeping the timing resolution (and m_CoeffsEnergy) in MSubModuleDepthReadout (specifically in line 87):
https://github.com/fhagemann/nuclearizer/blob/bbd325ac35b957c7f6873c845c1c916bf4bdaf60/src/MSubModuleDepthReadout.cxx#L82-L87
| //! The drift time in ns since creation of event | ||
| double m_DriftTime; |
| if (ID > 0) { | ||
| NNLeftSH.m_ROE.SetStripID(ID - 1); | ||
| NNLeftSH.m_IsGuardRing = false; | ||
| // NNLeftSH.m_IsNearestNeighbor = true; |
There was a problem hiding this comment.
Will this flag then be added in the StripTrigger module instead?
There was a problem hiding this comment.
This is the plan. We only know after combining strip hits if a strip exceeded the slow threshold or not, and becomes a triggered strip or a "nearest neighbor"
There was a problem hiding this comment.
Plus, we don't know what the slow threshold is, which might only be applied later in MSubModuleStripReadout
| if (m_ApplyTimingResolutionCalibration == true) { | ||
| if (SH.m_IsGuardRing == false) { | ||
| unsigned int StripID = SH.m_ROE.GetStripID(); | ||
| if (SH.m_DriftTime > -200.0 ) { |
There was a problem hiding this comment.
Where does the -200 come from?
There was a problem hiding this comment.
This is an arbitrary threshold I chose based on the following:
m_DriftTimefor the main strip can (in principle) be as tiny as 0ns (or even slightly negative of certain values of the offset determined in the depth calibration)- nearest-neighbor strips, in this current implementation, can have values that are 50ns smaller
- With this -200, I want to keep all strip hits with "reasonable" timing (NN strips etc., offset values that we might expect), while getting rid of those with "unreasonable" timing
| cout<<"MSubModuleDepthReadout::AnalyzeEvent: Unphysical drift time."<<endl; | ||
| } | ||
| SH.m_TAC = 0; | ||
| SH.m_HasTriggered = false; |
There was a problem hiding this comment.
Shouldn't this be m_HasFastTiming == false instead of m_HasTriggered?
There was a problem hiding this comment.
Good point, I will update this.
| if (m_ApplyTimingResolutionCalibration == true) { | ||
| if (SH.m_IsGuardRing == false) { | ||
| unsigned int StripID = SH.m_ROE.GetStripID(); | ||
| if (SH.m_DriftTime > -200.0){ |
There was a problem hiding this comment.
Ah, this answers my question from above. Maybe just add a comment in the code as to why you choose 200 ns as a placeholder.
Why do we need this cut at all? Is this to filter out the unphysical large drift times when we don't have a valid depth calibrations (I thought those would be positive?)?
| cout<<"MSubModuleDepthReadout::AnalyzeEvent: Unphysical drift time."<<endl; | ||
| } | ||
| SH.m_TAC = 0; | ||
| SH.m_HasTriggered = false; |
There was a problem hiding this comment.
Same question as above about this flag. I'd need to go back to my notes/git convos, but I thought m_HasTriggered is about the slow circuit?
There was a problem hiding this comment.
Turns out MDEEStripHit did not have the flag m_HasFastTiming, so I added that now in bb05fb2.
|
Ok, I have incorporated all changes and pushed those to this PR. |
| const vector<double>& Coeffs = it->second; | ||
| double Stretch = Coeffs[0]; | ||
| double Offset = isLV ? Coeffs[1] : 0.0; | ||
| FastPeakTime = (DriftTimeSpline->Eval(Z) + Offset) * Stretch; |
There was a problem hiding this comment.
Small note: I did not rename DriftTimeSpline, because these really are drift times (plus electronics), but then changed everything else to FastPeakTime, where then timing effects of charge sharing or early peaking of nearest-neighbor strips will play an effect.
parshadkp
left a comment
There was a problem hiding this comment.
Hi @fhagemann, sorry this is super late but I was looking through the changes and found this one potential issue. Let me know what you think.
| // NNRightSH.m_IsNearestNeighbor = true; | ||
| } else { | ||
| NNRightSH.m_ROE.SetStripID(NStrips); | ||
| NNRightSH.m_IsGuardRing = true; |
There was a problem hiding this comment.
Sorry, this is super late but does this assign a GR hit even if there was no energy transported to the GR? Is this okay for a GR veto?
There was a problem hiding this comment.
Yes, it does, and it might trigger a GR veto in its current version.
But, by applying GR energy thresholds later in MSubModuleStripTrigger or MSubModuleStripReadout (I think this is not implemented right now), these events would then be filtered out.
There was a problem hiding this comment.
That makes sense. Once we have the thresholds implemented then I can check if the number of GR vetoes goes down.
This PR addresses issue #111, and is a first step to add realistic nearest-neighbor timing to the DEE.
TLDR: I'm moving applying the depth splines + stretch + offset from
MSubModuleDepthReadouttoMSubModuleChargeTransport, while keeping the smearing of timing values inMSubModuleDepthReadout,while adding an extra field to
MDEEStripHitto keep track of the expected time at which the fast-shaper signal peaks.Some background information:
Scenario 1: an event where all charge is collected on one strip, the main strip.
Scenario 2: an event where the charge is shared between two neighboring strips.
To first order, the relation between when the fast-shaper signal peaks and how much energy it records is pretty linear, i.e.
t_drift = t_unipolar - 50ns * (1 - E_recorded / E_event)The drift times for the main strips can be determined from the simulated electron and hole drift times, by applying stretch and offset from the depth calibration. For main strips with no charge sharing, the "time until fast shaper peaks" should be similar to those drift times.
In order to include simulated drift times in the DEE, we need to know which strips are main strips / NN strips and if there was charge sharing present or not, because for these the "time until fast shaper peaks" is not identical to the charge drift times, and we need additional information (basically the depth,
Zand if/how much energy was charge-shared), all of which is only available inMSubModuleChargeTransport. Therefore, the determination of charge drift times + stretch + offset is moved toMSubModuleChargeTransport, and for now we apply nearest-neighbor timing by using the simplistic linear relationshipt_drift = t_unipolar - 50ns * (1 - E_recorded / E_event).