LDMX Software
ecal::EcalWABRecProcessor Class Reference

Public Member Functions

 EcalWABRecProcessor (const std::string &name, framework::Process &process)
 
void onProcessEnd () override
 Callback for the EventProcessor to take any necessary action when the processing of events finishes, such as calculating job-summary quantities.
 
void configure (framework::config::Parameters &parameters) override
 Callback for the EventProcessor to configure itself from the given set of parameters.
 
void produce (framework::Event &event) override
 Process the event and put new data products into it.
 
- Public Member Functions inherited from framework::Producer
 Producer (const std::string &name, Process &process)
 Class constructor.
 
virtual void process (Event &event) final
 Processing an event for a Producer is calling produce.
 
- Public Member Functions inherited from framework::EventProcessor
 DECLARE_FACTORY (EventProcessor, EventProcessor *, const std::string &, Process &)
 declare that we have a factory for this class
 
 EventProcessor (const std::string &name, Process &process)
 Class constructor.
 
virtual ~EventProcessor ()=default
 Class destructor.
 
virtual void beforeNewRun (ldmx::RunHeader &run_header)
 Callback for Producers to add parameters to the run header before conditions are initialized.
 
virtual void onNewRun (const ldmx::RunHeader &run_header)
 Callback for the EventProcessor to take any necessary action when the run being processed changes.
 
virtual void onFileOpen (EventFile &event_file)
 Callback for the EventProcessor to take any necessary action when a new event input ROOT file is opened.
 
virtual void onFileClose (EventFile &event_file)
 Callback for the EventProcessor to take any necessary action when a event input ROOT file is closed.
 
virtual void onProcessStart ()
 Callback for the EventProcessor to take any necessary action when the processing of events starts, such as creating histograms.
 
template<class T >
const T & getCondition (const std::string &condition_name)
 Access a conditions object for the current event.
 
TDirectory * getHistoDirectory ()
 Access/create a directory in the histogram file for this event processor to create histograms and analysis tuples.
 
void setStorageHint (framework::StorageControl::Hint hint)
 Mark the current event as having the given storage control hint from this module_.
 
void setStorageHint (framework::StorageControl::Hint hint, const std::string &purposeString)
 Mark the current event as having the given storage control hint from this module and the given purpose string.
 
int getLogFrequency () const
 Get the current logging frequency from the process.
 
int getRunNumber () const
 Get the run number from the process.
 
std::string getName () const
 Get the processor name.
 
void createHistograms (const std::vector< framework::config::Parameters > &histos)
 Internal function which is used to create histograms passed from the python configuration @parma histos vector of Parameters that configure histograms to create.
 

Private Member Functions

std::tuple< Eigen::VectorXd, float, int, Eigen::MatrixXd, int > fit2DTracksConstrained (const std::vector< float > &x1, const std::vector< float > &y1, const std::vector< float > &s1, const std::vector< float > &x2, const std::vector< float > &y2, const std::vector< float > &s2, const std::vector< double > &guess, int maxIter, int verbosity, float dchisq, float abs_lim)
 
std::pair< Eigen::VectorXd, Eigen::VectorXd > polyfitXYvsZ (const std::vector< float > &x, const std::vector< float > &y, const std::vector< float > &z, int degree)
 

Private Attributes

std::string sp_pass_name_
 
std::string rec_pass_name_
 
std::string rec_coll_name_
 
std::string track_pass_name_
 
std::string track_coll_name_
 
int nevents_ {0}
 
float processing_time_ {0.}
 
std::string collection_name_ {"EcalWABRec"}
 Name of the collection which will contain the results.
 

Additional Inherited Members

- Protected Member Functions inherited from framework::EventProcessor
void abortEvent ()
 Abort the event immediately.
 
- Protected Attributes inherited from framework::EventProcessor
HistogramPool histograms_
 helper object for making and filling histograms
 
NtupleManagerntuple_ {NtupleManager::getInstance()}
 Manager for any ntuples.
 
logging::logger the_log_
 The logger for this EventProcessor.
 

Detailed Description

Definition at line 37 of file EcalWABRecProcessor.h.

Constructor & Destructor Documentation

◆ EcalWABRecProcessor()

ecal::EcalWABRecProcessor::EcalWABRecProcessor ( const std::string & name,
framework::Process & process )
inline

Definition at line 39 of file EcalWABRecProcessor.h.

40 : Producer(name, process) {}
Producer(const std::string &name, Process &process)
Class constructor.
virtual void process(Event &event) final
Processing an event for a Producer is calling produce.

Member Function Documentation

◆ configure()

void ecal::EcalWABRecProcessor::configure ( framework::config::Parameters & parameters)
overridevirtual

Callback for the EventProcessor to configure itself from the given set of parameters.

The parameters a processor has access to are the member variables of the python class in the sequence that has className equal to the EventProcessor class name.

For an example, look at MyProcessor.

Parameters
parametersParameters for configuration.

Reimplemented from framework::EventProcessor.

Definition at line 57 of file EcalWABRecProcessor.cxx.

57 {
58 // Set the collection name as defined in the configuration
59 sp_pass_name_ = parameters.get<std::string>("sp_pass_name");
60 collection_name_ = parameters.get<std::string>("collection_name");
61 rec_pass_name_ = parameters.get<std::string>("rec_pass_name");
62 rec_coll_name_ = parameters.get<std::string>("rec_coll_name");
63 track_pass_name_ = parameters.get<std::string>("track_pass_name");
64 track_coll_name_ = parameters.get<std::string>("track_coll_name");
65}
std::string collection_name_
Name of the collection which will contain the results.
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78

References collection_name_, and framework::config::Parameters::get().

◆ fit2DTracksConstrained()

