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