Skip to content

[DEE] Add drift times from depth calibration already in MSubModuleChargeTransport#165

Open
fhagemann wants to merge 3 commits into
cositools:develop/emfrom
fhagemann:feature/NN
Open

[DEE] Add drift times from depth calibration already in MSubModuleChargeTransport#165
fhagemann wants to merge 3 commits into
cositools:develop/emfrom
fhagemann:feature/NN

Conversation

@fhagemann

@fhagemann fhagemann commented Jun 15, 2026

Copy link
Copy Markdown

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 MSubModuleDepthReadout to MSubModuleChargeTransport, while keeping the smearing of timing values in MSubModuleDepthReadout,
while adding an extra field to MDEEStripHit to 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.

    • The fast-shaper signal of the main strip is unipolar, with a very pronounced peak. We use that for timing and depth calibration.
    • The nearest-neighbor (spectator) strips typically feature a bipolar fast-shaper. The fast-shaper signal for these strips features a peak-dip structure that peaks ~50ns earlier than the main strip (for most of the detector depth, ignoring events VERY close to the HV and LV sides)
    Screenshot from 2026-06-15 13-50-19
  • Scenario 2: an event where the charge is shared between two neighboring strips.

    • Here, the fast-shaper of both collecting strips is a superposition of the above-mentioned unipolar (direct) signal, and the bipolar (transient) signal, resulting both of them to peak earlier compared the direct signal in scenario 1.
    Screenshot from 2026-06-15 13-48-11

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, Z and if/how much energy was charge-shared), all of which is only available in MSubModuleChargeTransport. Therefore, the determination of charge drift times + stretch + offset is moved to MSubModuleChargeTransport, and for now we apply nearest-neighbor timing by using the simplistic linear relationship t_drift = t_unipolar - 50ns * (1 - E_recorded / E_event).

@fhagemann fhagemann added the DEE Development related to the detector effects engine label Jun 15, 2026
Comment thread include/MDEEStripHit.h
Comment on lines +91 to +92
//! The drift time in ns since creation of event
double m_DriftTime;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ^^

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;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment on lines 367 to +369
// 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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)


// create MDEEStripHit for the left NN
if (NNLeftStripEnergy > IonizationEnergy) {
// if (NNLeftStripEnergy > IonizationEnergy) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, I'm keeping all events, no matter if their energy is 0, just to keep the nearest-neighbor timing information.

MainSH.m_ROE.SetStripID(ID);
MainSH.m_OppositeStripID = OppositeStripID;
MainSH.m_Energy = MainStripEnergy;
MainSH.m_DriftTime = DriftTime - 50 * (1 - MainStripEnergy / SH.m_SimulatedEnergy);

@fhagemann fhagemann Jun 15, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +142 to +144
vector<double> Coeffs = m_Coeffs[PixelCode];
double CTD_FWHM = Coeffs[2] * m_Coeffs_Energy / SH.m_Energy;
double CTD_Sigma = CTD_FWHM / 2.355;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MSubModuleDepthReadout now only smears the final timing values and applies the inverse TAC calibration. Everything else is done in MSubModuleChargeTransport now.

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;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

if (m_ApplyTimingResolutionCalibration == true) {
if (SH.m_IsGuardRing == false) {
unsigned int StripID = SH.m_ROE.GetStripID();
if (SH.m_DriftTime > -200.0){

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

@fhagemann fhagemann marked this pull request as ready for review June 15, 2026 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DEE Development related to the detector effects engine

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant