I3Modules in Python

Dump

The Dump module just prints the table of contents of the frames that it receives. We’ll implement this module in python and then expand on it to make it do something useful.

We start by importing project icetray:

from icecube import icetray

and define a class that inherits from I3Module, with a do-nothing constructor, (the __init__ function). It forwards to the underlying constructor of I3Module and takes, the same as in C++, one argument which is an I3Context. The Physics method just prints and pushes the frame that it is passed:

class PyDump(icetray.I3Module):

    def __init__(self, context):
        icetray.I3Module.__init__(self, context)

    def Physics(self, frame):
        print frame
        self.PushFrame(frame)

one puts all of this into a file PyDump.py, and uses it as follows:

#!/usr/bin/env python3

from PyDump import PyDump
from icecube.icetray import I3Tray

tray = I3Tray()

tray.AddModule('BottomlessSource')

tray.AddModule(PyDump)

tray.Execute(10)

When you run this script, you should see ten empty frames go by.

Parameter handling

Python I3Modules add and get parameters very similar to C++ I3Modules. Here is a module that puts I3Int objects into the frames that go by, with consecutive increasing values:

from icecube import icetray

class PutInts(icetray.I3Module):

    def __init__(self, context):
        icetray.I3Module.__init__(self, context)
        self.AddParameter('Where',                     # name
                          'Where to put those ints',   # doc
                          'somewhere')                 # default

        self.AddParameter('StartWith',                 # name
                          'Start at this value',       # doc
                          0)                           # default

    def Configure(self):
        self.where = self.GetParameter('Where')
        self.value = self.GetParameter('StartWith')

    def Physics(self, frame):
        newint = icetray.I3Int(self.value)    # create the int
        frame[self.where] = newint            # put it in the frame
        self.PushFrame(frame)                 # push the frame
        self.value += 1                       # increment our value

the values of the parameters specified with AddParameter() and GetParameter() are passed to the tray the same as for C++ modules. The call to AddModule() takes the python class object, not an instance of the class. If we’ve stored the module above in a file PutInts.py:

from PutInts import PutInts
from icecube import icetray # (etc)

...

tray.AddModule(PutInts, 'pi',
               Where = 'intval_location',
               StartWith = 13)

Note

Similar to functions, note that we pass the bare python object to the I3Tray, not its name in a string, as with C++ modules. i.e. it isn’t this:

tray.AddModule('PutInts', 'pi', ...

it is this:

tray.AddModule(PutInts, 'pi', ....

and that symbol PutInts of course must be known to the script, typically via a call to import.

Allowable parameter types for python modules (any!)

One effect of the integration of python and C++ in icetray is that python I3Modules can take parameters of any type. For instance, this module takes a python dictionary as an argument, using the keys in the dictionary as frame locations to store I3Doubles, whose values are the values found in the dictionary:

class MultiAdder(I3Module):
    def __init__(self, context):
        I3Module.__init__(self, context)
        self.AddParameter("values", "key/value pairs to put into the frame", None)

    def Configure(self):
        self.d = self.GetParameter("values")
        print ">>>>> Configured with", self.d

    def Physics(self, frame):
        for (k,v) in self.d.items():
            i = icetray.I3Int(v)
            frame.Put(k, i)
        self.PushFrame(frame)

You pass the python dictionary parameter to the tray in the same way you would pass any other parameter. Here we pass it as a literal:

tray.AddModule(MultiAdder, "mod",
               values = { 'one' : 1,
                          'two' : 2,
                          'three' : 777 })

Putting the MultiAdder module between a BottomlessSource and a Dump, you should see frames going by that look like this:

[ I3Frame :
  'one' ==> I3Int
  'three' ==> I3Int
  'two' ==> I3Int
]

Parameters can be input/output

Python objects like lists have identity. That is, if I create a dictionary that two python identifiers point to, and change the dictionary via one identifier, the other will see the change:

>>> d = { 'one' : 1, 'two' : 2 }
>>> e = d
>>> e
{'two': 2, 'one': 1}
>>> e['three'] = 3
>>> e
{'three': 3, 'two': 2, 'one': 1}
>>> d
{'three': 3, 'two': 2, 'one': 1}

which makes it easy to extract and collect values from the run of a tray, via its parameters (this is considerably cleaner, and even less trouble, than doing it via globals). This module extracts and collects the values of passing I3Ints in the frame:

class IntCollector(I3Module):
    def __init__(self, context):
        I3Module.__init__(self, context)
        self.AddParameter("where", "where to get the ints from", None)
        self.AddParameter("dest", "where to put the collected values", None)

    def Configure(self):
        self.where = self.GetParameter("where")
        self.dest = self.GetParameter("dest")

    def Physics(self, frame):
        self.dest.append(frame[self.where].value)
        self.PushFrame(frame)

when configuring this module, we would pass an empty list, referenced by an existing identifier, to the dest parameter:

dest_list = []
#
# add modules to get data from somewhere here
#

tray.AddModule(IntCollector, 'collect',
               where = 'i3int_location',
               dest = dest_list)

tray.Execute()

print "The values we collected are:", dest_list

of course to pass a literal empty list to the IntCollector module does us no good, though it is legal:

tray.AddModule(IntCollector, 'collect',
               where = 'i3int_location',
               dest = [])

as we have no way to access the data when the tray has finished executing. The possibilities here are quite large: you could pass functions to modules, frame objects like I3Geometry … feel free to get messy.