ARTEMIS-CRIB
 
Loading...
Searching...
No Matches
TTGTIKProcessor.cc
Go to the documentation of this file.
1/**
2 * @file TTGTIKProcessor.cc
3 * @brief Implementation of the TTGTIKProcessor class.
4 * @author Kodai Okawa <okawa@cns.s.u-tokyo.ac.jp>
5 * @date 2023-08-01 22:35:07
6 * @note last modified: 2025-03-05 21:44:31
7 * @details bisection method (not Newton method)
8 */
9
10#include "TTGTIKProcessor.h"
11#include "../TProcessorUtil.h"
12
14#include "TReactionInfo.h"
15#include <Mass.h> // TSrim library
16#include <TFile.h>
17#include <TGraph.h>
18#include <TKey.h>
19#include <TLorentzVector.h>
20#include <TRandom.h>
21
22/// ROOT macro for class implementation
24
25namespace art::crib {
26
28 : fInData(nullptr),
29 fInTrackData(nullptr),
30 fOutData(nullptr),
31 srim(nullptr) {
32 RegisterInputCollection("InputCollection", "Input collection of telescope data objects (derived from TTelescopeData)",
33 fInputColName, TString("tel"));
34 RegisterInputCollection("InputTrackCollection", "Input collection of tracking data objects (derived from TTrack)",
35 fInputTrackColName, TString("track"));
36 RegisterOutputCollection("OutputCollection", "Output collection containing reaction reconstruction information using the TGTIK method",
37 fOutputColName, TString("result"));
38 RegisterProcessorParameter("DetectorParameter", "Name of the telescope parameter collection (detector parameters)",
39 fDetectorParameterName, TString("prm_detectors"));
40 RegisterProcessorParameter("TargetParameter", "Name of the target parameter collection",
41 fTargetParameterName, TString("prm_targets"));
42
43 IntVec_t init_i_vec;
44 RegisterProcessorParameter("InitialBeamEnergy", "Initial beam energy (in MeV) immediately after the exit window",
46 RegisterProcessorParameter("TargetName", "Name of the target gas (used in TSrim calculation)",
47 fTargetName, TString(""));
48 RegisterProcessorParameter("TargetPressure", "Target gas pressure in Torr",
49 fPressure, 0.0);
50 RegisterProcessorParameter("TargetTemperature", "Target gas temperature in Kelvin",
51 fTemperature, 0.0);
52
53 RegisterProcessorParameter("ParticleZArray", "Array of atomic numbers (Z) for reaction particles",
54 fParticleZArray, init_i_vec);
55 RegisterProcessorParameter("ParticleAArray", "Array of mass numbers (A) for reaction particles",
56 fParticleAArray, init_i_vec);
57 RegisterProcessorParameter("ExcitedEnergy", "Excited state energy (MeV); use a negative value if not applicable",
58 fExcitedEnergy, -1.0);
59
60 // custom function
61 RegisterProcessorParameter("UseCustomFunction", "Flag to enable custom processing functions for additional corrections",
62 fDoCustom, false);
63 RegisterProcessorParameter("UseCenterPosition", "Flag to use the detector's center position (useful when the DSSSD is not operational)",
64 fDoCenterPos, false);
65}
66
67// free the memory
69 delete fOutData;
70 fOutData = nullptr;
71 delete srim;
72 srim = nullptr;
73}
74
75/**
76 * @details
77 * This function prepares necessary input and output objects, validates parameters,
78 * computes mass values for the reaction, initializes the TSrim object for energy loss calculations,
79 * and sets up the output collection.
80 */
81void TTGTIKProcessor::Init(TEventCollection *col) {
82 Info("Init", "%s, %s => %s", fInputColName.Data(), fInputTrackColName.Data(), fOutputColName.Data());
83
84 // Retrieve the input telescope data collection.
86 col, fInputColName, "TClonesArray", "art::crib::TTelescopeData");
87 if (std::holds_alternative<TString>(result)) {
88 SetStateError(std::get<TString>(result));
89 return;
90 }
91 fInData = std::get<TClonesArray **>(result);
92
93 // Retrieve the input tracking data collection.
94 auto result_track = util::GetInputObject<TClonesArray>(
95 col, fInputTrackColName, "TClonesArray", "art::TTrack");
96 if (std::holds_alternative<TString>(result_track)) {
97 SetStateError(std::get<TString>(result_track));
98 return;
99 }
100 fInTrackData = std::get<TClonesArray **>(result_track);
101
102 // Retrieve the input detector parameter object.
103 auto result_det_prm = util::GetParameterObject<TClonesArray>(
104 col, fDetectorParameterName, "TClonesArray", "art::crib::TDetectorParameter");
105 if (std::holds_alternative<TString>(result_det_prm)) {
106 SetStateError(std::get<TString>(result_det_prm));
107 return;
108 }
109 fDetectorPrm = std::get<TClonesArray *>(result_det_prm);
110
111 // Retrieve the input target parameter object.
112 auto result_tar_prm = util::GetParameterObject<TClonesArray>(
113 col, fTargetParameterName, "TClonesArray", "art::crib::TTargetParameter");
114 if (std::holds_alternative<TString>(result_tar_prm)) {
115 SetStateError(std::get<TString>(result_tar_prm));
116 return;
117 }
118 fTargetPrm = std::get<TClonesArray *>(result_tar_prm);
119
120 if (fParticleZArray.size() != 4 || fParticleAArray.size() != 4) {
121 SetStateError("Particle array size should be 4 in the steering file");
122 return;
123 }
124
125 // unit = mass (MeV)
126 // Calculate masses for reaction particles (unit: MeV).
127 M1 = amdc::Mass(fParticleZArray[0], fParticleAArray[0]) * amdc::amu;
128 M2 = amdc::Mass(fParticleZArray[1], fParticleAArray[1]) * amdc::amu;
129 M3_default = amdc::Mass(fParticleZArray[2], fParticleAArray[2]) * amdc::amu;
130 M4 = amdc::Mass(fParticleZArray[3], fParticleAArray[3]) * amdc::amu;
131
132 if (!fDoCustom && fExcitedEnergy > 0)
134 else
135 M3 = M3_default;
136
137 Info("Init", "reconstract the reaction: %d%s + %d%s -> %d%s + %d%s (detected)",
138 fParticleAArray[0], amdc::GetEl(fParticleZArray[0]).c_str(),
139 fParticleAArray[1], amdc::GetEl(fParticleZArray[1]).c_str(),
140 fParticleAArray[2], amdc::GetEl(fParticleZArray[2]).c_str(),
141 fParticleAArray[3], amdc::GetEl(fParticleZArray[3]).c_str());
142 Info("Init", "\tQ-value: %lf MeV", (M1 + M2) - (M3 + M4));
143
144 // Initialize the TSrim object.
145 const char *tsrim_path = std::getenv("TSRIM_DATA_HOME");
146 if (!tsrim_path) {
147 SetStateError("TSRIM_DATA_HOME environment variable is not defined");
148 return;
149 }
150 srim = new TSrim();
151 srim->AddElement("srim", 16, Form("%s/%s/range_fit_pol16_%s.txt", tsrim_path, fTargetName.Data(), fTargetName.Data()));
152
153 // Prepare the output collection for reaction information.
154 fOutData = new TClonesArray("art::crib::TReactionInfo");
155 fOutData->SetName(fOutputColName);
156 col->Add(fOutputColName, fOutData, fOutputIsTransparent);
157
158 // Seed the random number generator. (used in custom function)
159 gRandom->SetSeed(time(nullptr));
160}
161
162/**
163 * @details
164 * This function processes the input telescope and tracking data to reconstruct the reaction information.
165 */
167 fOutData->Clear("C");
168 if (!fInData) {
169 Warning("Process", "No input data object");
170 return;
171 }
172
173 const int nData = (*fInData)->GetEntriesFast();
174 if (nData == 0)
175 return;
176 else if (nData != 1) {
177 Warning("Process", "The input %s entry number is not 1, but %d.",
178 fInputColName.Data(), nData);
179 }
180
181 const int nTrackData = (*fInTrackData)->GetEntriesFast();
182 if (nTrackData == 0)
183 return;
184 if (nTrackData != 1) {
185 Warning("Process", "The input %s entry number is not 1 but %d.",
186 fInputTrackColName.Data(), nTrackData);
187 }
188
189 const auto *Data = dynamic_cast<const TTelescopeData *>((*fInData)->At(0));
190 const auto *TrackData = dynamic_cast<const TTrack *>((*fInTrackData)->At(0));
191
192 if (!IsValid(Data->GetTelID()))
193 return;
194
195 // Apply an energy threshold: skip events with total energy below 0.01.
196 if (Data->GetEtotal() < 0.01)
197 return;
198
199 // Process excited state energy.
200 Double_t excited_energy = 0.0;
201 ///////////////////////////////////////////
202 // custom process (currently for 26Si(a, p) analysis)
203 ///////////////////////////////////////////
204 if (fDoCustom) {
205 excited_energy = GetCustomExcitedEnergy(Data->GetTelID(), Data->GetEtotal());
206 if (excited_energy < -1.0)
207 return; // assuming (a, 2p) process
208 M3 = M3_default + excited_energy;
209 } else if (fExcitedEnergy > 0)
210 excited_energy = fExcitedEnergy;
211
212 Double_t reac_z = GetReactionPosition(TrackData, Data);
213 if (!IsValid(reac_z))
214 return;
215
216 // debug
217 // std::cout << reac_z << std::endl;
218
219 auto *outData = static_cast<TReactionInfo *>(fOutData->ConstructedAt(0));
220 outData->SetID(0);
221 outData->SetXYZ(TrackData->GetX(reac_z), TrackData->GetY(reac_z), reac_z);
222
223 Double_t Ecm = GetEcmFromBeam(reac_z, TrackData);
224 outData->SetEnergy(Ecm);
225
226 auto [ELab, ALab] = GetELabALabPair(reac_z, TrackData, Data);
227 outData->SetTheta(180.0 - (180.0 / TMath::Pi()) * GetCMAngle(ELab, Ecm, ALab));
228 outData->SetExEnergy(excited_energy);
229}
230
231/**
232 * @details
233 * This function computes the reaction position (z-coordinate) by employing the bisection method.
234 * Although an alternative Newton method is available, the bisection method is preferred for its robustness.
235 * Newton method is not implemented yet.
236 */
237Double_t TTGTIKProcessor::GetReactionPosition(const TTrack *track, const TTelescopeData *data) {
238 // return newton(track, data);
239 return bisection(track, data);
240}
241
242/**
243 * @details
244 * Currently it is unavailable, return kInvalidD.
245 */
246Double_t TTGTIKProcessor::newton(const TTrack *, const TTelescopeData *) {
247 return kInvalidD;
248}
249
250/**
251 * @details
252 * This function first performs a rough search over the interval [kInitialMin, kInitialMax] to
253 * identify a valid subinterval where the target function is defined and exhibits a sign change.
254 * It then applies the standard bisection method within that subinterval to converge on a zero.
255 */
256Double_t TTGTIKProcessor::bisection(const TTrack *track, const TTelescopeData *data) {
257 // Stage 1: Rough bracketing within [kInitialMin, kInitialMax].
258 const Int_t numSteps = 10;
259 const Double_t stepSize = (kInitialMax - kInitialMin) / static_cast<Double_t>(numSteps);
260 bool signChanged = false;
261 bool firstValid = true;
262 Double_t z_low = kInitialMin; // Bracketing lower bound (to be determined)
263 Double_t z_high = kInitialMax; // Bracketing upper bound (to be determined)
264 Double_t prev_z = 0.0, prev_f = 0.0;
265
266 // Single loop: Identify valid function values and detect a sign change.
267 for (Int_t i = 0; i <= numSteps; ++i) {
268 Double_t current_z = kInitialMin + i * stepSize;
269 Double_t current_f = TargetFunction(current_z, track, data);
270 if (!IsValid(current_f))
271 continue; // Skip invalid function values.
272 if (firstValid) {
273 // First valid sample found.
274 prev_z = current_z;
275 prev_f = current_f;
276 z_low = current_z; // Initialize bracket lower bound.
277 z_high = current_z; // Initialize bracket upper bound.
278 firstValid = false;
279 } else {
280 z_high = current_z; // Update bracket upper bound with the current valid value.
281 if (prev_f * current_f < 0.0) {
282 // Sign change detected between the previous valid sample and the current one.
283 z_low = prev_z;
284 z_high = current_z;
285 signChanged = true;
286 break;
287 }
288 // Update previous valid sample.
289 prev_z = current_z;
290 prev_f = current_f;
291 }
292 }
293 if (firstValid) {
294 Warning("bisection", "No valid function value found in the initial range. (E = %lf)", data->GetEtotal());
295 return kInvalidD;
296 }
297 if (!signChanged) {
298 Warning("bisection", "Could not find a valid zero crossing in the initial range. (E = %lf)", data->GetEtotal());
299 return kInvalidD;
300 }
301
302 // Validate the bracket: Ensure both endpoints are valid and have opposite signs.
303 Double_t f_low = TargetFunction(z_low, track, data);
304 Double_t f_high = TargetFunction(z_high, track, data);
305 if (!IsValid(f_low) || !IsValid(f_high)) {
306 Warning("bisection", "f(z_low) or f(z_high) is invalid.");
307 return kInvalidD;
308 }
309 if (f_low * f_high > 0.0) {
310 Warning("bisection", "f(z_low) and f(z_high) do not bracket a zero: L = %lf / H = %lf, E = %lf",
311 f_low, f_high, data->GetEtotal());
312 return kInvalidD;
313 }
314 if (z_low == kInitialMin || z_high == kInitialMax) {
315 Warning("bisection", "Bracketing failed: interval touches boundaries.");
316 return kInvalidD;
317 }
318
319 // Stage 2: Standard bisection method within the bracket [z_low, z_high].
320 Double_t left = z_low;
321 Double_t right = z_high;
322 Double_t middle = 0.0;
323 Int_t iteration = 0;
324 Double_t f_left = f_low;
325
326 while (TMath::Abs(right - left) > kEpsilon) {
327 middle = (left + right) / 2.0;
328 Double_t f_middle = TargetFunction(middle, track, data);
329 if (!IsValid(f_left) || !IsValid(f_middle))
330 return kInvalidD;
331
332 if (f_left * f_middle < 0.0) {
333 right = middle;
334 } else {
335 left = middle;
336 f_left = f_middle;
337 }
338
339 iteration++;
340 if (iteration >= kMaxIteration) {
341 Warning("bisection", "Convergence not achieved within maximum iteration!");
342 return kInvalidD;
343 }
344 }
345 return middle;
346}
347
348/**
349 * @details
350 * This function calculates two center-of-mass energies based on an assumed reaction
351 * position (z):
352 * - Ecm(beam): Calculated from beam information.
353 * - Ecm(detected): Calculated from detected particle information.
354 *
355 * The target function is defined as:
356 * f(z) = Ecm(beam) - Ecm(detected)
357 *
358 * A zero crossing of this function indicates that the assumed z position corresponds
359 * to the true reaction position. The (x, y, z) coordinates are then determined from the tracking data.
360 */
361Double_t TTGTIKProcessor::TargetFunction(Double_t z, const TTrack *track, const TTelescopeData *data) {
362 Double_t Ecm_beam = GetEcmFromBeam(z, track);
363 Double_t Ecm_detect = GetEcmFromDetectParticle(z, track, data);
364 if (!IsValid(Ecm_beam) || !IsValid(Ecm_detect)) {
365 return kInvalidD;
366 }
367 return Ecm_beam - Ecm_detect;
368}
369
370/**
371 * @details
372 * This function calculates the center-of-mass energy (Ecm) based on the beam's kinematics
373 * and its energy loss when traversing the target material. The beam is considered to be
374 * the first particle (Z1) of the reaction system [Z1, Z2, Z3, Z4].
375 *
376 * The procedure is as follows:
377 * - Compute the beam's flight vector from its initial position (at z = 0) to the assumed
378 * reaction position z.
379 * - Determine the effective path length through the target by applying a sign based on z.
380 * - Use the TSrim library to compute the residual energy after energy loss.
381 * - Calculate the beam's velocity (β) from its kinetic energy.
382 * - Obtain the beam's direction from the track angles (assumed small so that tan(theta) approximations hold),
383 * and compute the normalized beta vector.
384 * - Boost a beam TLorentzVector (initially at rest with mass M1) using this beta vector so that its energy becomes M1 + kinetic energy.
385 * - Construct a target TLorentzVector at rest (mass M2), sum with the beam, and boost to the center-of-mass frame.
386 * - Finally, return the total kinetic energy in the center-of-mass frame, which is the sum of (E - M) for both particles.
387 */
388Double_t TTGTIKProcessor::GetEcmFromBeam(Double_t z, const TTrack *track) {
389 // Determine the sign for the effective target thickness based on z.
390 // (Positive z implies forward thickness; negative z implies reverse thickness.)
391 Int_t sign = z > 0 ? 1 : -1;
392
393 // Calculate the beam flight vector from the initial position (z = 0) to the assumed reaction position.
394 TVector3 beam_flight(track->GetX(z) - track->GetX(0),
395 track->GetY(z) - track->GetY(0),
396 z);
397
398 // Calculate the residual energy after energy loss in the target.
399 // The effective path length is given by the magnitude of beam_flight multiplied by the sign.
400 Double_t energy = srim->EnergyNew(fParticleZArray[0],
403 std::string(fTargetName.Data()),
404 sign * beam_flight.Mag(),
405 fPressure,
407 if (energy < 0.01)
408 return 0.0;
409
410 // Calculate the beam's velocity (beta) from the relativistic relation:
411 // beta = sqrt(1 - (M1/(M1 + kineticEnergy))^2)
412 Double_t beta = TMath::Sqrt(1.0 - TMath::Power(M1 / (M1 + energy), 2));
413
414 // Retrieve the track angles for the beam direction.
415 Double_t ang[2] = {track->GetA(), track->GetB()};
416
417 // Compute the norm of the direction vector (tan(angleA), tan(angleB), 1).
418 Double_t norm = TMath::Sqrt(TMath::Power(TMath::Tan(ang[0]), 2) +
419 TMath::Power(TMath::Tan(ang[1]), 2) + 1.0);
420
421 // Calculate the beta vector components by normalizing the direction.
422 Double_t betaX = beta * TMath::Tan(ang[0]) / norm;
423 Double_t betaY = beta * TMath::Tan(ang[1]) / norm;
424 Double_t betaZ = beta / norm;
425 TVector3 betaVector(betaX, betaY, betaZ);
426
427 // Construct a TLorentzVector for the beam particle at rest with mass M1.
428 TLorentzVector beam(0., 0., 0., M1);
429 // Boost the beam vector to obtain its 4-momentum (energy becomes M1 + energy).
430 beam.Boost(betaVector);
431
432 // Construct a TLorentzVector for the target particle at rest with mass M2.
433 TLorentzVector target(0., 0., 0., M2);
434
435 // Compute the total 4-momentum and determine the boost vector for the center-of-mass frame.
436 TLorentzVector cm_vec = beam + target;
437 TVector3 beta_cm = cm_vec.BoostVector();
438 // Boost both beam and target into the center-of-mass frame.
439 beam.Boost(-beta_cm);
440 target.Boost(-beta_cm);
441
442 // Return the total kinetic energy in the center-of-mass frame.
443 // Kinetic energy is computed as (E - M) for each particle.
444 return (beam.E() - beam.M()) + (target.E() - target.M());
445}
446
447/**
448 * @details
449 * This function computes the center-of-mass energy (Ecm) using the laboratory energy and angle
450 * of the detected particle obtained from the assumed reaction position (z). Currently, it employs
451 * classical kinematics for the calculation.
452 */
453Double_t TTGTIKProcessor::GetEcmFromDetectParticle(Double_t z, const TTrack *track, const TTelescopeData *data) {
454 auto [energy, theta] = GetELabALabPair(z, track, data);
455 if (!IsValid(energy))
456 return kInvalidD;
457
458 // kinematics (using bisection method) detected particle id=3
459 // relative kinematics (not implemented)
460 // return GetEcm_kinematics(energy, theta, 0.01, 1.0e+4);
461
462 // classic kinematics
463 return GetEcm_classic_kinematics(energy, theta);
464}
465
466/**
467 * @details
468 * This function calculates the LAB energy (ELab) and LAB angle (ALab) of the detected particle,
469 * based on an assumed reaction position (z). It retrieves the detector parameters corresponding to
470 * the telescope ID, computes the effective detection position using either the strip information or
471 * the detector center position (depending on the flag), and then applies the TSrim library to
472 * determine the energy loss. The LAB angle is computed as the angle between the track direction and
473 * the vector from the reaction position to the detection position.
474 */
475std::pair<Double_t, Double_t> TTGTIKProcessor::GetELabALabPair(Double_t z, const TTrack *track, const TTelescopeData *data) {
476 // Retrieve detector parameters based on the telescope ID.
477 Int_t tel_id = data->GetTelID();
478 const TDetectorParameter *Prm = static_cast<const TDetectorParameter *>(fDetectorPrm->At(tel_id - 1));
479 if (!Prm) {
480 Warning("GetELabALabPair", "Parameter is not found");
481 return {kInvalidD, kInvalidD};
482 }
483
484 // Obtain the number of strips and calculate individual strip sizes for X and Y directions.
485 Int_t stripNum[2] = {Prm->GetStripNum(0), Prm->GetStripNum(1)};
486 Int_t stripID[2] = {data->GetXID(), data->GetYID()};
487 Double_t stripSize[2] = {Prm->GetSize(0) / static_cast<Double_t>(stripNum[0]),
488 Prm->GetSize(1) / static_cast<Double_t>(stripNum[1])};
489
490 // Determine the detection position along X.
491 Double_t det_x = 0.0;
492 if (fDoCenterPos) {
493 // Use the detector center position.
494 det_x = Prm->GetSize(0) / 2.0;
495 } else if (stripID[0] >= 0 && stripID[0] < stripNum[0]) {
496 // Use the position corresponding to the valid strip ID.
497 det_x = -Prm->GetSize(0) / 2.0 + stripSize[0] * static_cast<Double_t>(stripID[0]);
498 } else {
499 // Default to the detector center if stripID is invalid.
500 det_x = Prm->GetSize(0) / 2.0;
501 }
502
503 // Determine the detection position along Y.
504 Double_t det_y = 0.0;
505 if (fDoCenterPos)
506 det_y = Prm->GetSize(1) / 2.0;
507 else if (stripID[1] >= 0 && stripID[1] < stripNum[1])
508 det_y = -Prm->GetSize(1) / 2.0 + stripSize[1] * (Double_t)stripID[1];
509 else
510 det_y = Prm->GetSize(1) / 2.0;
511
512 // Build the detection position vector.
513 TVector3 detect_position(det_x, det_y, Prm->GetDistance());
514 // Apply rotation about the Y-axis according to the detector angle.
515 detect_position.RotateY(Prm->GetAngle()); // rad
516 // Apply translation to adjust for the detector's rotational center.
517 detect_position += TVector3(Prm->GetCenterRotPos(0),
518 Prm->GetCenterRotPos(1),
519 Prm->GetCenterRotPos(2));
520
521 // Determine the reaction position based on tracking data.
522 TVector3 reaction_position(track->GetX(z), track->GetY(z), z);
523 // Calculate the approximate track direction using positions at z = 0 and z = 1.
524 TVector3 track_direction(track->GetX(1.) - track->GetX(0.),
525 track->GetY(1.) - track->GetY(0.),
526 1.0);
527
528 // Compute the LAB angle as the angle between the track direction and the vector from the reaction position to the detection position.
529 Double_t theta = track_direction.Angle(detect_position - reaction_position); // LAB, rad
530
531 // Use TSrim to calculate the LAB energy for the detected particle (assumed particle ID = 3).
532 Double_t energy = srim->EnergyNew(fParticleZArray[3], fParticleAArray[3], // id = 3 particle
533 data->GetEtotal(), std::string(fTargetName.Data()),
534 -(detect_position - reaction_position).Mag(),
536 return {energy, theta};
537}
538
539/**
540 * @details
541 * This function is intended to compute the center-of-mass energy (Ecm) from the detected
542 * laboratory energy and angle using relativistic formulas. It is designed to be used in the
543 * GetEcmFromDetectParticle method. However, the relativistic kinematics implementation is
544 * currently not available, and this function always returns kInvalidD.
545 */
546Double_t TTGTIKProcessor::GetEcm_kinematics(Double_t, Double_t, Double_t, Double_t) {
547 return kInvalidD;
548}
549
550/**
551 * @details
552 * This function calculates the center-of-mass energy (Ecm) from the detected particle's
553 * laboratory energy and angle using classical (non-relativistic) kinematics. It is used in the
554 * GetEcmFromDetectParticle method. The formulas employed here are based on those detailed in
555 * Okawa's master thesis.
556 */
557Double_t TTGTIKProcessor::GetEcm_classic_kinematics(Double_t energy, Double_t theta) {
558 // Compute kinematic factors based on the masses:
559 // alpha: factor from the beam (particle 1) and target (particle 2) system.
560 // beta: factor from the detected particle (particle 4) and the complementary fragment (particle 3).
561 Double_t alpha = (M2 * (M1 + M2)) / (2.0 * M1);
562 Double_t beta = (M4 * (M3 + M4)) / (2.0 * M3);
563 // Q-value of the reaction.
564 Double_t qvalue = (M1 + M2) - (M3 + M4);
565
566 // Calculate the velocity of the detected particle (particle 4) from its kinetic energy.
567 // Using the classical relation: energy = 0.5 * M4 * v4^2 => v4 = sqrt(2 * energy / M4)
568 Double_t v4 = TMath::Sqrt(2.0 * energy / M4);
569
570 // Elastic scattering case: when alpha and beta are nearly equal.
571 if (TMath::Abs(alpha - beta) < 1.0e-5) {
572 Double_t cosTheta = TMath::Cos(theta);
573 if (TMath::Abs(cosTheta) < 1.0e-5) {
574 Warning("GetEcm_classic_kinematics", "cos(theta) is too small in elastic scattering calculation");
575 return kInvalidD;
576 }
577 // Calculate the center-of-mass velocity for elastic scattering.
578 Double_t vcm_elastic = -(qvalue - beta * v4 * v4) / (2.0 * beta * v4 * cosTheta);
579 if (vcm_elastic < 0) {
580 Warning("GetEcm_classic_kinematics", "vcm < 0! : vcm = %lf, det_energy = %lf, theta = %lf",
581 vcm_elastic, energy, theta);
582 return kInvalidD;
583 }
584 return alpha * vcm_elastic * vcm_elastic;
585 }
586
587 // Non-elastic scattering case:
588 // Solve the quadratic equation for v_cm: v_cm = -b + sqrt(b^2 - c),
589 // where b = (beta * v4 * cos(theta)) / (alpha - beta) and
590 // c = (qvalue - beta * v4 * v4) / (alpha - beta)
591 Double_t denominator = (alpha - beta);
592 Double_t b = (beta * v4 * TMath::Cos(theta)) / denominator;
593 Double_t c = (qvalue - beta * v4 * v4) / denominator;
594 Double_t D = b * b - c;
595 if (D < 0) {
596 if (TMath::Abs(D) < 1.0e-5) {
597 D = 0.0;
598 } else {
599 Warning("GetEcm_classic_kinematics", "b^2 - c = %lf < 0, det_energy = %lf, theta = %lf",
600 D, energy, theta);
601 return kInvalidD;
602 }
603 }
604
605 Double_t vcm = -b + TMath::Sqrt(D);
606 if (vcm < 0) {
607 Warning("GetEcm_classic_kinematics", "vcm < 0!, vcm = %lf + %lf, det_energy = %lf, theta = %lf",
608 -b, TMath::Sqrt(D), energy, theta);
609 return kInvalidD;
610 }
611
612 return alpha * vcm * vcm;
613}
614
615/**
616 * @details
617 * This function calculates the CM scattering angle (θ_cm) based on the detected particle's
618 * laboratory energy (ELab) and laboratory scattering angle (ALab), as well as the center-of-mass
619 * energy (Ecm). The calculation employs classical kinematics, using coefficients derived from
620 * the masses of the reaction participants. In particular:
621 * - α is computed from the beam (particle 1) and target (particle 2) masses.
622 * - β is computed from the detected particle (particle 4) and the complementary fragment (particle 3) masses.
623 * - q-value represents the mass difference between the entrance and exit channels.
624 * The function then computes the classical velocities of the detected particle in the LAB frame (v4),
625 * the beam velocity in the CM frame (v_cm), and the detected particle's velocity in the CM frame (v4_cm).
626 * Finally, the CM angle is obtained from the cosine relationship:
627 * θ_cm = acos((v4*cos(ALab) - v_cm) / v4_cm).
628 */
629Double_t TTGTIKProcessor::GetCMAngle(Double_t ELab, Double_t Ecm, Double_t ALab) {
630 // Calculate kinematic coefficients from the particle masses.
631 Double_t alpha = (M2 * (M1 + M2)) / (2.0 * M1);
632 Double_t beta = (M4 * (M3 + M4)) / (2.0 * M3);
633 Double_t qvalue = (M1 + M2) - (M3 + M4);
634
635 // Compute the detected particle's velocity in the LAB frame (classical approximation).
636 Double_t v4 = TMath::Sqrt(2.0 * ELab / M4);
637 // Compute the effective beam velocity in the center-of-mass frame.
638 Double_t vcm = TMath::Sqrt(Ecm / alpha);
639 // Compute the detected particle's velocity in the center-of-mass frame.
640 Double_t v4cm = TMath::Sqrt((Ecm + qvalue) / beta);
641
642 // Calculate the CM scattering angle using the cosine law.
643 Double_t theta_cm = TMath::ACos((v4 * TMath::Cos(ALab) - vcm) / v4cm);
644 return theta_cm;
645}
646
647/**
648 * @details
649 * This is used for 26Si(a, p)29P analysis.
650 * This function uses a custom random generator ROOT file (specific to this analysis)
651 * to assign an excited state energy for 29P based on TALYS simulation data. It reads
652 * graphs from a directory corresponding to the given telescope ID, computes the ratio
653 * of excited state contributions as a function of energy, and then, using a uniform
654 * random number, selects one excited state.
655 *
656 * NOTE: This function is not designed for generic use and performs file I/O on an
657 * event-by-event basis, which is inefficient.
658 */
659Double_t TTGTIKProcessor::GetCustomExcitedEnergy(Int_t telID, Double_t Etotal) {
660 // Up to 40th excited states of 29P
661 Int_t ex_id = 0;
662 const Double_t ex_energys[42] = {
663 0.0,
664 1.38355,
665 1.95391,
666 2.4227,
667 3.1059,
668 3.4476,
669 4.0805,
670 4.343001,
671 4.642001,
672 4.759001,
673 4.95410142,
674 5.0470012,
675 5.293002,
676 5.527001,
677 5.583001,
678 5.716001,
679 5.740001,
680 5.826001,
681 5.968,
682 6.191,
683 6.328,
684 6.505,
685 6.577,
686 6.828,
687 6.956,
688 7.021,
689 7.148,
690 7.272,
691 7.361,
692 7.456,
693 7.523,
694 7.641,
695 7.755,
696 7.95,
697 7.998,
698 8.106,
699 8.234,
700 8.297,
701 8.379,
702 8.432,
703 8.51,
704 -100.0,
705 };
706 Double_t max_energy = 30.0; // MeV
707 if (Etotal > max_energy)
708 return 0.0;
709
710 // Open the custom ROOT file.
711 TString filepath = "/home/okawa/art_analysis/develop/develop/ana2/random_generator.root";
712 TFile *file = TFile::Open(filepath);
713 if (!file || file->IsZombie()) {
714 Warning("GetCustomExcitedEnergy", "Error opening file: %s", filepath.Data());
715 if (file)
716 delete file;
717 return 0.0;
718 }
719
720 // Get the directory for the given telescope ID.
721 TDirectory *dir = dynamic_cast<TDirectory *>(file->Get(Form("tel%d", telID)));
722 if (!dir) {
723
724 Warning("GetCustomExcitedEnergy", "Error opening directory: tel%d", telID);
725 file->Close();
726 delete file;
727 return 0.0;
728 }
729
730 // Read TGraph objects from the directory.
731 std::vector<TGraph *> graphs;
732 std::vector<TGraph *> grs_ratio;
733 TList *keys = dir->GetListOfKeys();
734 TIter next(keys);
735 TKey *key = nullptr;
736 while ((key = (TKey *)next())) {
737 TObject *obj = key->ReadObj();
738 if (obj && obj->InheritsFrom(TGraph::Class())) {
739 graphs.emplace_back(static_cast<TGraph *>(obj));
740 TGraph *g = new TGraph();
741 grs_ratio.emplace_back(g);
742 } else {
743 delete obj;
744 }
745 }
746
747 // Build the ratio graphs over an energy range from 5.0 to max_energy (step 0.1).
748 Int_t i_point = 0;
749 for (Double_t ene = 5.0; ene < max_energy; ene += 0.1) {
750 Double_t total = 0.0;
751 for (const auto *gr : graphs) {
752 // Double_t val = gr->Eval(ene, nullptr, "S");
753 Double_t val = gr->Eval(ene);
754 if (val < 0.0)
755 val = 0.0;
756 total += val;
757 }
758
759 for (Size_t j = 0; j < graphs.size(); j++) {
760 // Double_t raw_val = graphs[j]->Eval(ene, nullptr, "S");
761 Double_t raw_val = graphs[j]->Eval(ene);
762 if (raw_val < 0.0)
763 raw_val = 0.0;
764 Double_t ratio = (total > 0.001) ? raw_val / total : (j == 0 ? 1.0 : 0.0);
765 grs_ratio[j]->SetPoint(i_point, ene, ratio);
766 }
767 i_point++;
768 }
769
770 // Use a uniform random number to select an excited state based on the ratio graphs.
771 Double_t uniform = gRandom->Uniform();
772 Double_t tmp = 0.0;
773 for (Size_t i = 0; i < grs_ratio.size(); i++) {
774 // Double_t val = grs_ratio[i]->Eval(Etotal, nullptr, "S");
775 Double_t val = grs_ratio[i]->Eval(Etotal);
776 if (val < 0.0)
777 val = 0.0;
778 tmp += val;
779 if (uniform < tmp) {
780 ex_id = i;
781 break;
782 }
783 if (i == grs_ratio.size() - 1) {
784 Warning("GetCustomExcitedEnergy", "Could not assign excited id!");
785 }
786 }
787
788 // Clean up dynamically allocated TGraph objects in grs_ratio.
789 for (auto g : grs_ratio) {
790 delete g;
791 }
792 grs_ratio.clear();
793
794 // Clean up the TGraph objects from the file.
795 for (auto g : graphs) {
796 delete g;
797 }
798 graphs.clear();
799
800 file->Close();
801 delete file;
802
803 // debug
804 // std::cout << Etotal << ", assigned ex_id = " << ex_id << std::endl;
805
806 return ex_energys[ex_id];
807}
808
809} // namespace art::crib
Utility functions for handling input and parameter objects in TEventCollection.
ClassImp(art::crib::TTGTIKProcessor)
ROOT macro for class implementation.
Processor for reconstructing reaction positions using the Thick Gas Target Inverse Kinematics (TGTIK)...
Double_t GetCenterRotPos(Int_t id) const
Int_t GetStripNum(Int_t id) const
Double_t GetSize(Int_t id) const
void SetXYZ(Double_t x, Double_t y, Double_t z)
Calculates the reaction position using telescope and tracking data.
IntVec_t fParticleAArray
Array of mass numbers for reaction particles.
const Double_t kEpsilon
Convergence threshold for the bisection method.
TClonesArray * fOutData
! Pointer to the output reaction information (TClonesArray of TReactionInfo)
Double_t newton(const TTrack *track, const TTelescopeData *data)
(Deprecated) Newton method for calculating reaction position.
TString fOutputColName
Name of the output reaction information collection (TReactionInfo)
TClonesArray ** fInData
! Pointer to the input telescope data (TClonesArray of TTelescopeData)
TClonesArray ** fInTrackData
! Pointer to the input tracking data (TClonesArray of TTrack)
const Int_t kMaxIteration
Maximum number of iterations for the bisection method.
const Double_t kInitialMax
Initial maximum value for bisection method (mm)
Double_t GetEcmFromBeam(Double_t z, const TTrack *track)
Calculate the center-of-mass energy from beam data.
Double_t GetEcmFromDetectParticle(Double_t z, const TTrack *track, const TTelescopeData *data)
Calculate the center-of-mass energy from detected particle data.
Double_t fPressure
Gas pressure in Torr.
Double_t GetCMAngle(Double_t ELab, Double_t Ecm, Double_t ALab)
Recalculate the LAB angle after reconstruction.
Double_t bisection(const TTrack *track, const TTelescopeData *data)
Bisection method for calculating reaction position.
Double_t fTemperature
Gas temperature in Kelvin.
TString fTargetParameterName
Name of the target parameter (TTargetParameter)
Double_t TargetFunction(Double_t z, const TTrack *track, const TTelescopeData *data)
Target function for the bisection (and Newton) method.
Double_t GetEcm_classic_kinematics(Double_t energy, Double_t theta)
Calculate the center-of-mass energy using classical kinematics.
IntVec_t fParticleZArray
Array of atomic numbers for reaction particles.
TString fInputTrackColName
Name of the input tracking data collection (TTrack)
Double_t fInitialBeamEnergy
Beam energy immediately after the window (MeV)
void Init(TEventCollection *col) override
Initialize the processor with an event collection.
Bool_t fDoCustom
Flag to enable custom processing.
Double_t GetReactionPosition(const TTrack *track, const TTelescopeData *data)
Calculate the reaction position along the Z-axis.
Double_t GetCustomExcitedEnergy(Int_t telID, Double_t Etotal)
Generate a custom excited state energy.
TClonesArray * fTargetPrm
! Pointer to target parameter objects (TClonesArray of TTargetParameter)
TString fInputColName
Name of the input telescope data collection (TTelescopeData)
TClonesArray * fDetectorPrm
! Pointer to detector parameter objects (TClonesArray of TDetectorParameter)
const Double_t kInitialMin
Initial minimum value for bisection method (mm)
TString fTargetName
Name of the gas target used in TSrim calculations.
Bool_t fDoCenterPos
Flag to use the detector center position.
void Process() override
Main processing function.
Double_t GetEcm_kinematics(Double_t energy, Double_t theta, Double_t low_e, Double_t high_e)
Calculate the center-of-mass energy using relativistic kinematics.
TString fDetectorParameterName
Name of the detector parameter (TDetectorParameter)
TSrim * srim
! TSrim object to calculate energy loss
std::pair< Double_t, Double_t > GetELabALabPair(Double_t z, const TTrack *track, const TTelescopeData *data)
Calculate the LAB energy and LAB angle from detected particle data.
Double_t fExcitedEnergy
Excited state energy (MeV)
~TTGTIKProcessor() override
Default destructor.
Double_t GetEtotal() const
std::enable_if_t< std::is_base_of_v< TObject, T >, std::variant< T **, TString > > GetInputObject(TEventCollection *col, const TString &name, const TString &expectedTypeName, const TString &elementTypeName="TObject")
Retrieve an object from TEventCollection with type validation.
std::enable_if_t< std::is_base_of_v< TObject, T >, std::variant< T *, TString > > GetParameterObject(TEventCollection *col, const TString &name, const TString &expectedTypeName, const TString &elementTypeName="art::TParameterObject")
Retrieves a parameter object from a TEventCollection.
return to the guide