std::tuple< Eigen::VectorXd, float, int, Eigen::MatrixXd, int > ecal::EcalWABRecProcessor::fit2DTracksConstrained ( const std::vector< float > & x1,
const std::vector< float > & y1,
const std::vector< float > & s1,
const std::vector< float > & x2,
const std::vector< float > & y2,
const std::vector< float > & s2,
const std::vector< double > & guess,
int maxIter,
int verbosity,
float dchisq,
float abs_lim )
private

Definition at line 545 of file EcalWABRecProcessor.cxx.

550 {
551 /*
552 Function that fits two 2D tracks with a vertex constraint (same intercepts).
553 The fitted model is initially defined as:
554 y1 = par[0] * x1 + abs_lim * tanh(par[2] / abs_lim)
555 y2 = par[1] * x2 + abs_lim * tanh(par[2] / abs_lim)
556 After updating parameters the fitted values are computed as:
557 y1 = par[0] * (x1 - par[2])
558 y2 = par[1] * (x2 - par[2])
559
560 Inputs:
561 x1, y1, s1 : measured coordinates and errors for track 1
562 x2, y2, s2 : measured coordinates and errors for track 2
563 guess : initial guess for the parameter vector (size 3)
564 max_iter : maximum number of iterations (default 20)
565 verbose : level of verbose (default 0)
566 d_chisq : stopping criterion for chi-squared improvement (default
567 0.001) abs_lim : a parameter used in the fit (default 10) Returns: A tuple
568 containing: par : fitted parameters (Eigen::VectorXd of size 3) chisq :
569 chi-squared at minimum (float) ndof : number of degrees of freedom (int)
570 cov : covariance matrix (Eigen::MatrixXd 3x3)
571 niter : number of iterations used (int)
572 */
573 // Copy the initial guess into a 3-element parameter vector
574 Eigen::VectorXd par(3);
575 par(0) = guess[0];
576 par(1) = guess[1];
577 par(2) = guess[2];
578
579 // Determine number of points in each track and total
580 int n1 = x1.size();
581 int n2 = x2.size();
582 int n = n1 + n2;
583
584 // Concatenate x_, y_, and s into Eigen vectors of size n.
585 Eigen::VectorXd x(n), y(n), s(n);
586 for (int i = 0; i < n1; ++i) {
587 x(i) = x1[i];
588 y(i) = y1[i];
589 s(i) = s1[i];
590 }
591 for (int i = 0; i < n2; ++i) {
592 x(n1 + i) = x2[i];
593 y(n1 + i) = y2[i];
594 s(n1 + i) = s2[i];
595 }
596
597 // Build the weight matrix W = diag(1/s_i^2)
598 Eigen::MatrixXd w = Eigen::MatrixXd::Zero(n, n);
599 for (int i = 0; i < n; ++i) {
600 w(i, i) = 1.0 / ((s(i)) * (s(i)));
601 }
602
603 float chi_sq = 0.0;
604 float old_chi_sq = 1e12; // a large initial value
605 int n_iter = 0;
606 Eigen::MatrixXd cov(3, 3); // covariance matrix
607
608 // Iterative fitting loop
609 for (int iter = 0; iter < max_iter; ++iter) {
610 n_iter = iter + 1;
611
612 // Compute fitted y coordinates for each track using the current
613 // parameters. For track 1: y1_fit = par[0] * x1 + abs_lim *
614 // tanh(par[2]/abs_lim) For track 2: y2_fit = par[1] * x2 + abs_lim *
615 // tanh(par[2]/abs_lim)
616 Eigen::VectorXd y1_fit(n1), y2_fit(n2);
617 float tanh_term = std::tanh(par(2) / abs_lim);
618 for (int i = 0; i < n1; ++i) {
619 y1_fit(i) = par(0) * x1[i] + abs_lim * tanh_term;
620 }
621 for (int i = 0; i < n2; ++i) {
622 y2_fit(i) = par(1) * x2[i] + abs_lim * tanh_term;
623 }
624
625 // Concatenate the fitted values
626 Eigen::VectorXd y_fit(n);
627 for (int i = 0; i < n1; ++i) {
628 y_fit(i) = y1_fit(i);
629 }
630 for (int i = 0; i < n2; ++i) {
631 y_fit(n1 + i) = y2_fit(i);
632 }
633
634 // Compute chi-squared: sum_i [ (y_fit[i]-y_[i])^2 / s[i]^2 ]
635 chi_sq = 0.0;
636 for (int i = 0; i < n; ++i) {
637 float diff = y_fit(i) - y(i);
638 chi_sq += (diff * diff) / ((s(i)) * (s(i)));
639 }
640
641 if (verbose > 0) {
642 ldmx_log(debug) << "Before iteration " << iter << ", chi_sq = " << chi_sq;
643 ldmx_log(debug) << "Track 1 residuals: ";
644 for (int i = 0; i < n1; ++i) {
645 ldmx_log(debug) << (y1_fit(i) - y1[i]);
646 }
647 ldmx_log(debug) << "Track 2 residuals: ";
648 for (int i = 0; i < n2; ++i) {
649 ldmx_log(debug) << (y2_fit(i) - y2[i]);
650 }
651 }
652
653 // Compute the derivatives (Jacobian components)
654 // For track 1:
655 // dy1/dpar0 = x1, dy1/dpar1 = 0, dy1/dpar2 =
656 // (1/cosh(par[2]/abs_lim))^2 (constant for all points)
657 // For track 2:
658 // dy2/dpar0 = 0, dy2/dpar1 = x2, dy2/dpar2 =
659 // (1/cosh(par[2]/abs_lim))^2
660 Eigen::VectorXd dy1_dpar_0(n1), dy1_dpar_1 = Eigen::VectorXd::Zero(n1),
661 dy1_dpar_2(n1);
662 Eigen::VectorXd dy2_dpar_0 = Eigen::VectorXd::Zero(n2), dy2_dpar_1(n2),
663 dy2_dpar_2(n2);
664
665 float d_term = 1.0 / std::cosh(par(2) / abs_lim);
666 d_term = d_term * d_term; // square it
667 for (int i = 0; i < n1; ++i) {
668 dy1_dpar_0(i) = x1[i];
669 dy1_dpar_2(i) = d_term;
670 }
671 for (int i = 0; i < n2; ++i) {
672 dy2_dpar_1(i) = x2[i];
673 dy2_dpar_2(i) = d_term;
674 }
675
676 // Concatenate the derivatives for both tracks into full vectors of length
677 // n.
678 Eigen::VectorXd dy_dpar_0(n), dy_dpar_1(n), dy_dpar_2(n);
679 for (int i = 0; i < n1; ++i) {
680 dy_dpar_0(i) = dy1_dpar_0(i);
681 dy_dpar_1(i) = dy1_dpar_1(i);
682 dy_dpar_2(i) = dy1_dpar_2(i);
683 }
684 for (int i = 0; i < n2; ++i) {
685 dy_dpar_0(n1 + i) = dy2_dpar_0(i);
686 dy_dpar_1(n1 + i) = dy2_dpar_1(i);
687 dy_dpar_2(n1 + i) = dy2_dpar_2(i);
688 }
689
690 // Build the "A" matrix (the Jacobian) in its transposed form (3 x n)
691 Eigen::MatrixXd a_trans(3, n);
692 a_trans.row(0) = dy_dpar_0.transpose();
693 a_trans.row(1) = dy_dpar_1.transpose();
694 a_trans.row(2) = dy_dpar_2.transpose();
695
696 // The Jacobian (n x 3) is the transpose of a_trans.
697 Eigen::MatrixXd a = a_trans.transpose();
698
699 // The residual vector (difference between measured and fitted y values)
700 Eigen::VectorXd dy_vec = y - y_fit;
701
702 // Compute the (3 x 3) matrix: M = a_trans * W * a
703 Eigen::MatrixXd temp = a_trans * w; // 3 x n
704 Eigen::MatrixXd temp2 = temp * a; // 3 x 3
705
706 // Add a regularization term to ensure numerical stability.
707 Eigen::MatrixXd reg =
708 1e-10 * Eigen::MatrixXd::Identity(temp2.rows(), temp2.cols());
709 Eigen::MatrixXd temp2_reg = temp2 + reg;
710
711 // Invert the matrix to obtain the covariance matrix.
712 cov = temp2_reg.inverse();
713
714 // Compute the parameter correction: dpar = cov * a_trans * W * dy_vec
715 Eigen::MatrixXd temp4 = cov * a_trans; // 3 x n
716 Eigen::MatrixXd temp5 = temp4 * w; // 3 x n
717 Eigen::VectorXd dpar = temp5 * dy_vec; // 3 x 1
718
719 // Update the parameters
720 par += dpar;
721
722 // After the update, the fitted y values are recalculated with a different
723 // formula:
724 // y1_fit = par[0]*(x1 - par[2])
725 // y2_fit = par[1]*(x2 - par[2])
726 for (int i = 0; i < n1; ++i) {
727 y1_fit(i) = par(0) * (x1[i] - par(2));
728 }
729 for (int i = 0; i < n2; ++i) {
730 y2_fit(i) = par(1) * (x2[i] - par(2));
731 }
732 for (int i = 0; i < n1; ++i) {
733 y_fit(i) = y1_fit(i);
734 }
735 for (int i = 0; i < n2; ++i) {
736 y_fit(n1 + i) = y2_fit(i);
737 }
738
739 // Recompute chi-squared with the updated fitted values.
740 float new_chi_sq = 0.0;
741 for (int i = 0; i < n; ++i) {
742 float diff = y_fit(i) - y(i);
743 new_chi_sq += (diff * diff) / ((s(i)) * (s(i)));
744 }
745 chi_sq = new_chi_sq;
746
747 // Check for convergence
748 if (iter > 0) {
749 if (std::abs(chi_sq - old_chi_sq) < d_chisq) {
750 break;
751 }
752 }
753 old_chi_sq = chi_sq;
754 } // end for loop
755
756 if (verbose > 0) {
757 ldmx_log(debug) << "At the end chi_sq = " << chi_sq;
758 ldmx_log(debug) << "Scaled residuals for track 1:";
759 for (int i = 0; i < n1; ++i) {
760 float fit_val = par(0) * (x1[i] - par(2));
761 ldmx_log(debug) << 10000 * (fit_val - y1[i]);
762 }
763 ldmx_log(debug) << "Scaled residuals for track 2:";
764 for (int i = 0; i < n2; ++i) {
765 float fit_val = par(1) * (x2[i] - par(2));
766 ldmx_log(debug) << 10000 * (fit_val - y2[i]);
767 }
768 }
769
770 int ndof = n1 + n2 - 3;
771 return std::make_tuple(par, chi_sq, ndof, cov, n_iter);
772}

