Back to home page

EIC code displayed by LXR

 
 

    


Warning, /tutorial-reconstruction-algorithms/episodes/05-adding-an-algorithm.md is written in an unsupported language. File is not indexed.

0001 ---
0002 title: "Adding an algorithm"
0003 teaching: 5
0004 exercises: 1
0005 ---
0006 
0007 ::::::::::::::::::::::::::::::::::::::::::::: questions
0008 
0009 - What is the difference between a factory and an algorithm?
0010 - Where does algorithm code live, what does its interface look like, and how does a factory call it?
0011 
0012 :::::::::::::::::::::::::::::::::::::::::::::
0013 
0014 ::::::::::::::::::::::::::::::::::::::::::::: objectives
0015 
0016 - Understand the difference between a factory and an algorithm.
0017 - Understand where to put the algorithm code.
0018 - Understand the basic algorithm interface.
0019 - Understand how to call an algorithm from a factory.
0020 
0021 :::::::::::::::::::::::::::::::::::::::::::::
0022 
0023 ## The difference between a factory and an algorithm
0024 
0025 *Algorithms* are classes that perform one kind of calculation we need and they do so in a generic, framework-independent way. The core of an Algorithm is a method called `process` which takes a tuple of input PODIO collections and a tuple of output PODIO collections. Algorithms don't know or care where the inputs come from and where they go. Algorithms also don't know much about where their parameters come from; rather, they are passed a `Config` structure which contains the parameters' values. The nice thing about algorithms is that they are simple to design and test, and easy to reuse for different detectors, frameworks, or even entire experiments.
0026 
0027 
0028 In EICrecon, all new algorithms inherit from the templated `algorithms::Algorithm<Input<...>, Output<...>>` interface (provided by the [eic/algorithms](https://github.com/eic/algorithms) "framework-less framework"), and use the `WithPodConfig<ConfigT>` mixin to attach a configuration struct. The `algorithms::Algorithm` base provides logging facilities (`info()`, `debug()`, `trace()`, ...) and a structured way of declaring inputs and outputs by their PODIO collection types.
0029 
0030 ## Where to put the algorithm code
0031 
0032 All algorithms that are not specific to a single detector should go under `src/algorithms`. Because this falls in the category of reconstruction, we'll put it in `src/algorithms/reco`.
0033 
0034 
0035 ## The basic algorithm interface
0036 
0037 Here is a template for an algorithm header file:
0038 
0039 ```c++
0040 
0041 #pragma once
0042 
0043 #include <algorithms/algorithm.h>
0044 // #include relevant edm4eic / edm4hep collection headers here
0045 
0046 #include "MyAlgorithmNameConfig.h"
0047 #include "algorithms/interfaces/WithPodConfig.h"
0048 
0049 namespace eicrecon {
0050 
0051     using MyAlgorithmNameAlgorithm =
0052         algorithms::Algorithm<algorithms::Input<MyInputCollection>,
0053                               algorithms::Output<MyOutputCollection>>;
0054 
0055     class MyAlgorithmName : public MyAlgorithmNameAlgorithm,
0056                             public WithPodConfig<MyAlgorithmNameConfig> {
0057 
0058     public:
0059 
0060         MyAlgorithmName(std::string_view name)
0061             : MyAlgorithmNameAlgorithm{name,
0062                                        {"inputCollectionName"},
0063                                        {"outputCollectionName"},
0064                                        "Short description of what this algorithm does."} {}
0065 
0066         // init() is called once before processing starts. Most algorithms do not need it.
0067         void init() final {};
0068 
0069         // process() does the actual work for each event. The Input/Output tuples
0070         // contain pointers to the PODIO collections.
0071         void process(const Input&, const Output&) const final;
0072 
0073     };
0074 } // namespace eicrecon
0075 
0076 ```
0077 
0078 A few things worth noting:
0079 
0080 - The class is *templated* on the list of input and output collection types. The `Input` and `Output` aliases inside the class expand into `std::tuple` of pointers (`gsl::not_null<const T*>` for inputs, `T*` for outputs).
0081 - `process()` is `const` — algorithms must not mutate their own state during event processing. Run-by-run state should be set up in `init()` instead.
0082 - Logging is inherited from `algorithms::AlgorithmBase`, so inside `process()` you simply call `info(...)`, `debug(...)`, or `trace(...)` directly — no logger pointer needs to be passed in.
0083 - The configuration struct is held by `WithPodConfig` and accessible as the protected member `m_cfg`.
0084 
0085 The corresponding implementation file unpacks the input and output tuples with structured bindings:
0086 
0087 ```c++
0088 
0089 #include "MyAlgorithmName.h"
0090 
0091 namespace eicrecon {
0092 
0093     void MyAlgorithmName::process(const Input& input, const Output& output) const {
0094 
0095         const auto [in_particles] = input;
0096         auto [out_particles]      = output;
0097 
0098         // ... fill out_particles using in_particles and m_cfg ...
0099     }
0100 
0101 } // namespace eicrecon
0102 
0103 ```
0104 
0105 ## How to call an algorithm from a factory
0106 
0107 The code to call an algorithm from a factory generally follows a specific pattern:
0108 
0109 ```c++
0110     void Configure() {
0111         // This is called when the factory is instantiated.
0112         // Use this callback to make sure the algorithm is configured.
0113         // The logger, parameters, and services have all been fetched before this is called
0114 
0115         // Construct the algorithm with the factory's prefix as its name —
0116         // this is what hooks the algorithm's logger up to the same prefix as the factory.
0117         m_algo = std::make_unique<eicrecon::ElectronReconstruction>(GetPrefix());
0118 
0119         // Forward the JANA log level down to the algorithm.
0120         m_algo->level(static_cast<algorithms::LogLevel>(logger()->level()));
0121 
0122         // Pass the config object to the algorithm.
0123         m_algo->applyConfig(config());
0124 
0125         // Call init() once. Note that init() takes no arguments — services
0126         // (e.g. geometry) are accessed by the algorithms framework via the
0127         // algorithms::ServiceSvc / algorithms::GeoSvc, not by passing pointers in.
0128         m_algo->init();
0129     }
0130 
0131     void Process(int32_t /* run_number */, uint64_t /* event_number */) {
0132         // This is called on every event. The inputs will have already been fetched.
0133         // Call process() with brace-enclosed lists of input pointers and output pointers.
0134         m_algo->process({m_in_particles()}, {m_out_particles().get()});
0135     }
0136 ```
0137 
0138 
0139 ::::::::::::::::::::::::::::::::::::::::::::: challenge
0140 
0141 ## Exercise
0142 
0143 - Create your own ElectronReconstruction algorithm using the code skeleton above.
0144 - Print some log messages from your algorithm's `process()` method using `info(...)` / `debug(...)`.
0145 - Have your ElectronReconstruction factory call the algorithm.
0146 - Run this end-to-end.
0147 
0148 ::::::::::::::: solution
0149 
0150 Create `ElectronReconstruction.h`/`.cc` (plus a `ElectronReconstructionConfig.h`) under
0151 `src/algorithms/reco`, inheriting from `algorithms::Algorithm<Input<...>, Output<...>>` and
0152 `WithPodConfig`. Add `debug(...)` calls in `process()`. In the factory's `Configure()`, construct
0153 the algorithm with `GetPrefix()`, `applyConfig(config())`, and `init()`; in `Process()` call
0154 `m_algo->process({...}, {...})`. Rebuilding EICrecon and running `eicrecon` at debug log level
0155 should show your messages.
0156 
0157 :::::::::::::::
0158 
0159 :::::::::::::::::::::::::::::::::::::::::::::
0160 
0161 ::::::::::::::::::::::::::::::::::::::::::::: keypoints
0162 
0163 - Algorithms do framework-independent work; their core is a `const process(const Input&, const Output&)` method.
0164 - Reusable algorithms live under `src/algorithms` (here `src/algorithms/reco`).
0165 - Algorithms inherit logging and declare inputs/outputs as template types; config lives in `m_cfg` via `WithPodConfig`.
0166 - A factory constructs the algorithm in `Configure()` (`applyConfig`, `init`) and calls `process()` in `Process()`.
0167 
0168 :::::::::::::::::::::::::::::::::::::::::::::