Back to home page

EIC code displayed by LXR

 
 

    


Warning, /tutorial-reconstruction-algorithms/episodes/07-putting-everything-together.md is written in an unsupported language. File is not indexed.

0001 ---
0002 title: "Putting everything together"
0003 teaching: 5
0004 exercises: 0
0005 ---
0006 
0007 ::::::::::::::::::::::::::::::::::::::::::::: questions
0008 
0009 - How do the factory, algorithm, config, and generator fit together in the final electron finder?
0010 
0011 :::::::::::::::::::::::::::::::::::::::::::::
0012 
0013 ::::::::::::::::::::::::::::::::::::::::::::: objectives
0014 
0015 - Get the electron finder running end-to-end.
0016 
0017 :::::::::::::::::::::::::::::::::::::::::::::
0018 
0019 ## The final ReconstructedElectron factory
0020 
0021 Here is the final `ReconstructedElectrons_factory`. The current implementation in `main` is much simpler than the variadic-input version that was originally drafted: by the time we reach the reco stage, charged tracks have already been combined with calorimeter clusters into `edm4eic::ReconstructedParticle` objects (each carrying a `getClusters()` relation), so we only need to read that one collection and apply the E/p cut on it.
0022 
0023 `src/factories/reco/ReconstructedElectrons_factory.h`:
0024 
0025 ```c++
0026 
0027 #pragma once
0028 
0029 #include "extensions/jana/JOmniFactory.h"
0030 
0031 #include "algorithms/reco/ElectronReconstruction.h"
0032 
0033 namespace eicrecon {
0034 
0035 class ReconstructedElectrons_factory
0036     : public JOmniFactory<ReconstructedElectrons_factory, ElectronReconstructionConfig> {
0037 public:
0038   using AlgoT = eicrecon::ElectronReconstruction;
0039 
0040 private:
0041   // Underlying algorithm
0042   std::unique_ptr<AlgoT> m_algo;
0043 
0044   // Declare inputs
0045   PodioInput<edm4eic::ReconstructedParticle> m_in_rc_particles{this, "ReconstructedParticles"};
0046 
0047   // Declare outputs
0048   PodioOutput<edm4eic::ReconstructedParticle> m_out_reco_particles{this};
0049 
0050   // Declare parameters
0051   ParameterRef<double> m_min_energy_over_momentum{this, "minEnergyOverMomentum",
0052                                                   config().min_energy_over_momentum};
0053   ParameterRef<double> m_max_energy_over_momentum{this, "maxEnergyOverMomentum",
0054                                                   config().max_energy_over_momentum};
0055 
0056 public:
0057   void Configure() {
0058     // This is called when the factory is instantiated.
0059     // Use this callback to make sure the algorithm is configured.
0060     // The logger, parameters, and services have all been fetched before this is called
0061     m_algo = std::make_unique<AlgoT>(GetPrefix());
0062     m_algo->level(static_cast<algorithms::LogLevel>(logger()->level()));
0063 
0064     // Pass config object to algorithm
0065     m_algo->applyConfig(config());
0066 
0067     m_algo->init();
0068   }
0069 
0070   void Process(int32_t /* run_number */, uint64_t /* event_number */) {
0071     // This is called on every event.
0072     // Use this callback to call your Algorithm using all inputs and outputs.
0073     // The inputs will have already been fetched for you at this point.
0074     m_algo->process({m_in_rc_particles()}, {m_out_reco_particles().get()});
0075 
0076     logger()->debug("Found {} reconstructed electron candidates", m_out_reco_particles()->size());
0077   }
0078 };
0079 } // namespace eicrecon
0080 
0081 ```
0082 
0083 Note that `ChangeRun()` is omitted entirely — the JOmniFactory base class provides a default no-op implementation, so a factory that doesn't need to react to run-number changes does not have to override it.
0084 
0085 Next, we register this with the `reco` plugin in `src/global/reco/reco.cc`:
0086 ```c++
0087   app->Add(new JOmniFactoryGeneratorT<ReconstructedElectrons_factory>(
0088       "ReconstructedElectrons", {"ReconstructedParticles"}, {"ReconstructedElectrons"}, {}, app));
0089 ```
0090 
0091 You can also create additional instances of the same factory with different parameter values. For example, the same `reco.cc` registers a second instance, `ReconstructedElectronsForDIS`, with a wider E/p window:
0092 ```c++
0093   app->Add(new JOmniFactoryGeneratorT<ReconstructedElectrons_factory>(
0094       "ReconstructedElectronsForDIS", {"ReconstructedParticles"}, {"ReconstructedElectronsForDIS"},
0095       {
0096           .min_energy_over_momentum = 0.7, // GeV
0097           .max_energy_over_momentum = 1.3  // GeV
0098       },
0099       app));
0100 ```
0101 
0102 And finally, we add its output collection name to the output include list in `src/services/io/podio/JEventProcessorPODIO.cc`:
0103 
0104 ```c++
0105     "ReconstructedElectrons",
0106 
0107 ```
0108 
0109 
0110 ## The ElectronReconstruction algorithm
0111 
0112 `src/algorithms/reco/ElectronReconstruction.h`:
0113 
0114 ```c++
0115 
0116 #pragma once
0117 
0118 #include <algorithms/algorithm.h>
0119 #include <edm4eic/ReconstructedParticleCollection.h>
0120 #include <string>
0121 #include <string_view>
0122 
0123 #include "ElectronReconstructionConfig.h"
0124 #include "algorithms/interfaces/WithPodConfig.h"
0125 
0126 namespace eicrecon {
0127 
0128 using ElectronReconstructionAlgorithm =
0129     algorithms::Algorithm<algorithms::Input<edm4eic::ReconstructedParticleCollection>,
0130                           algorithms::Output<edm4eic::ReconstructedParticleCollection>>;
0131 
0132 class ElectronReconstruction : public ElectronReconstructionAlgorithm,
0133                                public WithPodConfig<ElectronReconstructionConfig> {
0134 
0135 public:
0136   ElectronReconstruction(std::string_view name)
0137       : ElectronReconstructionAlgorithm{name,
0138                                         {"inputParticles"},
0139                                         {"outputParticles"},
0140                                         "selected electrons from reconstructed particles"} {}
0141 
0142   void init() final {};
0143   void process(const Input&, const Output&) const final;
0144 };
0145 
0146 } // namespace eicrecon
0147 
0148 ```
0149 
0150 A few things to note about the modern algorithm interface:
0151 
0152 - The algorithm inherits from `algorithms::Algorithm<Input<...>, Output<...>>`, so its inputs and outputs are declared *as types* in the template parameter list. The compiler then checks at the call site that the right collection types are passed in.
0153 - `WithPodConfig<ElectronReconstructionConfig>` provides the protected member `m_cfg`, which is what the algorithm uses to access its configuration values.
0154 - Logging methods (`info`, `debug`, `trace`, `warning`, `error`) are inherited — there is no `m_log` member to manage by hand.
0155 - `process()` is `const`. Any state that has to be set up once per job goes in `init()` (which here is empty); state that depends on the run number can be retrieved on the fly from `algorithms::GeoSvc` and friends.
0156 
0157 The algorithm's parameters live in a Config struct at `src/algorithms/reco/ElectronReconstructionConfig.h`:
0158 
0159 ```c++
0160 namespace eicrecon {
0161 
0162 struct ElectronReconstructionConfig {
0163 
0164   double min_energy_over_momentum = 0.9;
0165   double max_energy_over_momentum = 1.2;
0166 };
0167 
0168 } // namespace eicrecon
0169 ```
0170 
0171 The algorithm itself lives at `src/algorithms/reco/ElectronReconstruction.cc`:
0172 
0173 ```c++
0174 
0175 #include "ElectronReconstruction.h"
0176 
0177 #include <edm4eic/ClusterCollection.h>
0178 #include <edm4eic/ReconstructedParticleCollection.h>
0179 #include <edm4hep/utils/vector_utils.h>
0180 #include <fmt/core.h>
0181 #include <podio/RelationRange.h>
0182 #include <gsl/pointers>
0183 
0184 #include "algorithms/reco/ElectronReconstructionConfig.h"
0185 
0186 namespace eicrecon {
0187 
0188 void ElectronReconstruction::process(const Input& input, const Output& output) const {
0189 
0190   const auto [rcparts] = input;
0191   auto [out_electrons] = output;
0192 
0193   // out_electrons is a *subset* collection of the input ReconstructedParticles —
0194   // PODIO objects are owned by exactly one collection, and a subset collection
0195   // lets us refer to existing objects without copying or transferring ownership.
0196   out_electrons->setSubsetCollection();
0197 
0198   for (const auto particle : *rcparts) {
0199 
0200     // Skip particles without an associated cluster (no calorimeter info)
0201     if (particle.getClusters().empty()) {
0202       continue;
0203     }
0204     // Skip neutral particles (no track momentum to compare against)
0205     if (particle.getCharge() == 0) {
0206       continue;
0207     }
0208 
0209     double E      = particle.getClusters()[0].getEnergy();
0210     double p      = edm4hep::utils::magnitude(particle.getMomentum());
0211     double EOverP = E / p;
0212 
0213     trace("ReconstructedElectron: Energy={} GeV, p={} GeV, E/p = {} for PDG (from truth): {}",
0214           E, p, EOverP, particle.getPDG());
0215 
0216     // Apply the E/p cut here to select electrons
0217     if (EOverP >= m_cfg.min_energy_over_momentum &&
0218         EOverP <= m_cfg.max_energy_over_momentum) {
0219       out_electrons->push_back(particle);
0220     }
0221   }
0222   debug("Found {} electron candidates", out_electrons->size());
0223 }
0224 
0225 } // namespace eicrecon
0226 
0227 ```
0228 
0229 Compared to the truth-association-based draft from earlier in the tutorial, this version is much shorter because:
0230 
0231 - The track ↔ cluster matching has already been done upstream and is exposed via `ReconstructedParticle::getClusters()`.
0232 - We don't need any `MCRecoParticleAssociation` or `MCRecoClusterParticleAssociation` collections — the only input is the merged `ReconstructedParticles`.
0233 - We don't allocate new particles; we just keep references to the originals via a *subset* collection (`setSubsetCollection()`).
0234 
0235 The exercise from earlier episodes — wiring a custom variant that takes truth associations as additional inputs — is still a useful one. Compare your version with the version on `main` to see the trade-offs between richness of inputs and code simplicity.
0236 
0237 ::::::::::::::::::::::::::::::::::::::::::::: keypoints
0238 
0239 - The final electron finder is one factory (`ReconstructedElectrons_factory`), one algorithm (`ElectronReconstruction`), and a Config struct, wired up in `src/global/reco/reco.cc`.
0240 - Reading the pre-merged `ReconstructedParticles` (with its `getClusters()` relation) makes the algorithm short — no truth-association inputs are needed.
0241 - The output is a subset collection of selected electrons; register additional generator instances (e.g. `ReconstructedElectronsForDIS`) for different E/p windows.
0242 - Omit `ChangeRun()` when a factory has no run-dependent state; the base class provides a no-op default.
0243 
0244 :::::::::::::::::::::::::::::::::::::::::::::