Source code for order.process

# coding: utf-8

Classes to define physics processes.

__all__ = ["Process"]

import sys

import six
from scinum import Number

from order.unique import UniqueObject, UniqueObjectIndex, unique_tree
from order.mixins import CopyMixin, AuxDataMixin, TagMixin, DataSourceMixin, LabelMixin, ColorMixin
from order.util import typed

[docs]@unique_tree(parents=-1, deep_children=True, deep_parents=True) class Process( UniqueObject, CopyMixin, AuxDataMixin, TagMixin, DataSourceMixin, LabelMixin, ColorMixin, ): r""" Definition of a phyiscs process. **Arguments** *xsecs* should be a mapping of (e.g.) center-of-mass energies to cross sections values (automatically converted to `scinum.Number <>`__ instances). *color*, *color1*, *color2* and *color3* are forwarded to the :py:class:`~order.mixins.ColorMixin`, *label* and *label_short* to the :py:class:`~order.mixins.LabelMixin`, *is_data* to the :py:class:`~order.mixins.DataSourceMixin`, *tags* to the :py:class:`~order.mixins.TagMixin`, *aux* to the :py:class:`~order.mixins.AuxDataMixin`, and *name* and *id* to the :py:class:`~order.unique.UniqueObject` constructor. A process can have parent-child relations to other processes. Initial child processes are set to *processes*. **Copy behavior** ``copy()`` All attributes are copied. Also, please be aware that deep copies of heavily nested process structures might lead to python running into a recursion error (``maximum recursion depth exceeded while calling a Python object``). If this is the case, you might want to consider `increasing the recursion depth <>`__. ``copy_shallow()`` All attributes except for (child) :py:attr:`processes` and :py:attr:`parent_processes` are copied. **Example** .. code-block:: python import order as od from scinum import Number, REL p = od.Process( name="ttH", id=1, xsecs={ 13: Number(0.5071, {"scale": 0.036j}), # +-3.6% scale uncertainty }, label=r"$t\bar{t}H$", color=(255, 0, 0), ) p.get_xsec(13).str("%.2f") # -> "0.51 +- 0.02 (scale)" p.label_root # -> "t#bar{t}H" p2 = p.add_process( name="ttH_bb", id=2, xsecs={ 13: p.get_xsec(13) * 0.5824, }, label=p.label + r", $b\bar{b}$", ) p2 == p.get_process("ttH_bb") # -> True p2.label_root # -> "t#bar{t}H, b#bar{b}" p2.has_parent_process("ttH") # -> True **Members** .. py:attribute:: xsecs type: dictionary (any -> :py:class:`scinum.Number`) Cross sections values mapped to keys (e.g. center-of-mass energies). """ cls_name_singular = "process" cls_name_plural = "processes" # attributes for copying copy_specs = ( [ { "attr": "_processes", "skip_shallow": True, "skip_value": CopyMixin.Deferred(lambda inst: UniqueObjectIndex(cls=Process)), }, { "attr": "_parent_processes", "skip_shallow": True, "skip_value": CopyMixin.Deferred(lambda inst: UniqueObjectIndex(cls=Process)), }, ] + UniqueObject.copy_specs + AuxDataMixin.copy_specs + TagMixin.copy_specs + DataSourceMixin.copy_specs + LabelMixin.copy_specs + ColorMixin.copy_specs ) def __init__( self, name, id, xsecs=None, processes=None, color=None, color1=None, color2=None, color3=None, label=None, label_short=None, is_data=False, tags=None, aux=None, ): UniqueObject.__init__(self, name, id) CopyMixin.__init__(self) AuxDataMixin.__init__(self, aux=aux) TagMixin.__init__(self, tags=tags) DataSourceMixin.__init__(self, is_data=is_data) LabelMixin.__init__(self, label=label, label_short=label_short) ColorMixin.__init__(self, color=color, color1=color1, color2=color2, color3=color3) # instance members self._xsecs = {} # set initial values if xsecs is not None: self.xsecs = xsecs # set initial child processes if processes is not None: self.extend_processes(processes) @typed def xsecs(self, xsecs): # xsecs parser try: xsecs = dict(xsecs) except: raise TypeError("invalid xsecs type: {}".format(xsecs)) # parse particular values _xsecs = {} for key, xsec in xsecs.items(): # when key is a number, make sure it's a float if isinstance(key, (int, Number)): key = float(key) # value check if not isinstance(xsec, Number): try: xsec = Number(xsec) except: raise TypeError("invalid xsec value type: {}".format(xsec)) _xsecs[key] = xsec return _xsecs
[docs] def get_xsec(self, key): """ Returns the cross section (a `scinum.Number <>`__ instance) for a *key* (e.g. a center-of-mass energy). When *key* is an integer or a number instance, it is converted to float first. """ if isinstance(key, (int, Number)): key = float(key) return self.xsecs[key]
[docs] def set_xsec(self, key, xsec): """ Sets the cross section for a *key* (e.g. a center-of-mass energy) to *xsec*. When *key* is an integer or a number instance, it is converted to float first. When *xsec* is not a `scinum.Number <>`__ instance, it is converted to one. The (probably converted) value is returned. """ key, xsec = list(self.__class__.xsecs.fparse(self, {key: xsec}).items())[0] self.xsecs[key] = xsec return xsec
[docs] def pretty_print(self, xsec_key=None, offset=40, max_depth=-1, stream=None, _depth=0, **kwargs): """ pretty_print(xsec_key=None, offset=40, max_depth=-1, stream=None, **kwargs) Prints this process and potentially its subprocesses down to a maximum depth *max_depth* in a structured fashion. When *xsec_key* is set, process cross section values are shown as well with a maximum horizontal distance of *offset*. *stream* can be a file object and defaults to *sys.stdout*. All *kwargs* are forwarded to the :py:meth:`Number.str` methods of the cross section numbers. """ if not stream: stream = sys.stdout # start the entry to print entry = "| " * _depth + "> {} ({})".format(, # add cross-section values following an offset when xsec_key is set if xsec_key is not None: entry += " " * (offset - len(entry)) xsec = self.xsecs.get(xsec_key) entry += " " * _depth + (xsec.str(**kwargs) if xsec else "no cross-section") stream.write(six.b(entry + "\n") if six.PY3 else (entry + "\n")) # stop here when max_depth is reached if 0 <= max_depth <= _depth: return for proc in self.processes: proc.pretty_print( xsec_key=xsec_key, offset=offset, max_depth=max_depth, stream=stream, _depth=_depth + 1, **kwargs # noqa: C815 )