◆ onProcessEnd()

void ecal::EcalWABRecProcessor::onProcessEnd ( )
overridevirtual

Callback for the EventProcessor to take any necessary action when the processing of events finishes, such as calculating job-summary quantities.

Reimplemented from framework::EventProcessor.

Definition at line 539 of file EcalWABRecProcessor.cxx.

539 {
540 ldmx_log(info) << "AVG Time/Event: " << std::fixed << std::setprecision(2)
541 << processing_time_ / nevents_ << " ms";
542}

◆ polyfitXYvsZ()

std::pair< Eigen::VectorXd, Eigen::VectorXd > ecal::EcalWABRecProcessor::polyfitXYvsZ ( const std::vector< float > & x,
const std::vector< float > & y,
const std::vector< float > & z,
int degree )
private

Definition at line 774 of file EcalWABRecProcessor.cxx.

776 {
777 /*
778 Function that fits two polynomials (x_ vs. z and y vs. z_) to 3D hit
779 position data using a least-squares method. The fitted models are defined
780 as: x = a₀
781 + a₁ * z + a₂ * z_² + ... + aₙ * zⁿ
782 y = b₀ + b₁ * z + b₂ * z_² + ... + bₙ * zⁿ
783 where n is the specified polynomial degree.
784
785 Inputs:
786 x_, y_, z : measured coordinates for the tracks;
787 x and y are the dependent variables, and z is the independent
788 variable (all provided as std::vector<float>) degree : degree of the
789 polynomial to be fitted (int)
790
791 Returns:
792 A pair containing:
793 first : polynomial coefficients for the x vs. z fit (Eigen::VectorXd)
794 second : polynomial coefficients for the y vs. z fit (Eigen::VectorXd)
795
796 Notes:
797 The polynomial is represented with the constant term first (i.e., [a₀, a₁,
798 ..., aₙ]), so the linear term (slope) is located at index_ 1.
799 */
800 const size_t n = z_.size();
801 if (n == 0 || x_.size() != n || y_.size() != n) {
802 throw std::invalid_argument(
803 "Vectors x_, y_, and z must be non-empty and have the same size.");
804 }
805
806 // Construct the Vandermonde (design) matrix A (n x (degree + 1)):
807 // Each row i: [1, z_[i], z_[i]^2, ..., z_[i]^degree]
808 Eigen::MatrixXd a(n, degree + 1);
809 for (size_t i = 0; i < n; ++i) {
810 float term = 1.0;
811 for (int j = 0; j <= degree; ++j) {
812 a(i, j) = term;
813 term *= z_[i];
814 }
815 }
816
817 // Map the x and y data into Eigen vectors.
818 Eigen::VectorXd bx(n), by(n);
819 for (size_t i = 0; i < n; ++i) {
820 bx(i) = x_[i];
821 by(i) = y_[i];
822 }
823
824 // Solve the least-squares problems:
825 // A * coeffsX ≈ bx and A * coeffsY ≈ by
826 Eigen::VectorXd coeffs_x = a.colPivHouseholderQr().solve(bx);
827 Eigen::VectorXd coeffs_y = a.colPivHouseholderQr().solve(by);
828
829 return {coeffs_x, coeffs_y};
830}

