Warning, /jana2/docs/tutorial.md is written in an unsupported language. File is not indexed.
0001 # Tutorial
0002
0003 This tutorial will walk you through creating a standalone JANA plugin, introducing the key ideas along the way.
0004 The end result for this example is available under
0005 [src/examples/Tutorial](https://github.com/JeffersonLab/JANA2/tree/master/src/examples/Tutorial).
0006
0007 ## Introduction
0008
0009 Before we begin, we need to make sure that
0010 * The JANA library is installed
0011 * The `JANA_HOME` environment variable points to the installation directory
0012 * Your `$PATH` contains `$JANA_HOME/bin`.
0013
0014 The installation process is described [here](install.md). We can quickly test that our install
0015 was successful by running a builtin benchmarking/scaling test:
0016
0017 ```
0018 jana -Pplugins=JTest -b # (cancel with Ctrl-C)
0019 ```
0020
0021 We can understand this command as follows:
0022
0023 * `jana` is the default command-line tool for launching JANA. If you would rather create your own executable which
0024 uses JANA internally, you are free to do so.
0025
0026 * The `-P` flag specifies a configuration parameter, e.g. `-Pjana:debug_plugin_loading=1` tells JANA to log
0027 detailed information about where the plugin loader went looking and what it found.
0028
0029 * `plugins` is the parameter specifying the names of plugins to load, as a comma-separated list (without spaces).
0030 By default JANA searches for these in `$JANA_HOME/plugins`, although you can also specify full paths.
0031
0032 * `-b` tells JANA to run everything in benchmark mode, i.e. it slowly increases the number of threads while
0033 measuring the overall throughput. You can cancel processing at any time by pressing Ctrl-C.
0034
0035
0036 ## Creating a JANA plugin
0037
0038 With JANA working, we can now create our own plugin. JANA provides a script which generates code skeletons
0039 to help us get started. We shall generate a skeleton for a plugin named "QuickTutorial" as follows:
0040 ```
0041 jana-generate.py Plugin QuickTutorial
0042 ```
0043
0044 This creates the following directory tree. By default, a minimal skelton is created in a single file:
0045 `QuickTutorial.cc`. This provides a JEventProcessor class as well as the the plugin entry point. The
0046 generated files include lots of comments providing helpful hints on their use.
0047
0048 ```
0049 QuickTutorial/
0050 ├── CMakeLists.txt
0051 │├─ QuickTutorial.cc
0052 ```
0053
0054 The `jana-generate.py Plugin ...` command provides some option flags as well that can be given at the
0055 end of the command line. Run `jana-generate.py --help` to see what they are.
0056
0057 ## Integrating into an existing project
0058
0059 If you are working with an existing project such as eJANA or GlueX, then you don't need the CMake
0060 project. All you need are the source files (e.g. `QuickTutorial.cc`):
0061 ```
0062 cp QuickTutorial $PATH_TO_PROJECT_SOURCE/src/plugins/QuickTutorial
0063 ```
0064
0065 Be aware that you will have to manually tell the parent CMakeLists.txt to
0066 `add_subdirectory(QuickTutorial)`.
0067
0068 The rest of the tutorial assumes that we are using a standalone plugin.
0069
0070
0071 ## Building the plugin
0072 We build and run the plugin with the following:
0073
0074 ```
0075 cd QuickTutorial
0076 mkdir build
0077 cd build
0078 cmake3 ..
0079 make install
0080 jana -Pplugins=QuickTutorial
0081 ```
0082
0083 ## Adding an event source
0084
0085 When we run this, we observe that JANA loads the plugin, opens our QuickTutorialProcessor, closes it
0086 again without processing any events, and exits. This is because there is nothing to do because we haven't
0087 specified any sources. If we are running in the context of an existing project, we can pull in event sources
0088 from other plugins and observe our processor dutifully print out the event number. For now, however, we
0089 assume that we don't have access to an event source, so we'll create one ourselves. Our first event
0090 source will emit an infinite stream of random data, so we'll name it RandomSource.
0091
0092 ```
0093 cd ..
0094 jana-generate.py JEventSource RandomSource
0095 ```
0096
0097 This creates two files, `RandomSource.cc` and `RandomSource.h`, in the current directory. We'll
0098 need to add them to `CMakeLists.txt` ourselves. Note that we retain complete control over our directory
0099 structure. In this tutorial, for simplicity, we'll keep all .h and .cc files in the topmost directory.
0100 For larger projects, `jana-generate project MyProjectName` creates
0101 a much more complex code skeleton.
0102
0103 To use our new RandomSource as-is, we need to do three things:
0104 * Add `RandomSource.cc` and `RandomSource.h` to the `add_library(...)` line in `CMakeLists.txt`.
0105 * Register our `RandomSource` with JANA inside `QuickTutorial.cc`
0106 * Rebuild the cmake project, rebuild the plugin target, and install.
0107
0108 The modified line in the CMakeLists.txt line should look like:
0109 ```
0110 add_library(QuickTutorial_plugin SHARED QuickTutorial.cc RandomSource.cc RandomSource.h)
0111 ```
0112
0113 The modified `QuickTuorial.cc` file needs to have the new `RandomSource.h` header included so
0114 it can instantiatie an object and pass it over to the JApplication in the `InitPlugin()` routine.
0115 The bottom of the file should look like this:
0116
0117 ```
0118 #include <RandomSource.h> // <- ADD THIS LINE (probably better to put this at top of file)
0119
0120 extern "C" {
0121 void InitPlugin(JApplication *app) {
0122 InitJANAPlugin(app);
0123 app->Add(new QuickTutorialProcessor);
0124 app->Add(new RandomSource); // <- ADD THIS LINE
0125 }
0126 }
0127 ```
0128
0129 And finally, rebuild ...
0130 ```
0131 cd build
0132 make install
0133 ```
0134
0135 When we run the QuickTutorial plugin now, we observe that `QuickTutorialProcessor::Process`
0136 is being called on every event. Note that `Process` is 'seeing' events slightly out-of-order. This is
0137 because there are multiple threads running `Process`, which means that we have to be careful about how
0138 we organize the work we do inside there. This will be discussed in depth later.
0139
0140 ## Configuring an event source
0141
0142 Because neither the source nor the processor are doing any 'real work', the events are being processed
0143 very quickly. To throttle the rate events get emitted, to whatever frequency we like, we can add a delay
0144 inside `GetEvent`. Perhaps we'd even like to set the emit frequency at runtime. First, we declare a member variable on
0145 `RandomSource`, initializing it to our preferred default value:
0146
0147 ```
0148 class RandomSource : public JEventSource {
0149 int m_max_emit_freq_hz = 100; // <- ADD THIS LINE
0150
0151 public:
0152 RandomSource();
0153 RandomSource(std::string resource_name, JApplication* app);
0154 virtual ~RandomSource() = default;
0155 void Open() override;
0156 void Close() override;
0157 Result Emit(JEvent& event) override;
0158 static std::string GetDescription();
0159 };
0160 ```
0161
0162 Next we sync the variable with the parameter manager inside `Open`. We do this by calling
0163 `JApplication::SetDefaultParameter`, which tells JANA to look among its configuration parameters for one
0164 called "random_source:max_emit_freq_hz". If it finds one, it
0165 sets `m_max_emit_freq_hz` to the value it found. Otherwise, it leaves the variable alone. JANA remembers
0166 all such 'default parameters' along with their default values so that it can report them and generate config
0167 files. Note that we conventionally prefix our parameter names with the name of the requesting component or
0168 plugin. This helps prevent namespace collisions.
0169
0170 ```
0171 void RandomSource::Open() {
0172 JApplication* app = GetApplication(); // <- ADD THIS LINE
0173 app->SetDefaultParameter("random_source:max_emit_freq_hz", // <- ADD THIS LINE
0174 m_max_emit_freq_hz, // <- ADD THIS LINE
0175 "Maximum event rate [Hz] for RandomSource"); // <- ADD THIS LINE
0176 }
0177 ```
0178
0179 We can now use the value of `m_max_emit_freq_hz`, confident that it is consistent with the current
0180 runtime configuration:
0181
0182 ```
0183 JEventSource::Result RandomSource::Emit(JEvent& event) {
0184
0185 /// Configure event and run numbers
0186 static size_t current_event_number = 1;
0187 event.SetEventNumber(current_event_number++);
0188 event.SetRunNumber(22);
0189
0190 /// Slow down event source // <- ADD THIS LINE
0191 auto delay_ms = std::chrono::milliseconds(1000/m_max_emit_freq_hz); // <- ADD THIS LINE
0192 std::this_thread::sleep_for(delay_ms); // <- ADD THIS LINE
0193
0194 return Result::Success;
0195 }
0196 ```
0197
0198 Finally, we can set this parameter on the command line and observe the throughput change accordingly:
0199
0200 ```
0201 jana -Pplugins=QuickTutorial -Prandom_source:max_emit_freq_hz=10
0202 ```
0203
0204
0205 ## Creating JObjects
0206
0207 So far `RandomSource` has been emitting events with no data attached. Now we'd like to have them
0208 emit randomly generated 'Hit' objects which simulate the readout from a detector. First, we need
0209 to set up our data model. Although we can insert pointers of any kind into our `JEvent`, we strongly
0210 recommend using `JObjects` for reasons we will discuss later.
0211
0212 ```
0213 cd src
0214 jana-generate.py JObject Hit
0215 ```
0216
0217 JObjects are meant to be plain-old data. For this tutorial we pretend that our detector consists of a 3D grid
0218 of sensors, each of which measures some energy at some time. Note that we are declaring `Hit` to be a `struct`
0219 instead of a `class`. This is because `JObjects` should be lightweight containers with no creation logic and
0220 no invariants which need to be encapsulated. JObjects are free to contain pointers to arbitrary data types and
0221 nested STL containers, but the recommended approach is to maintain a flat structure of primitives whenever possible.
0222 A JObject should conceptually resemble a row in a database table.
0223
0224 ```
0225 struct Hit : public JObject {
0226 int x; // Pixel coordinates
0227 int y; // Pixel coordinates
0228 double E; // Energy loss in GeV
0229 double t; // Time in us
0230
0231 // Make it possible to construct a Hit as a one-liner
0232 Hit(int x, int y, double E, double t) : x(x), y(y), E(E), t(t) {};
0233 ...
0234 ```
0235
0236 The only additional thing we need to fill out is the `Summarize` method, which aids in debugging and introspection.
0237 Basically, it tells JANA how to convert this JObject into a (structured) string. Inside `Summarize`, we add each of
0238 our primitive member variables to the provided `JObjectSummary`, along with the variable name, a C-style format
0239 specifier, and a description of what that variable means. JANA provides a `NAME_OF` macro so that if we rename a
0240 member variable using automatic refactoring tools, it will automatically update the string representation of the variable
0241 name as well.
0242
0243 ```
0244 ...
0245 void Summarize(JObjectSummary& summary) const override {
0246 summary.add(x, NAME_OF(x), "%d", "Pixel coordinates centered around 0,0");
0247 summary.add(y, NAME_OF(y), "%d", "Pixel coordinates centered around 0,0");
0248 summary.add(E, NAME_OF(E), "%f", "Energy loss in GeV");
0249 summary.add(t, NAME_OF(t), "%f", "Time in us");
0250 }
0251 }
0252 ```
0253
0254 ## Inserting JObjects into a JEvent
0255
0256 Now it is time to have our `RandomSource` emit events which contain `Hit` objects. For the sake
0257 of brevity, we shall keep our hit generation logic as simple as possible: four hits which are constant.
0258 We can make our detector simulation arbitrarily complex, but be aware that `JEventSources` only run on
0259 a single thread by default, so complex simulations can reduce the event rate. Synchronizing `GetEvent` makes our job
0260 easier, however, because we can manipulate non-thread-local state such as file pointers or cursors or message buffers
0261 without having to worry about race conditions and deadlocks.
0262
0263 The pattern we use for inserting data into the event is simple: For data of type `T`, create a `std::vector<T*>`, fill
0264 it, and pass it to `JEvent::Insert`, which will move its contents directly into the `JEvent` object. If we want,
0265 when we insert we can also specify a tag, which is just a string. The purpose of a tag is to provide an extra level
0266 of granularity. For instance, if we have two detectors which both use the `Hit` datatype but have separate processing
0267 logic, we want to be able to access them independently.
0268
0269 ```
0270 #include "Hit.h"
0271 // ...
0272
0273 Result RandomSource::Emit(JEvent& event) {
0274 // ...
0275
0276 /// Insert simulated data into event // ADD ME
0277
0278 std::vector<Hit*> hits; // ADD ME
0279 hits.push_back(new Hit(0, 0, 1.0, 0)); // ADD ME
0280 hits.push_back(new Hit(0, 1, 1.0, 0)); // ADD ME
0281 hits.push_back(new Hit(1, 0, 1.0, 0)); // ADD ME
0282 hits.push_back(new Hit(1, 1, 1.0, 0)); // ADD ME
0283
0284 event.Insert(hits); // ADD ME
0285 //event.Insert(hits, "fcal"); // If we used a tag
0286
0287 return Result::Success;
0288 }
0289 ```
0290
0291
0292 ## Writing our own JEventProcessor
0293
0294 A JEventProcessor does two things: It calculates a bunch of intermediate results
0295 for each event (this part is done in parallel), and then it aggregates those results
0296 into a single output (this part is done sequentially). The canonical example is to
0297 calculate clusters, track candidates, and tracks separately for each event, and then
0298 produce a histogram using all of the tracks of all of the events.
0299
0300 In this section, we are going to modify the automatically generated `TutorialProcessor` to
0301 produce a heatmap that only uses hit data. We discuss how to structure more complicated
0302 calculations later. First, we add a quick-and-dirty heatmap member variable:
0303
0304 ```
0305 class QuickTutorialProcessor : public JEventProcessor {
0306 double m_heatmap[100][100]; // ADD ME
0307 std::mutex m_mutex;
0308
0309 public:
0310 // ...
0311 ```
0312
0313 The heatmap itself is a piece of _shared_ _state_. We have to be careful because if
0314 multiple threads try to read and write to this shared state, they will conflict with each
0315 other and corrupt it. This means we have to protect _who_ can access it and _when_.
0316 Only QuickTutorialProcessor should be able to access it, so we make it a private member.
0317 However, this is not enough. Only one thread running `QuickTutorialProcessor::Process` must
0318 be allowed to access it at a time, which we enforce using `m_mutex`. Let's look at how this
0319 is used:
0320
0321 ```
0322 #include "Hit.h" // ADD ME
0323
0324 void QuickTutorialProcessor::Process(const std::shared_ptr<const JEvent> &event) {
0325
0326 /// Do everything we can in parallel
0327 /// Warning: We are only allowed to use local variables and `event` here
0328 auto hits = event->Get<Hit>(); // ADD ME
0329
0330 /// Lock mutex
0331 std::lock_guard<std::mutex>lock(m_mutex);
0332
0333 /// Do the rest sequentially
0334 /// Now we are free to access shared state such as m_heatmap
0335 for (const Hit* hit : hits) { // ADD ME
0336 m_heatmap[hit->x][hit->y] += hit->E; // ADD ME
0337 }
0338 }
0339 ```
0340
0341 As you can see, we do everything we can in parallel, before we lock our mutex. All we are
0342 doing for now is retrieve the `Hit` objects we `Insert`ed earlier, however, as we will later
0343 see, virtually all of our per-event computations will be called from here. Remember that
0344 we should _only_ access local variables and data retrieved from a `JEvent` at first, whereas
0345 after we lock the mutex, we are free to access our private member variables as well.
0346
0347 We proceed to define our `Init` and `Finish` methods. The former zeroes out each bucket and
0348 the latter prints the heatmap to standard out as ASCII art. Note that if we want to output
0349 our results to a file all at once, we should do so in `Finish`. `Finish` will be called even
0350 if we forcibly terminate JANA with Ctrl-C. On the other hand, if we wanted to write to a file
0351 incrementally, we can open it in `Init`, access it `Process`, and close it in `Finish`.
0352
0353 ```
0354 void QuickTutorialProcessor::Init() {
0355 LOG << "QuickTutorialProcessor::Init: Initializing heatmap" << LOG_END;
0356
0357 for (int i=0; i<100; ++i) {
0358 for (int j=0; j<100; ++j) {
0359 m_heatmap[i][j] = 0.0;
0360 }
0361 }
0362 }
0363
0364 void QuickTutorialProcessor::Finish() {
0365 LOG << "QuickTutorialProcessor::Finish: Displaying heatmap" << LOG_END;
0366
0367 double min_value = m_heatmap[0][0];
0368 double max_value = m_heatmap[0][0];
0369
0370 for (int i=0; i<100; ++i) {
0371 for (int j=0; j<100; ++j) {
0372 double value = m_heatmap[i][j];
0373 if (min_value > value) min_value = value;
0374 if (max_value < value) max_value = value;
0375 }
0376 }
0377 if (min_value != max_value) {
0378 char ramp[] = " .:-=+*#%@";
0379 for (int i=0; i<100; ++i) {
0380 for (int j=0; j<100; ++j) {
0381 int shade = int((m_heatmap[i][j] - min_value)/(max_value - min_value) * 9);
0382 std::cout << ramp[shade];
0383 }
0384 std::cout << std::endl;
0385 }
0386 }
0387 }
0388 ```
0389
0390 ## Organizing computations using JFactories
0391
0392 Just as JANA uses JObjects to organize experiment data, it uses JFactories to organize the algorithms for processing said data.
0393
0394 JFactories are slightly different from the 'Factory' design patterns: rather than abstracting away the subclass of the
0395 object being constructed, JFactories abstract away the multiplicity instead. This is a good match for nuclear and
0396 high-energy physics, where m inputs produce n outputs and n isn't always known until after the algorithm has finished.
0397 JFactories confer other benefits as well:
0398
0399 - Algorithms can be swapped at runtime
0400 - Results are calculated only if they are needed ('lazy')
0401 - Results are only calculated once and then reused as needed ('memoized')
0402 - JFactories are agnostic as to whether their inputs were calculated by another JFactory or inserted by a JEventSource
0403 - Different paths for deriving a result may come into play depending on the source data
0404
0405 For this example, we create a simple algorithm computing clusters, given hit data. We start by generating a cluster
0406 JObject:
0407
0408 ```jana-generate.py JObject Cluster```
0409
0410 We fill out the `Cluster.h` skeleton, defining a cluster to be the coordinates of its center along with the total energy
0411 and time interval. Note that using JObjects helps keep our domain model malleable, so we can evolve it over
0412 time as we learn more.
0413
0414 ```
0415 struct Cluster : public JObject {
0416 double x_center; // Pixel coordinates centered around 0,0
0417 double y_center; // Pixel coordinates centered around 0,0
0418 double E_tot; // Energy loss in GeV
0419 double t_begin; // Time in us
0420 double t_end; // Time in us
0421
0422 Cluster(double x_center, double y_center, double E_tot, double t_begin, double t_end)
0423 : x_center(x_center), y_center(y_center), E_tot(E_tot), t_begin(t_begin), t_end(t_end) {};
0424
0425 void Summarize(JObjectSummary& summary) const override {
0426 summary.add(x_center, NAME_OF(x_center), "%f", "Pixel coords <- [0,80)");
0427 summary.add(y_center, NAME_OF(y_center), "%f", "Pixel coords <- [0,24)");
0428 summary.add(E_tot, NAME_OF(E_tot), "%f", "Energy loss in GeV");
0429 summary.add(t_begin, NAME_OF(t_begin), "%f", "Earliest observed time in us");
0430 summary.add(t_end, NAME_OF(t_end), "%f", "Latest observed time in us");
0431 }
0432 ...
0433 }
0434 ```
0435
0436 Now we generate a JFactory which will compute n `Cluster`s given m `Hit`s. Note that
0437 we need to provide both the classname of our factory and the classname of the JObject
0438 it produces.
0439
0440 ```jana-generate.py JFactory SimpleClusterFactory Cluster```
0441
0442 The heart of a JFactory is the function `Process`, where we take an event, extract
0443 whatever inputs we need by calling `JEvent::Get` or one of its variants, produce
0444 some number of outputs, and publish them by calling `JFactory::Set`. These outputs
0445 will stay cached as long as the current event is in flight and get cleared afterwards.
0446 To keep things really simple, our example shall assume there is only one cluster and all of the
0447 hits associated with this event belong to it.
0448
0449 ```
0450 #include "Hit.h"
0451 // ...
0452
0453 void SimpleClusterFactory::Process(const std::shared_ptr<const JEvent> &event) {
0454
0455 auto hits = event->Get<Hit>();
0456
0457 auto cluster = new Cluster(0,0,0,0,0);
0458 for (auto hit : hits) {
0459 cluster->x_center += hit->x;
0460 cluster->y_center += hit->y;
0461 cluster->E_tot += hit->E;
0462 if (cluster->t_begin > hit->t) cluster->t_begin = hit->t;
0463 if (cluster->t_end < hit->t) cluster->t_end = hit->t;
0464 }
0465 cluster->x_center /= hits.size();
0466 cluster->y_center /= hits.size();
0467
0468 std::vector<Cluster*> results;
0469 results.push_back(cluster);
0470 Set(results);
0471 }
0472 ```
0473
0474 For our tutorial, we don't need to do anything inside `Init` or `ChangeRun`. Usually,
0475 these are useful for collecting statistics, or when the algorithm depends on calibration
0476 constants which we want to cache. We are free to access member variables without locking
0477 a mutex because a JFactory is assigned to at most one thread at a time.
0478
0479 Although JFactories are relatively simple, there are several important details.
0480 First, because each instance is assigned at most one thread, it won't see the entire event stream.
0481 Second, there will be at least as many instances of each JFactory in existence as
0482 threads, and possibly more depending on how JANA is configured, so `Initialize` and
0483 `ChangeRun` should be fast. Thirdly, although it is tempting to use static variables
0484 to share state between different instances of the same JFactory, this practice is
0485 discouraged. That state should live in a JService instead.
0486
0487 Next, we register our `SimpleClusterFactory` with our JApplication. Because JANA will
0488 need arbitrarily many instances of these, we pass in a `JFactoryGenerator` which
0489 knows how to create a `SimpleClusterFactory`. As long as our JFactory has a zero-argument
0490 constructor, this is easy:
0491
0492 ```
0493 #include <JANA/JFactoryGenerator.h> // ADD ME
0494 #include "SimpleClusterFactory.h" // ADD ME
0495 // ...
0496
0497 extern "C" {
0498 void InitPlugin(JApplication* app) {
0499
0500 InitJANAPlugin(app);
0501
0502 app->Add(new QuickTutorialProcessor);
0503 app->Add(new RandomSource);
0504 app->Add(new JFactoryGeneratorT<SimpleClusterFactory>); // ADD ME
0505 }
0506 }
0507 ```
0508
0509 We are now free to modify `QuickTutorialProcessor` (or create a new
0510 `JEventProcessor`) which histograms clusters instead of hits. Crucially,
0511 `JEvent::Get` doesn't care whether the `JObjects` were `Insert`ed by an event
0512 source or whether they were `Set` by a `JFactory`. The interface for retrieving
0513 them is the same either way.
0514
0515 ## Reading files using a JEventSource
0516
0517 Earlier we created a `JEventSource` which we added directly to the
0518 `JApplication`. This works well for simple cases but becomes cumbersome due to
0519 the amount of configuration needed: First we'd have to tell the plugin which
0520 `JEventSource` to register, then tell that source which files to open, and we'd have
0521 to do this for each `JEventSource` separately. Instead, JANA gives us a cleaner option tailored
0522 to our workflow: we specify a set of input URIs (a.k.a. file paths or sockets) and let JANA decide
0523 which JEventSource to instantiate for each. Thus we prefer to call JANA like this:
0524
0525 ```
0526 jana -PQuickTutorial,CsvSourcePlugin,RootSourcePlugin path/to/file1.csv path/to/file2.root
0527 ```
0528
0529 In order to make this happen, we need to define a `JEventSourceGenerator`. This is conceptually
0530 similar to the `JFactoryGenerator` we mentioned earlier, with one important addition: a method which
0531 reports back the likelihood that the underlying event source can make sense of that resource.
0532 Let's remove the line where we added the `RandomSource` instance directly to the JApplication, and replace it with
0533 a corresponding `JEventSourceGenerator`:
0534
0535
0536 ```
0537 #include <JANA/JApplication.h>
0538 #include <JANA/JFactoryGenerator.h>
0539 #include <JANA/JEventSourceGeneratorT.h> // ADD ME
0540
0541 #include "Hit.h"
0542 #include "RandomSource.h"
0543 #include "QuickTutorialProcessor.h"
0544 #include "SimpleClusterFactory.h"
0545
0546 extern "C" {
0547 void InitPlugin(JApplication* app) {
0548
0549 InitJANAPlugin(app);
0550
0551 app->Add(new QuickTutorialProcessor);
0552 // app->Add(new RandomSource); // REMOVE ME
0553 app->Add(new JEventSourceGeneratorT<RandomSource>); // ADD ME
0554 app->Add(new JFactoryGeneratorT<SimpleClusterFactory>);
0555 }
0556 }
0557 ```
0558
0559 By default, `JEventSourceGeneratorT` will report a confidence of 0.1 that it can open
0560 any resource it is given. Let's make this more realistic: suppose we want to use this
0561 event source if and only if the resource name is "random". In `RandomSource.h`, observe
0562 that `jana-generate.py` already declared for us:
0563
0564 ```
0565 template <>
0566 double JEventSourceGeneratorT<RandomSource>::CheckOpenable(std::string);
0567 ```
0568
0569 We fill out the definition in `RandomSource.cc`:
0570
0571 ```
0572 template <>
0573 double JEventSourceGeneratorT<RandomSource>::CheckOpenable(std::string resource_name) {
0574 return (resource_name == "random") ? 1.0 : 0.0;
0575 }
0576 ```
0577
0578 Note that `JEventSourceGenerator` puts some constraints on our `JEventSource`. Specifically,
0579 we need to note that:
0580
0581 - Our `JEventSource` needs a two-argument constructor which accepts a string containing
0582 the resource name, and a `JApplication` pointer.
0583
0584 - Our `JEventSource` needs a static method `GetDescription`, to help JANA report to the user which sources
0585 are available and which ended up being chosen.
0586
0587 - In case we need to override JANA's preferred JEventSource for some resource, we can specify the
0588 typename of the event source we'd rather use instead via the configuration parameter `event_source_type`.
0589
0590 - When we implement `Open` for an event source that reads a file, we get the filename
0591 from `JEventSource::GetResourceName()`.
0592
0593
0594 ## Exercises for the reader
0595
0596 - Create a new `JEventProcessor` which generates a heatmap of `Clusters` instead of `Hits`.
0597
0598 - Create a `BetterClusterFactory` which handles multiple clusters per event. Bonus points if
0599 it is a lightweight wrapper around an industrial-strength clustering algorithm.
0600 Inside `InitPlugin`, use a configuration parameter to decide which `JFactoryT<Cluster>`
0601 gets registered with the `JApplication`.
0602
0603 - Use tags to register both `ClusterFactories` with the `JApplication`. Create a
0604 `JEventProcessor` which asks for the results from both algorithms and compares their results.
0605
0606
0607