Conventions for @configurable function arguments
@nnolte started a discussion in !207 (closed):
it was discussed yesterday whether this way of putting in cuts is useful, but i missed it. Was there a conclusion?
For @configurable
functions defined in trigger lines, we should decide on a convention for how the configuration is defined. There are several approaches, of which a few are:
- Put all cut values as arguments
@configurable
def displaced_kaons(min_MIPCHI2=4.0, min_PT=1*GeV, ...):
functor = (MIPCHI2(make_pvs()) > min_MIPCHI2) & (PT > min_PT) & ...
return ParticleFilter(Functor=functor, Input=make_kaons())
- Put the whole functor as an argument
@configurable
def displaced_kaons(functor=(MIPCHI2(make_pvs()) > 4.0) & (PT > 1*GeV) & ...):
return ParticleFilter(Functor=functor, Input=make_kaons())
or, very similarly, passing a dictionary of thresholds
@configurable
def displaced_kaons(thresholds):
functor = (MIPCHI2(make_pvs()) > thresholds['min_MIPCHI2']) & (PT > thresholds['min_PT']) & ...
return ParticleFilter(Functor=functor, Input=make_kaons())
- Put none of the selection as an argument
@configurable
def displaced_kaons():
functor = (MIPCHI2(make_pvs()) > 4.0) & (PT > 1*GeV) & ...
return ParticleFilter(Functor=functor, Input=make_kaons())
I'll go through each here, giving my own take on the pros and cons (in many cases, the pros can also be considered as cons and vice-versa).
Option 1
Similar to HLT2 and the Stripping in Run 2 in that the cut thresholds are 'exposed' to configuration via dictionaries.
Configuring
with displaced_kaons.bind(min_MIPCHI2=10.0):
# do stuff
# Or call directly
displaced_kaons(min_MIPCHI2=10.0)
We can likely think up of a way to have this exposed as an actual dict
in some settings file somewhere, e.g. an HltSettings module, and then map the keys and values of this dictionary to .bind
calls. Then the 'user-facing' aspect of configuration is very similar to Run 2.
Pros and cons
- Somewhat similar to what people are used to
👍 - Clear what each function will cut on
👍 - Multiple ways to override a value; how to know which to use when?
👎 - Functions end up having many arguments, making the lines harder to inspect visually
👎
Option 2
Similar in many ways to option 1. Configuring is identical, except that one must pass a whole functor expression to bind
or the function call.
Pros and cons
- Clear what each function will cut on
👍 - Easier to change the selection itself, rather than just the thresholds
👍 - Multiple ways to override a value; how to know which to use when?
👎 - Need to express the whole functor even if only changing a single threshold
👎 - Large functor expressions as default args will be ugly and hard parse visually
👎 - Cannot define default arguments to functors and then use them
👎 e.g. if one needs a configurable PV maker like below
@configurable
def displaced_kaons(make_pvs=make_pvs, min_MIPCHI2=4.0, min_PT=1*GeV, ...):
functor = (MIPCHI2(make_pvs()) > min_MIPCHI2) & (PT > min_PT) & ...
return ParticleFilter(Functor=functor, Input=make_kaons())
Option 3
Pros and cons
- Clear exactly what selection the function will apply, no
bind
magic or overriding will change the thresholds👍 - Clear and lean function definitions, might negate the need to use
@configurable
in many cases👍 - No way to override anything (I think; without some real magic)
👎
Discussion
We have a few high-level considerations:
- What do we want
@configurable
functions to look like? How should configuration be discoverable? - When should a caller use
.bind
over overriding arguments directly/ - What do we want the equivalent of HltSettings to look like? Do we even want it? What if almost nothing is configurable in a selection, and we just have to edit the selection source itself to change things?
- How do we enforce whatever conventions we end up deciding upon?
@gligorov suggested one way that might help us answer these questions:
A helpful way to pick might be to consciously implement a couple of lines in each of the different possible ways and then look at them together. Particularly non-trivial cases where you have intermediate particles which get shared between some lines but not always with the same cut values, which will expose the real differences between the options.