Services in python¶
Icetray v3 supports parameters to I3Module
instances of
arbitrary type, as shown in Allowable parameter types for python modules (any!). This means that in
various cases, given the necessary python wrappers, it is possible to
configure services for a module by passing them directly through the
module’s parameters.
The only option in V2 - pass service to module via context/factory¶
Consider the following C++ module which uses an I3RandomService
to get random numbers and put them into the frame inside an I3Double
:
class UseRandomV2 : public I3Module
{
I3RandomServicePtr rs_; // will hold service fetched manually from context
std::string rs_key_;
std::string dest_key_; // will hold name of service's location
public:
UseRandomV2(const I3Context& ctx)
: I3Module(ctx)
{
AddParameter("I3RandomServiceKey",
"my random service location",
rs_key_);
AddParameter("PutWhere",
"where the doubles go",
dest_key_);
}
void Configure()
{
GetParameter("I3RandomServiceKey", rs_key_);
GetParameter("PutWhere", dest_key_);
rs_ = context_.Get<I3RandomServicePtr>(rs_key_); // manual 'fetch service' step
}
void Physics(I3FramePtr frame)
{
double d = rs_->Gaus(0, 1);
I3DoublePtr dp(new I3Double(d));
frame->Put(dest_key_, dp);
PushFrame(frame);
}
};
I3_MODULE(UseRandomV2);
Of note are that the module takes a parameter of type ‘string’ that it
uses to locate an I3RandomService
instance in its
I3Context
.
Configuration of this module and its random service would look like this:
Of note here:
We can’t see how the
I3GSLRandomServiceFactory
knows which context to install I3GSLRandomServices in.The configuration of this random-using module
UseRandom
is smeared across the steering file.The fact that a ‘factory’ is involved is distracting. Sometimes called a ‘leaky abstraction’, this is a pattern that is intended to simplify things for the end user but doesn’t adequately hide its implementation details. It simply exchanges one kind of complexity for a different kind.
It is difficult to test the
I3GSLRandomService
… configuration and construction are tied to this factory pattern.
New option in V3 - just pass as parameter¶
The V2 way is still available, but there is now a simpler way to get the job done. The goal is to simplify configuration and testing of icetray components and if possible provide ways to do rapid prototyping.
The first requirement is that the random service in question have python wrappers. A wrapped I3GSLRandomService is usable from python like this:
>>> from icecube import icetray, phys_services
>>> rng = phys_services.I3GSLRandomService(seed = 31337)
>>> rng.Gaus(0,1)
-0.046058528394790486
>>> rng.Gaus(0,1)
-1.0140449021555507
Here we construct an instance of I3GSLRandomService, passing in the
seed value, and call the Gaus()
a couple of times. We can modify
the UseRandom
class above to take this service via parameter:
class UseRandom : public I3Module
{
I3RandomServicePtr rs;
std::string key;
public:
UseRandom(const I3Context& ctx) : I3Module(ctx)
{
AddParameter("I3RandomService", // not 'key' anymore, not a string
"my random service",
rs);
AddParameter("PutWhere",
"where the doubles go",
key);
}
void Configure()
{
GetParameter("I3RandomService", rs); // Get a randomservice right from the tray
log_debug("rndserv is at %p", rs.get());
GetParameter("PutWhere", key);
}
void Physics(I3FramePtr frame)
{
log_debug("rndserv is at %p", rs.get());
double d = rs->Gaus(0, 1);
I3DoublePtr dp(new I3Double(d));
frame->Put(key, dp);
PushFrame(frame);
}
};
So the parameter I3RandomServiceKey
, a lookup string, has been
replaced with a parameter I3RandomService
. The module
calls GetParameter()
passing the
I3RandomServicePtr
named rs
, which the steering file
connects to whatever is passed in by the user:
rndserv = phys_services.I3GSLRandomService(31334)
tray.AddModule("UseRandom", "ur",
I3RandomService = rndserv, # this parameter is a python object
PutWhere = "here")
here,
It is clear what random service is connected to what module.
You can test the I3GSLRandomService with a python script, or use it in non-icetray contexts.
Configuration is shorter
There is no ‘servicefactory’ involved.
New in icetray version 11-01-01 to ease with this transition: many modules will still need
to maintain the functionality to get some services from the context and also as a parameter.
You might expect, if you don’t explicitly pass a pointer to a service, after the call to GetParameter rs
(in the example above) should remain uninitialized as a NULL pointer (i.e. the same value it was
when it was “Add”ed). This was, in fact, not the case and would throw an error. Python didn’t
know how to convert the NoneType
object. In general it’s not clear, but when you have None
on the python side and are expecting a shared pointer it’s perfectly reasonable to convert that
to NULL pointer. So now you can decide how to handle that in the code. Here’s an example:
class UseRandom : public I3Module
{
I3RandomServicePtr rs;
std::string key;
public:
UseRandom(const I3Context& ctx) : I3Module(ctx)
{
AddParameter("I3RandomService", // not 'key' anymore, not a string
"my random service",
rs);
AddParameter("PutWhere",
"where the doubles go",
key);
}
void Configure()
{
GetParameter("I3RandomService", rs); // Get a randomservice right from the tray
if(!rs){
// This script is still using the old method and has loaded the service
// with a Factory. Without the change to I3Configuration
// the above call to GetParameter would have thrown an error.
rs = ctx_.Get<I3RandomServicePtr>()
}
log_debug("rndserv is at %p", rs.get());
GetParameter("PutWhere", key);
}
void Physics(I3FramePtr frame)
{
log_debug("rndserv is at %p", rs.get());
double d = rs->Gaus(0, 1);
I3DoublePtr dp(new I3Double(d));
frame->Put(key, dp);
PushFrame(frame);
}
};
Using services from python modules¶
If the class of a service is properly python-wrapped, like the I3GSLRandomService, it is of course just as usable from python modules as it is from c++ modules. Here is the corresponding python implementation of the UseRandom module, above:
from icecube import icetray, dataclasses
class UseRandom(icetray.I3Module):
def __init__(self, context):
icetray.I3Module.__init__(self, context)
self.AddParameter("I3RandomService", "the service", None)
self.AddParameter("PutWhere", "where the doubles go", None)
def Configure(self):
self.rs = self.GetParameter("I3RandomService")
self.where = self.GetParameter("PutWhere")
def Physics(self, frame):
rnd = self.rs.Gaus(0,1)
d = dataclasses.I3Double(rnd)
frame.Put(self.where, d)
self.PushFrame(frame)
Assuming that this class is inside file MyModules.py
, the
steering file looks nearly identical to that for the c++ version,
except UseRandom
is no longer quoted, as we pass the python
class object itself to I3Tray.AddModule()
:
from icecube import phys_services
from MyModules import UseRandom
rndserv = phys_services.I3GSLRandomService(31334)
tray.AddModule(UseRandom, "ur",
I3RandomService = rndserv,
PutWhere = "here")
Implementing services in python¶
Given the necessary python wrapper of the C++ base class (in these
examples, I3RandomService
), one can implement the service in
python and pass this to I3Modules (both C++ modules and python).
Here is an dummy python implementation, ConstantService
, of
I3RandomService
:
from icecube import icetray, dataclasses
from icecube.phys_services import I3RandomService
class ConstantService(I3RandomService):
def __init__(self, value):
I3RandomService.__init__(self)
self.value = value
def Binomial(self, ntot, prob):
return self.value
def Exp(self, tau):
return self.value
def Integer(self, imax):
return self.value
def Poisson(self, x1):
return self.value
def PoissonD(self, x1, x2):
return self.value
def Gaus(self, mean, stddev):
return self.value
The python implementation inherits from the abstract base class which forms the interface: exactly the same as in C++.
Putting this class into a file MyServices.py, you can instantiate and test this class from the python command line:
>>> from MyServices import ConstantService
>>> cs = ConstantService(value = 333)
>>> cs.Gaus(0,1)
333
>>> cs.Gaus(0,1)
333
>>> cs.Poisson(3)
333
and pass it to the UseRandom module like any other I3RandomService:
tray.AddModule("UseRandom", "ur",
I3RandomService = cs,
PutWhere = "here")
Note here that we have passed ‘UseRandom’ in quotes: we mean the C++ module. This module receives an I3RandomServicePtr in its arguments, and in this example, that randomservice will be implemented in python. The C++ module doesn’t know this, and doesn’t need to know it: it cares only that it has an object that it request random numbers from.