◆ produce()

void ecal::EcalWABRecProcessor::produce ( framework::Event & event)
overridevirtual

Process the event and put new data products into it.

Parameters
eventThe Event to process.

Implements framework::Producer.

Definition at line 67 of file EcalWABRecProcessor.cxx.

67 {
68 // Define start time for processing
69 auto start = std::chrono::high_resolution_clock::now();
70 nevents_++;
71
72 // Keep track of event progress where:
73 // 0: No tracks found
74 // 1: Track found but not enough info to reconstruct either electron or photon
75 // 2: Track found and enough info to reconstruct electron
76 // 3: Track found and enough info to reconstruct electron and photon
77 int progress_num = 0;
78
79 // Get the Ecal Geometry
81 ldmx::EcalGeometry::CONDITIONS_OBJECT_NAME);
82
83 // Get the collection of ecal_rec_hits and tracks
84 const std::vector<ldmx::EcalHit> ecal_rec_hits =
85 event.getCollection<ldmx::EcalHit>(rec_coll_name_, rec_pass_name_);
86 const std::vector<ldmx::StraightTrack> linear_tracks =
87 event.getCollection<ldmx::StraightTrack>(track_coll_name_,
88 track_pass_name_);
89
90 // Define variables to save recoil electron/photon information (SP)
91 std::vector<double> recoil_e_p, recoil_y_p;
92 std::vector<float> recoil_e_pos, recoil_y_pos;
93
94 // Result object that stores kinematic variables
96
97 // Define kinematic variables
98 const std::vector<float> z_hat = {0, 0, 1};
99 float true_theta_electron = -9.;
100 float true_theta_photon = -9.;
101 float true_phi_electron = -9.;
102 float true_phi_photon = -9.;
103 float rec_theta_electron = -9.;
104 float rec_theta_photon = -9.;
105 float rec_phi_electron = -9.;
106 float rec_phi_photon = -9.;
107 float true_theta_diff_electron_photon = -9.;
108 float true_phi_diff_electron_photon = -9.;
109 float rec_theta_diff_electron_photon = -9.;
110 float rec_phi_diff_electron_photon = -9.;
111 float true_rec_theta_diff_electron = -9.;
112 float true_rec_phi_diff_electron = -9.;
113 float true_rec_theta_diff_photon = -9.;
114 float true_rec_phi_diff_photon = -9.;
115 float true_electron_shower_energy = -999.;
116 float true_photon_shower_energy = -999.;
117 float rec_electron_shower_energy = -999.;
118 float rec_photon_shower_energy = -999.;
119
120 // Create lists for rec hits_ and electron/photon shower hits_
121 std::vector<std::array<float, 6>> rec_hit_list;
122 std::vector<std::array<float, 6>> ele_hit_list;
123 std::vector<std::array<float, 6>> phot_hit_list;
124
125 // Save rec hit info to rec_hit_list
126 for (const ldmx::EcalHit& hit : ecal_rec_hits) {
127 ldmx::EcalID id(hit.getID());
128 auto pos = geometry->getPosition(id);
129 auto [x_, y_, z_] = std::apply(
130 [](double a, double b, double c) {
131 return std::make_tuple(static_cast<float>(a), static_cast<float>(b),
132 static_cast<float>(c));
133 },
134 pos);
135 float energy = hit.getEnergy();
136 float layer_num = id.layer();
137 rec_hit_list.push_back({x_, y_, z_, layer_num, 0, energy});
138 }
139
140 if (event.exists("TargetScoringPlaneHits", sp_pass_name_)) {
141 //
142 // Loop through all of the sim particles and find the recoil
143 // photon/electron.
144 //
145
146 // Find Target SP hit for recoil photon/electron
147 const std::vector<ldmx::SimTrackerHit> target_sp_hits =
148 event.getCollection<ldmx::SimTrackerHit>("TargetScoringPlaneHits",
149 sp_pass_name_);
150 float photon_p_zmax = 0, electron_p_zmax = 0;
151 for (const ldmx::SimTrackerHit& sp_hit : target_sp_hits) {
152 ldmx::SimSpecialID hit_id(sp_hit.getID());
153 if (hit_id.plane() != 1 || sp_hit.getMomentum()[2] <= 0) continue;
154
155 if (sp_hit.getPdgID() == 11) {
156 if (sp_hit.getMomentum()[2] > electron_p_zmax) {
157 recoil_e_p = sp_hit.getMomentum();
158 true_electron_shower_energy = sp_hit.getEnergy();
159 recoil_e_pos = sp_hit.getPosition();
160 electron_p_zmax = recoil_e_p[2];
161 }
162 }
163 if (sp_hit.getPdgID() == 22) {
164 if (sp_hit.getMomentum()[2] > photon_p_zmax) {
165 recoil_y_p = sp_hit.getMomentum();
166 true_photon_shower_energy = sp_hit.getEnergy();
167 recoil_y_pos = sp_hit.getPosition();
168 photon_p_zmax = recoil_y_p[2];
169 }
170 }
171 }
172
173 // Calculating true theta values using SP hit parameters
174 if (recoil_y_p.size() == 3 &&
175 std::sqrt((recoil_y_p[0]) * (recoil_y_p[0]) +
176 (recoil_y_p[1]) * (recoil_y_p[1]) +
177 (recoil_y_p[2]) * (recoil_y_p[2])) != 0 &&
178 recoil_e_p.size() == 3 &&
179 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
180 (recoil_e_p[1]) * (recoil_e_p[1]) +
181 (recoil_e_p[2]) * (recoil_e_p[2])) != 0) {
182 true_theta_electron =
183 (180 / std::numbers::pi) *
184 std::acos(std::inner_product(recoil_e_p.begin(), recoil_e_p.end(),
185 z_hat.begin(), 0.0) /
186 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
187 (recoil_e_p[1]) * (recoil_e_p[1]) +
188 (recoil_e_p[2]) * (recoil_e_p[2])));
189 true_theta_photon =
190 (180 / std::numbers::pi) *
191 std::acos(std::inner_product(recoil_y_p.begin(), recoil_y_p.end(),
192 z_hat.begin(), 0.0) /
193 std::sqrt((recoil_y_p[0]) * (recoil_y_p[0]) +
194 (recoil_y_p[1]) * (recoil_y_p[1]) +
195 (recoil_y_p[2]) * (recoil_y_p[2])));
196 }
197
198 // Calculating true phi values using SP hit parameters
199 if (recoil_y_p.size() == 3 && recoil_y_p[2] != 0 &&
200 recoil_e_p.size() == 3 && recoil_e_p[2] != 0) {
201 true_phi_electron =
202 (180 / std::numbers::pi) * std::atan(recoil_e_p[1] / recoil_e_p[0]);
203 if (recoil_e_p[1] < 0) {
204 true_phi_electron += 180;
205 }
206 if (recoil_e_p[0] < 0 && recoil_e_p[1] > 0) {
207 true_phi_electron += 360;
208 }
209 true_phi_photon =
210 (180 / std::numbers::pi) * std::atan(recoil_y_p[1] / recoil_y_p[0]);
211 if (recoil_y_p[1] < 0) {
212 true_phi_photon += 180;
213 }
214 if (recoil_y_p[0] < 0 && recoil_y_p[1] > 0) {
215 true_phi_photon += 360;
216 }
217 }
218
219 // Calculating true delta_phi/delta_theta using SP hit parameters
220 if (recoil_y_p.size() == 3 && recoil_e_p.size() == 3) {
221 std::array<double, 2> phi_diff_electron_arr = {recoil_e_p[0],
222 recoil_e_p[1]};
223 std::array<double, 2> phi_diff_photon_arr = {recoil_y_p[0],
224 recoil_y_p[1]};
225 std::array<double, 2> theta_diff_electron_arr = {recoil_e_p[2],
226 recoil_e_p[0]};
227 std::array<double, 2> theta_diff_photon_arr = {recoil_y_p[2],
228 recoil_y_p[0]};
229
230 true_theta_diff_electron_photon =
231 (180 / std::numbers::pi) *
232 std::acos(
233 std::inner_product(theta_diff_electron_arr.begin(),
234 theta_diff_electron_arr.end(),
235 theta_diff_photon_arr.begin(), 0.0) /
236 (std::sqrt((theta_diff_electron_arr[0]) *
237 (theta_diff_electron_arr[0]) +
238 (theta_diff_electron_arr[1]) *
239 (theta_diff_electron_arr[1])) *
240 std::sqrt(
241 (theta_diff_photon_arr[0]) * (theta_diff_photon_arr[0]) +
242 (theta_diff_photon_arr[1]) * (theta_diff_photon_arr[1]))));
243 true_phi_diff_electron_photon =
244 (180 / std::numbers::pi) *
245 std::acos(
246 std::inner_product(phi_diff_electron_arr.begin(),
247 phi_diff_electron_arr.end(),
248 phi_diff_photon_arr.begin(), 0.0) /
249 (std::sqrt(
250 (phi_diff_electron_arr[0]) * (phi_diff_electron_arr[0]) +
251 (phi_diff_electron_arr[1]) * (phi_diff_electron_arr[1])) *
252 std::sqrt((phi_diff_photon_arr[0]) * (phi_diff_photon_arr[0]) +
253 (phi_diff_photon_arr[1]) * (phi_diff_photon_arr[1]))));
254 }
255 }
256
257 // Defining variables to save best fit results
258 std::pair<Eigen::VectorXd, Eigen::VectorXd> linear_fit_coeffs;
259 std::tuple<Eigen::VectorXd, float, int, Eigen::MatrixXd, int> best_x_result;
260 std::tuple<Eigen::VectorXd, float, int, Eigen::MatrixXd, int> best_y_result;
261 std::get<1>(best_x_result) = 10e99;
262 std::get<1>(best_y_result) = 10e99;
263
264 // Looping over tracks to find best fit to hits_
265 std::vector<float> ele_roc = RADIUS_68_THETA_30_TO_90;
266 for (const ldmx::StraightTrack& track : linear_tracks) {
267 progress_num = 1;
268 // Determining the RoC value to use based on recoil electron (track) theta
269 std::vector<double> track_vec = {track.getSlopeX(), track.getSlopeY(), 1};
270 float track_theta =
271 (180 / std::numbers::pi) *
272 std::acos(std::inner_product(track_vec.begin(), track_vec.end(),
273 z_hat.begin(), 0.0) /
274 std::sqrt((track_vec[0]) * (track_vec[0]) +
275 (track_vec[1]) * (track_vec[1]) + 1));
276 if (track_theta <= 10) {
277 ele_roc = RADIUS_68_THETA_0_TO_10;
278 } else if (track_theta > 10 && track_theta <= 15) {
279 ele_roc = RADIUS_68_THETA_10_TO_15;
280 } else if (track_theta > 15 && track_theta <= 20) {
281 ele_roc = RADIUS_68_THETA_15_TO_20;
282 } else if (track_theta > 20 && track_theta <= 30) {
283 ele_roc = RADIUS_68_THETA_20_TO_30;
284 }
285
286 // Labeling hits_ as electron (1) or photon (0)
287 for (std::array<float, 6>& hit : rec_hit_list) {
288 if (std::sqrt(
289 (hit[0] - (track.getSlopeX() * hit[2] + track.getInterceptX())) *
290 (hit[0] -
291 (track.getSlopeX() * hit[2] + track.getInterceptX())) +
292 (hit[1] - (track.getSlopeY() * hit[2] + track.getInterceptY())) *
293 (hit[1] - (track.getSlopeY() * hit[2] +
294 track.getInterceptY()))) < ele_roc[hit[3]]) {
295 hit[4] = 1;
296 }
297 }
298 // Create vectors to hold electron/photon hits_ specifically
299 std::vector<float> ele_hit_list_x, ele_hit_list_y, ele_hit_list_z;
300 std::vector<float> phot_hit_list_x, phot_hit_list_y, phot_hit_list_z;
301
302 // Use labels to sort hits_ as electron/photon and calculate shower energies
303 for (const auto& hit : rec_hit_list) {
304 if (hit[4] == 1) {
305 ele_hit_list.push_back(hit);
306 ele_hit_list_x.push_back(hit[0]);
307 ele_hit_list_y.push_back(hit[1]);
308 ele_hit_list_z.push_back(hit[2]);
309 } else if (hit[4] == 0) {
310 phot_hit_list.push_back(hit);
311 phot_hit_list_x.push_back(hit[0]);
312 phot_hit_list_y.push_back(hit[1]);
313 phot_hit_list_z.push_back(hit[2]);
314 }
315 }
316
317 // Fit both photon/electron or just electron hits_ based on # of viable
318 // showers
319 if (phot_hit_list.size() >= 3 && ele_hit_list.size() >= 3) {
320 progress_num =
321 3; // Set progress_num to 3 to halt electron-only reconstruction
322
323 // Generate guesses and error vectors for vertex constrained fit
324 std::vector<double> x_guess = {
325 track.getSlopeX(),
326 (phot_hit_list.back()[0] - phot_hit_list[0][0]) /
327 (phot_hit_list.back()[2] - phot_hit_list[0][2]),
328 track.getInterceptX()};
329 std::vector<double> y_guess = {
330 track.getSlopeY(),
331 (phot_hit_list.back()[1] - phot_hit_list[0][1]) /
332 (phot_hit_list.back()[2] - phot_hit_list[0][2]),
333 track.getInterceptY()};
334 std::vector<float> phot_hit_error(phot_hit_list.size(),
335 0.456435464588 * 4.816);
336 std::vector<float> ele_hit_error(ele_hit_list.size(),
337 0.456435464588 * 4.816);
338
339 int max_iter = 200;
340 // Carry out fit
341 std::tuple<Eigen::VectorXd, float, int, Eigen::MatrixXd, int> x_result =
342 fit2DTracksConstrained(ele_hit_list_z, ele_hit_list_x, ele_hit_error,
343 phot_hit_list_z, phot_hit_list_x,
344 phot_hit_error, x_guess, max_iter, 0, 0.001,
345 10.0);
346
347 std::tuple<Eigen::VectorXd, float, int, Eigen::MatrixXd, int> y_result =
348 fit2DTracksConstrained(ele_hit_list_z, ele_hit_list_y, ele_hit_error,
349 phot_hit_list_z, phot_hit_list_y,
350 phot_hit_error, y_guess, max_iter, 0, 0.001,
351 40.0);
352
353 // Update best fit variables if current fit is an improvement
354 if ((std::get<1>(x_result) + std::get<1>(y_result)) / 2 <
355 (std::get<1>(best_x_result) + std::get<1>(best_y_result)) / 2) {
356 best_x_result = x_result;
357 best_y_result = y_result;
358 }
359 }
360
361 // If there isn't enough info for both electron and photon reconstruction,
362 // reconstruct electron if possible
363 else if (ele_hit_list.size() >= 3) {
364 if (progress_num != 3) {
365 progress_num = 2; // Set progress_num to 3 to indicate electron-only
366 // reconstruction
367 linear_fit_coeffs =
368 polyfitXYvsZ(ele_hit_list_x, ele_hit_list_y, ele_hit_list_z, 1);
369 }
370 }
371 }
372
373 // Calculate kinematic variables for electron and/or photon
374 // based on # of viable showers (with reconstruction information)
375 if (std::get<0>(best_x_result).size() != 0) {
376 rec_electron_shower_energy = 0;
377 rec_photon_shower_energy = 0;
378 for (const auto& hit : ele_hit_list) {
379 rec_electron_shower_energy += hit[5];
380 }
381 for (const auto& hit : phot_hit_list) {
382 rec_photon_shower_energy += hit[5];
383 }
384
385 std::vector<double> ele_params = {std::get<0>(best_x_result)(0),
386 std::get<0>(best_y_result)(0)};
387 std::vector<double> phot_params = {std::get<0>(best_x_result)(1),
388 std::get<0>(best_y_result)(1)};
389 std::vector<double> ele_params_x = {std::get<0>(best_x_result)(0)};
390 std::vector<double> phot_params_x = {std::get<0>(best_x_result)(1)};
391
392 rec_theta_electron =
393 (180 / std::numbers::pi) *
394 std::acos(1 / std::sqrt((ele_params[0]) * (ele_params[0]) +
395 (ele_params[1]) * (ele_params[1]) + 1));
396 rec_theta_photon =
397 (180 / std::numbers::pi) *
398 std::acos(1 / std::sqrt((phot_params[0]) * (phot_params[0]) +
399 (phot_params[1]) * (phot_params[1]) + 1));
400
401 rec_phi_electron =
402 (180 / std::numbers::pi) * std::atan(ele_params[1] / ele_params[0]);
403 if (ele_params[1] < 0) {
404 rec_phi_electron += 180;
405 }
406 if (ele_params[0] < 0 && ele_params[1] > 0) {
407 rec_phi_electron += 360;
408 }
409 rec_phi_photon =
410 (180 / std::numbers::pi) * std::atan(phot_params[1] / phot_params[0]);
411 if (phot_params[1] < 0) {
412 rec_phi_photon += 180;
413 }
414 if (phot_params[0] < 0 && phot_params[1] > 0) {
415 rec_phi_photon += 360;
416 }
417
418 rec_theta_diff_electron_photon =
419 (180 / std::numbers::pi) *
420 std::acos(std::inner_product(ele_params_x.begin(), ele_params_x.end(),
421 phot_params_x.begin(), 1.0) /
422 (std::sqrt((ele_params_x[0]) * (ele_params_x[0]) + (1)) *
423 std::sqrt((phot_params_x[0]) * (phot_params_x[0]) + 1)));
424 rec_phi_diff_electron_photon =
425 (180 / std::numbers::pi) *
426 std::acos(std::inner_product(ele_params.begin(), ele_params.end(),
427 phot_params.begin(), 0.0) /
428 (std::sqrt((ele_params[0]) * (ele_params[0]) +
429 (ele_params[1]) * (ele_params[1])) *
430 std::sqrt((phot_params[0]) * (phot_params[0]) +
431 (phot_params[1]) * (phot_params[1]))));
432
433 if (recoil_y_p.size() == 3 && recoil_y_p[2] != 0 &&
434 recoil_e_p.size() == 3 && recoil_e_p[2] != 0) {
435 true_rec_theta_diff_electron =
436 (180 / std::numbers::pi) *
437 std::acos(std::inner_product(ele_params_x.begin(), ele_params_x.end(),
438 recoil_e_p.begin(), recoil_e_p[2]) /
439 (std::sqrt((ele_params_x[0]) * (ele_params_x[0]) + 1) *
440 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
441 (recoil_e_p[2]) * (recoil_e_p[2]))));
442 true_rec_theta_diff_photon =
443 (180 / std::numbers::pi) *
444 std::acos(std::inner_product(phot_params_x.begin(),
445 phot_params_x.end(), recoil_y_p.begin(),
446 recoil_y_p[2]) /
447 (std::sqrt((phot_params_x[0]) * (phot_params_x[0]) + 1) *
448 std::sqrt((recoil_y_p[0]) * (recoil_y_p[0]) +
449 (recoil_y_p[2]) * (recoil_y_p[2]))));
450 true_rec_phi_diff_electron =
451 (180 / std::numbers::pi) *
452 std::acos(std::inner_product(ele_params.begin(), ele_params.end(),
453 recoil_e_p.begin(), 0) /
454 (std::sqrt((ele_params[0]) * (ele_params[0]) +
455 (ele_params[1]) * (ele_params[1])) *
456 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
457 (recoil_e_p[1]) * (recoil_e_p[1]))));
458 true_rec_phi_diff_photon =
459 (180 / std::numbers::pi) *
460 std::acos(std::inner_product(phot_params.begin(), phot_params.end(),
461 recoil_y_p.begin(), 0) /
462 (std::sqrt((phot_params[0]) * (phot_params[0]) +
463 (phot_params[1]) * (phot_params[1])) *
464 std::sqrt((recoil_y_p[0]) * (recoil_y_p[0]) +
465 (recoil_y_p[1]) * (recoil_y_p[1]))));
466 }
467 } else if (progress_num == 2) {
468 rec_electron_shower_energy = 0;
469 for (const auto& hit : ele_hit_list) {
470 rec_electron_shower_energy += hit[5];
471 }
472
473 std::vector<double> ele_params = {linear_fit_coeffs.first(1),
474 linear_fit_coeffs.second(1)};
475 std::vector<double> ele_params_x = {linear_fit_coeffs.first(1)};
476
477 rec_theta_electron =
478 (180 / std::numbers::pi) *
479 std::acos(1 / std::sqrt((ele_params[0]) * (ele_params[0]) +
480 (ele_params[1]) * (ele_params[1]) + 1));
481 rec_phi_electron =
482 (180 / std::numbers::pi) * std::atan(ele_params[1] / ele_params[0]);
483 if (ele_params[1] < 0) {
484 rec_phi_electron += 180;
485 }
486 if (ele_params[0] < 0 && ele_params[1] > 0) {
487 rec_phi_electron += 360;
488 }
489
490 if (recoil_y_p.size() == 3 && recoil_y_p[2] != 0 &&
491 recoil_e_p.size() == 3 && recoil_e_p[2] != 0) {
492 true_rec_theta_diff_electron =
493 (180 / std::numbers::pi) *
494 std::acos(std::inner_product(ele_params_x.begin(), ele_params_x.end(),
495 recoil_e_p.begin(), recoil_e_p[2]) /
496 (std::sqrt((ele_params_x[0]) * (ele_params_x[0]) + 1) *
497 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
498 (recoil_e_p[2]) * (recoil_e_p[2]))));
499 true_rec_phi_diff_electron =
500 (180 / std::numbers::pi) *
501 std::acos(std::inner_product(ele_params.begin(), ele_params.end(),
502 recoil_e_p.begin(), 0) /
503 (std::sqrt((ele_params[0]) * (ele_params[0]) +
504 (ele_params[1]) * (ele_params[1])) *
505 std::sqrt((recoil_e_p[0]) * (recoil_e_p[0]) +
506 (recoil_e_p[1]) * (recoil_e_p[1]))));
507 }
508
509 // Set photon variables to non-physical value
510 // that corresponds to electron-only reco case
511 rec_theta_photon = -5.;
512 rec_phi_photon = -5.;
513 rec_theta_diff_electron_photon = -5.;
514 rec_phi_diff_electron_photon = -5.;
515 true_rec_theta_diff_photon = -5.;
516 true_rec_phi_diff_photon = -5.;
517 }
518
519 // Setting output object equal to calculated variables
520 result.setVariables(
521 true_theta_electron, true_theta_photon, true_phi_electron,
522 true_phi_photon, rec_theta_electron, rec_theta_photon, rec_phi_electron,
523 rec_phi_photon, true_theta_diff_electron_photon,
524 true_phi_diff_electron_photon, rec_theta_diff_electron_photon,
525 rec_phi_diff_electron_photon, true_rec_theta_diff_electron,
526 true_rec_phi_diff_electron, true_rec_theta_diff_photon,
527 true_rec_phi_diff_photon, true_electron_shower_energy,
528 true_photon_shower_energy, rec_electron_shower_energy,
529 rec_photon_shower_energy, progress_num);
530
531 event.add(collection_name_, result);
532
533 // Calculate processing time for event
534 auto end = std::chrono::high_resolution_clock::now();
535 auto diff = end - start;
536 processing_time_ += std::chrono::duration<float, std::milli>(diff).count();
537}
const T & getCondition(const std::string &condition_name)
Access a conditions object for the current event.
bool exists(const std::string &name, const std::string &passName, bool unique=true) const
Check for the existence of an object or collection with the given name and pass name in the event.
Definition Event.cxx:92
Translation between real-space positions and cell IDs within the ECal.
std::tuple< double, double, double > getPosition(EcalID id) const
Get a cell's position from its ID number.
Stores reconstructed hit information from the ECAL.
Definition EcalHit.h:19
Extension of DetectorID providing access to ECal layers and cell numbers in a hex grid.
Definition EcalID.h:20
Implements detector ids for special simulation-derived hits like scoring planes.
Represents a simulated tracker hit in the simulation.

