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