3.4.13. A simple python module

This lesson will give you an idea about the structure and use of basf2 modules. Most of the modules in our software are implemented in C++ and are made available for analysis by modularAnalysis. This package consists of python wrapper functions around the C++ modules in order to use them in the python steering file. You have already learned about this in The basics.

C++ is very strong and fast, but usually much more complicated to read and write than Python. For this reason the basf2 framework provides the possibility to write modules also in Python. This can be very helpful if you want to investigate or test something.

To put your hands on this, simply copy the code into a python file and run it with basf2 my_python_module.py. It is nothing more than a steering file with your own class.

Minimal example

Let’s begin with the following minimal example for a new python module. It is the “Hello World” of basf2 modules. The magic happens in the class MinModule(basf2.Module). In this basic example, the class only consists of one member function event that is called once for each event. We use the logging function basf2.B2INFO() to print our message. To execute the model, we need to create a path and generate dummy events. Then, our module is added to the path and we run the path.

 1#!/usr/bin/env python3
 2
 3import basf2 as b2
 4
 5
 6class MinModule(b2.Module):
 7    """Very minimal class to print Hello World in each event"""
 8
 9    def event(self):
10        """Event function, called once for each event"""
11        b2.B2INFO("Hello World!")
12
13
14# create a path
15main = b2.Path()
16
17# set to generate 10 dummy events
18main.add_module("EventInfoSetter", evtNumList=[10])
19
20# and add our module
21main.add_module(MinModule())
22
23# run the path
24b2.process(main)

You can see that implementing a minimal python module just takes 5 lines of code (3 without documentation) so it’s very nice for fast and quick prototyping.

Note

  • Python modules have to be implemented or imported in the steering file

  • Python modules are usually much slower then C++ modules but for many small tasks this does not make a significant difference.

  • These hacky modules will not appear in module list (basf2 -m)

  • Python modules can only be used in analysis code or private scripts. Only C++ modules can be added to the official reconstruction code that is run for HLT or for calibration.

Detailed usage

Let’s extend the minimal example class above to show all methods which can be implemented by a Python module. As you have seen above all the member functions are optional.

 1#!/usr/bin/env python3
 2
 3import basf2 as b2
 4
 5
 6class MinModule(b2.Module):
 7    """A minimal example of a basf2 module in python."""
 8
 9    def __init__(self):
10        """Constructor"""
11        # call constructor of base class, required if you implement __init__
12        # yourself!
13        super().__init__()
14        # and do whatever else is necessary like declaring member variables
15
16    def initialize(self):
17        """Called once in the beginning just before starting processing"""
18        b2.B2INFO("initialize()")
19
20    def beginRun(self):
21        """Called every time a run changes before the actual events in that run
22        are processed
23        """
24        b2.B2INFO("beginRun()")
25
26    def event(self):
27        """Called once for each event"""
28        b2.B2INFO("event()")
29
30    def endRun(self):
31        """Called every time a run changes after the actual events in that run
32        were processed
33        """
34        b2.B2INFO("endRun()")
35
36    def terminate(self):
37        """Called once after all the processing is complete"""
38        b2.B2INFO("terminate()")
39
40
41# create a path
42main = b2.Path()
43
44# set to generate 10 dummy events
45main.add_module("EventInfoSetter", evtNumList=[10])
46
47# and add our module
48main.add_module(MinModule())
49
50# run the path
51b2.process(main)

Accessing Datastore Objects

Datastore objects can be accessed via the PyStoreArray class and the PyStoreObj classes. Let’s create a small module which will print the event number and information on MCParticles, namely the PDG code. To have tracks available, we will use the ParticleGun module, which generates very simple events.

Exercise

Write a Python module that prints the number of charged particles per event and the total charge. Note: per default, the ParticleGun generates only one track per event, but you can adjust this.

Hint

You can find information on the Particle class in doxygen. The ParticleGun has the option nTracks.

Solution

 1#!/usr/bin/env python3
 2
 3import basf2 as b2
 4from ROOT import Belle2
 5
 6
 7class AccessingDataStoreModule(b2.Module):
 8    """An example of a basf2 module in python which accesses things in the datastore."""
 9
10    def initialize(self):
11        """Create a member to access event info and the MCParticles
12        StoreArray
13        """
14        #: an example object from the datastore (the metadata collection for the event)
15        self.eventinfo = Belle2.PyStoreObj("EventMetaData")
16        #: an example array from the datastore (the list of MC particles)
17        self.particles = Belle2.PyStoreArray("MCParticles")
18
19    def event(self):
20        """Print the number of charged particles and the total charge"""
21        n_charged = 0
22        total_charge = 0
23        for particle in self.particles:
24            charge = particle.getCharge()
25            if charge:
26                n_charged += 1
27            total_charge += charge
28
29        b2.B2INFO(
30            f"Number of charged particles = {n_charged}, "
31            f"total charge of event = {total_charge}"
32        )
33
34
35# create a path
36main = b2.Path()
37
38# generate events
39main.add_module("EventInfoSetter", evtNumList=[10])
40
41# generate events with 3 tracks (not all of them are charged tracks)
42main.add_module("ParticleGun", nTracks=3)
43
44# and add our module
45main.add_module(AccessingDataStoreModule())
46
47# run the path
48b2.process(main)

Note

For PyStoreObj you can access most members of the underlying class directly, like eventinfo.getEvent() above. However if you want to get the object directly or want to access a member which also exists by the same name in PyStoreObj you can use the obj() member to get a reference to the underlying object itself: eventinfo.obj().getEvent()

More advanced examples

  • framework/examples/cdcplotmodule.py - A full example that uses matplotlib to plot CDCSimHits

  • framework/examples/interactive_python.py drops into an interactive (i)python shell inside the event() function, allowing exploration of available objects and data structures

  • reconstruction/examples/plot_LL_diff.py - Gets PID log-likelihoods, uses relations to get corresponding MC truth and fills ROOT histograms accordingly

Stuck? We can help!

If you get stuck or have any questions to the online book material, the #starterkit-workshop channel in our chat is full of nice people who will provide fast help.

Refer to Collaborative Tools. for other places to get help if you have specific or detailed questions about your own analysis.

Improving things!

If you know how to do it, we recommend you to report bugs and other requests with JIRA. Make sure to use the documentation-training component of the Belle II Software project.

If you just want to give very quick feedback, use the last box “Quick feedback”.

Please make sure to be as precise as possible to make it easier for us to fix things! So for example:

  • typos (where?)

  • missing bits of information (what?)

  • bugs (what did you do? what goes wrong?)

  • too hard exercises (which one?)

  • etc.

If you are familiar with git and want to create your first pull request for the software, take a look at How to contribute. We’d be happy to have you on the team!

Quick feedback!

Author of this lesson

Pascal Schmolz