Warning, /tutorial-reconstruction-algorithms/episodes/02-creating-a-factory.md is written in an unsupported language. File is not indexed.
0001 ---
0002 title: "Creating a factory"
0003 teaching: 10
0004 exercises: 1
0005 ---
0006
0007 ::::::::::::::::::::::::::::::::::::::::::::: questions
0008
0009 - What is the difference between an algorithm and a factory?
0010 - How is EICrecon's plugin structure organized, and where do new factories go?
0011 - Which factory base class should I use, and what does its interface look like?
0012
0013 :::::::::::::::::::::::::::::::::::::::::::::
0014
0015 ::::::::::::::::::::::::::::::::::::::::::::: objectives
0016
0017 - Understand the basics of EICrecon's plugin structure.
0018 - Understand where to put new factories.
0019 - Understand which factory base class to use.
0020 - Understand the JOmniFactory interface.
0021
0022 :::::::::::::::::::::::::::::::::::::::::::::
0023
0024 ## Algorithms and Factories
0025
0026 We make a crisp distinction between *algorithms* and *factories*.
0027
0028 *Algorithms* are classes that perform one kind of calculation we need and they do so in a generic, framework-independent way. *Factories*, in comparison, are classes that attach Algorithms to the JANA framework. You should write Algorithms to be independently testable, and you should write a Factory so that JANA can use the Algorithm within EICrecon. The factory layer handles issues like obtaining all of the inputs from other factories, publishing the outputs so that other factories can use them or they can be written to file, obtaining the correct parameters, making sure the Algorithm has been initialized, and making sure that the correct calibrations are loaded when the run number changes.
0029
0030 Here's an example to help illustrate what goes into the Algorithm and what goes into the Factory. Consider calorimeter clustering. The clustering algorithm should be independent of any individual detector, and have a set of parameters that control its behavior and live in a plain-old-data `Config` object. You could copy-paste this code into a codebase that uses a completely different reconstruction framework and it would still work, as long as you were using the same datamodel (e.g. `edm4hep`). Each detector could have its own factory (if it calls the algorithm in a substantially different way) or they may all use the same factory (if the factories only differ in their parameter values). The parameter values themselves could be hardcoded to the factory, but we strongly prefer to set them externally using a factory generator. This gives us a cleaner separation of configuration from code, and will let us do fun things in the future such as wiring factories together from an external config file, or performing parameter studies.
0031
0032
0033 ## The basics of EICrecon's plugin structure
0034
0035 JANA plugins are a mechanism for controlling which parts of `EICrecon` get compiled and linked together. They give us the ability to avoid having to compile and link heavy dependencies that not everybody will be using all the time. For instance, by default EICrecon uses ACTS for tracking, but perhaps someone wants to benchmark ACTS against Genfit -- we wouldn't want to have to ship Genfit inside eic-shell all the time.
0036
0037 Plugins were also designed so that users could integrate their analyses directly into reconstruction while keeping them independent and optional. This pattern is heavily used in the GlueX experiment and recommended in the tutorials on JANA's own documentation. In EICrecon, we set up separate plugins for each detector and each benchmark, but not for each analysis. We strongly recommend following the advice given in the [analysis tutorials](https://eic.github.io/tutorial-analysis/) instead. See the [JANA2 end-user plugin tutorial](https://eic.github.io/tutorial-jana2/03-end-user-plugin.html) for instructions on adding a new plugin.
0038
0039
0040
0041 ## Where to put new factories
0042
0043 The EICrecon plugins are organized as follows. Under `src/detectors` we have subdirectories for each individual detector, and each of them corresponds to one plugin that adds the detector's factory generators. Benchmarks are analogous. If an algorithm/factory will only ever be used in that one context, it can live there; otherwise, and preferably, the corresponding algorithm lives under `src/algorithms` and the corresponding factory lives under `src/factories`.
0044
0045 Once you figure out which plugin your algorithm naturally belongs to, find its `InitPlugin()` method. By convention this lives in a `.cc` file with the same name as the plugin itself. This is where you will add your factory generator.
0046
0047 ## Which factory base class to use
0048
0049
0050 There are a number of different kinds of factories available in JANA which we have used within EICrecon at different points in time. Luckily, if you are writing an Algorithm from scratch, there is only one you will need to be familiar with: `JOmniFactory`. However, some of the earlier ones are still around, and just in case you need to modify or reuse those, here is a quick history lesson.
0051
0052
0053 `JFactoryT<T>` is JANA's fundamental factory base class. However, we don't use it in EICrecon because it has the following limitations:
0054
0055 1. It has difficulty with PODIO data. PODIO data needs very special handling, otherwise it will leak memory or corrupt your object associations. To address this, we developed `JFactoryPodioT`, which extends `JFactoryT` to support PODIO correctly.
0056
0057 2. *It can only output one collection.* This might seem fine at first, but frequently we need to output "Association" collections alongside the primary output collection. To address this, we developed `JMultifactory`, which supports multiple outputs, including PODIO data.
0058
0059 3. If you want to reuse an Algorithm in a different context, you need to duplicate the JFactoryT/JPodioFactoryT/JMultifactory. Until this point, collection and parameter names were hardcoded inside individual factories. To get around this, we developed `JChainMultifactoryT` so that we could create multiple instances of the same factory and assign them different collection and parameter names in a logical way.
0060
0061 4. It requires a deeper understanding of JANA internals to use correctly. The user is allowed to perform actions inside the factory callbacks that don't necessarily make sense. We remedied this issue by developing `JOmniFactory`, which *declares* what it needs upfront, and JANA *provides* it only when it makes sense. `JOmniFactory` supports all of the functionality developed for points (1), (2), and (3), and presents a simpler interface.
0062
0063
0064 In summary, always use `JOmniFactory` if you are writing something new. The migration of all EICrecon factories to `JOmniFactory` (tracked in [EICrecon issue #1176](https://github.com/eic/EICrecon/issues/1176)) is essentially complete, so you should not encounter the older base classes in current code.
0065
0066
0067 ## The JOmniFactory interface
0068
0069
0070 The basic idea behind an OmniFactory is to declare what you need upfront. That way, the framework can retrieve everything you need at the right time,
0071 and it can handle complex namespacing logic behind the scenes so that you can dynamically rewire and reconfigure factories.
0072
0073 Earlier factory base classes, such as JChainMultifactory, require users to do a lot in their callbacks. Not so with JOmniFactory, which moves most of the functionality into registered members instead, as we shall discuss later. The callbacks are still there, but are made much simple, and focus on satisfying the underlying Algorithm's needs instead of JANA's.
0074
0075 These are the callbacks you'll need to implement:
0076 ```c++
0077 void Configure();
0078 void ChangeRun(int32_t run_number);
0079 void Process(int32_t run_number, uint64_t event_number);
0080 ```
0081
0082 `Configure` is called once when the factory is instantiated. This is where the user should initialize the underlying Algorithm. JANA will have already fetched the services, configured the logger, and set the values of the `Config` struct, so all the user needs to do is pass these things to the Algorithm.
0083
0084 `ChangeRun` is called once JANA detects that a new run has been started. This is where the user should update calibration data or other resources keyed off of the run number. JOmniFactory also provides a `Resource` registered member to automatically retrieve data from an arbitrary Service, though this is still experimental.
0085
0086 `Process` is called for every event. (Side note: Although note that because different threads have different factory instances, any individual factory cannot be guaranteed to witness the entire event stream. If you need to have one instance that processes the entire event stream, JANA provides JEventProcessors and JEventProcessorSequential for that purpose.) JANA will have already prefetched the registered Inputs before `Process` is called. The user needs to execute the Algorithm using those inputs, and copy the resulting outputs back to the registered Outputs. JANA will then take care of publishing the outputs downstream.
0087
0088 Note that unlike earlier factory base classes, JOmniFactory uses the [Curiously Recurring Template Pattern](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) so that the callback methods aren't virtual. This lets the optimizer get rid of any performance penalty for the extra layer of indirection.
0089
0090 Here is the full JOmniFactory code skeleton:
0091
0092 ```c++
0093
0094 #pragma once
0095 #include "extensions/jana/JOmniFactory.h"
0096
0097 class ReconstructedElectrons_factory : public JOmniFactory<ReconstructedElectrons_factory> {
0098 private:
0099
0100 // Declare inputs and outputs
0101 // PodioInput<edm4hep::MCParticle> m_in_mc_particles {this, "MCParticles"};
0102 // PodioOutput<edm4eic::ReconstructedParticle> m_out_reco_particles {this};
0103
0104 // Declare parameters
0105 // ParameterRef<double> m_min_energy_over_momentum {this, "minEnergyOverMomentum", config().min_energy_over_momentum};
0106
0107 // Declare services
0108 // Service<DD4hep_service> m_geoSvc {this};
0109
0110 public:
0111 void Configure() {
0112 // This is called when the factory is instantiated.
0113 // Use this callback to make sure the algorithm is configured.
0114 // The logger, parameters, and services have all been fetched before this is called
0115 }
0116
0117 void ChangeRun(int32_t run_number) {
0118 // This is called whenever the run number is changed.
0119 // Use this callback to retrieve state that is keyed off of run number.
0120 }
0121
0122 void Process(int32_t run_number, uint64_t event_number) {
0123 // This is called on every event.
0124 // Use this callback to call your Algorithm using all inputs and outputs
0125 // The inputs will have already been fetched for you at this point.
0126 // m_algo->process({...}, {...});
0127
0128 logger()->debug( "Event {}: Calling Process()", event_number );
0129 }
0130 };
0131 ```
0132
0133 ## The JOmniFactory inputs and outputs
0134
0135 The user specifies the JOmniFactory's inputs by declaring `PodioInput` or `VariadicPodioInput` objects as data members. These are templated on the basic PODIO type (Not the collection type or mutable type or object type or data type), and require the user to pass `this` as a constructor argument. These objects immediately register themselves with the factory, so that the factory always knows exactly what data it needs to fetch. To access the data once it has been fetched, the user can call the object's `operator()`, which returns a constant pointer to a PODIO collection of the correct type. For instance, suppose the user declares the data member:
0136
0137 ```c++
0138 PodioInput<edm4hep::MCParticle> m_particles_in {this};
0139 ```
0140
0141 In this case, the user would access the input data like this:
0142
0143 ```c++
0144 const edm4hep::MCParticleCollection* particles_in = m_particles_in();
0145 ```
0146
0147 Of course, for brevity, the user could simply pass `m_particles_in()` straight into the algorithm, and write the algorithm output through `m_particles_out().get()` — which is the pattern most factories use today:
0148 ```c++
0149 m_algo->process({m_particles_in()}, {m_particles_out().get()});
0150 ```
0151
0152 As you have just seen, PodioOutputs are very analogous to PodioInputs.
0153
0154
0155 ::::::::::::::::::::::::::::::::::::::::::::: challenge
0156
0157 ## Exercise
0158
0159 - Create your own ElectronReconstruction factory from the code skeleton above.
0160 - Give your OmniFactory a single output collection.
0161 - Have its `Process()` method produce some log output.
0162 - Experiment with giving it different input collections.
0163
0164 ::::::::::::::: solution
0165
0166 Copy the skeleton into a new header under `src/factories/reco/`, keep a single `PodioOutput`
0167 member, and add a `logger()->debug(...)` call inside `Process()`. Adding or changing `PodioInput`
0168 members changes which collections JANA fetches for you; the factory will fail to run if you request
0169 a collection that no other factory produces.
0170
0171 :::::::::::::::
0172
0173 :::::::::::::::::::::::::::::::::::::::::::::
0174
0175 ::::::::::::::::::::::::::::::::::::::::::::: keypoints
0176
0177 - *Algorithms* do framework-independent calculation; *factories* attach algorithms to JANA2.
0178 - Plugins control what gets compiled; register factory generators in a plugin's `InitPlugin()`.
0179 - Reusable algorithms go in `src/algorithms` and their factories in `src/factories`.
0180 - Always use `JOmniFactory` for new code; declare inputs, outputs, parameters, and services as registered members.
0181
0182 :::::::::::::::::::::::::::::::::::::::::::::