References collection_name_, framework::Event::exists(), framework::EventProcessor::getCondition(), ldmx::EcalGeometry::getPosition(), and ldmx::SimSpecialID::plane().

Member Data Documentation

◆ collection_name_

std::string ecal::EcalWABRecProcessor::collection_name_ {"EcalWABRec"}
private

Name of the collection which will contain the results.

Definition at line 74 of file EcalWABRecProcessor.h.

74{"EcalWABRec"};

Referenced by configure(), and produce().

◆ nevents_

int ecal::EcalWABRecProcessor::nevents_ {0}
private

Definition at line 56 of file EcalWABRecProcessor.h.

56{0};

◆ processing_time_

float ecal::EcalWABRecProcessor::processing_time_ {0.}
private

Definition at line 57 of file EcalWABRecProcessor.h.

57{0.};

◆ rec_coll_name_

std::string ecal::EcalWABRecProcessor::rec_coll_name_
private

Definition at line 53 of file EcalWABRecProcessor.h.

◆ rec_pass_name_

std::string ecal::EcalWABRecProcessor::rec_pass_name_
private

Definition at line 52 of file EcalWABRecProcessor.h.

◆ sp_pass_name_

std::string ecal::EcalWABRecProcessor::sp_pass_name_
private

Definition at line 51 of file EcalWABRecProcessor.h.

◆ track_coll_name_

std::string ecal::EcalWABRecProcessor::track_coll_name_
private

Definition at line 55 of file EcalWABRecProcessor.h.

◆ track_pass_name_

std::string ecal::EcalWABRecProcessor::track_pass_name_
private

Definition at line 54 of file EcalWABRecProcessor.h.


The documentation for this class was generated from the following files: