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
 
NtupleManager & ntuple_ {NtupleManager::getInstance()}
 Manager for any ntuples.
 
logging::logger the_log_
 The logger for this EventProcessor.
 

Detailed Description

Definition at line 23 of file EcalWABRecProcessor.h.

Constructor & Destructor Documentation

◆ EcalWABRecProcessor()

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

Definition at line 25 of file EcalWABRecProcessor.h.

26 : 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 class_name equal to the EventProcessor class name.

For an example, look at MyProcessor.

Parameters
parametersParameters for configuration.

Reimplemented from framework::EventProcessor.

Definition at line 69 of file EcalWABRecProcessor.cxx.

69 {
70 // Set the collection name as defined in the configuration
71 sp_pass_name_ = parameters.get<std::string>("sp_pass_name");
72 collection_name_ = parameters.get<std::string>("collection_name");
73 rec_pass_name_ = parameters.get<std::string>("rec_pass_name");
74 rec_coll_name_ = parameters.get<std::string>("rec_coll_name");
75 track_pass_name_ = parameters.get<std::string>("track_pass_name");
76 track_coll_name_ = parameters.get<std::string>("track_coll_name");
77}
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:75

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 557 of file EcalWABRecProcessor.cxx.

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

◆ 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 551 of file EcalWABRecProcessor.cxx.

551 {
552 ldmx_log(info) << "AVG Time/Event: " << std::fixed << std::setprecision(2)
553 << processing_time_ / nevents_ << " ms";
554}

◆ 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 786 of file EcalWABRecProcessor.cxx.

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

◆ 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 79 of file EcalWABRecProcessor.cxx.

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

60{"EcalWABRec"};

Referenced by configure(), and produce().

◆ nevents_

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

Definition at line 42 of file EcalWABRecProcessor.h.

42{0};

◆ processing_time_

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

Definition at line 43 of file EcalWABRecProcessor.h.

43{0.};

◆ rec_coll_name_

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

Definition at line 39 of file EcalWABRecProcessor.h.

◆ rec_pass_name_

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

Definition at line 38 of file EcalWABRecProcessor.h.

◆ sp_pass_name_

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

Definition at line 37 of file EcalWABRecProcessor.h.

◆ track_coll_name_

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

Definition at line 41 of file EcalWABRecProcessor.h.

◆ track_pass_name_

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

Definition at line 40 of file EcalWABRecProcessor.h.


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