###############################################################################
# (c) Copyright 2022 CERN for the benefit of the LHCb and FCC Collaborations #
# #
# This software is distributed under the terms of the Apache License #
# version 2 (Apache-2.0), copied verbatim in the file "COPYING". #
# #
# In applying this licence, CERN does not waive the privileges and immunities #
# granted to it by virtue of its status as an Intergovernmental Organization #
# or submit itself to any jurisdiction. #
###############################################################################
__author__ = "Dominik Muller, Michal Mazurek, and Gloria Corti"
__email__ = "lhcb-simulation@cern.ch"
import os
import tempfile
import time
import urllib.request
from Gaudi.Configuration import appendPostConfigAction, log
from Gaussino.Generation import GaussinoGeneration
from Gaussino.Geometry import GaussinoGeometry
from Gaussino.Simulation import GaussinoSimulation
# Configurables (do NOT use 'from Configurables' here)
from Gaussino.Utilities import GaussinoConfigurable
from Gaussino.Visualization import GaussinoVisualization
[docs]
class Gaussino(GaussinoConfigurable):
"""
Main Configurable of Gaussino. It is dedicated to the
confiuration of general properties. Please, visit other
configurables for more options:
- :class:`GaussinoGeneration <Gaussino.Generation.GaussinoGeneration>` (configuration of the generation phase)
- :class:`GaussinoSimulation <Gaussino.Simulation.GaussinoSimulation>` (configuration of the simulation phase)
- :class:`GaussinoGeometry <Gaussino.Geometry.GaussinoGeometry>` (configuration of the geometry)
**Main**
:var EvtMax: default: ``-1``,
no. of event to produce, must be > 0
:vartype EvtMax: int, required
:var Phases: default: ``["Generator","Simulation"]``,
possible only: ``["Generator", "Simulation"]``
or ``["Generation"]``
:vartype Phases: list, optional
:var FirstEventNumber: default: ``1``
:vartype FirstEventNumber: int, optional
:var RunNumber: default: ``1``
:vartype RunNumber: int, optional
**Output**
:var Histograms: default: ``"DEFAULT"``
:vartype Histograms: str, optional
:var DatasetName: default: ``"Gaussino"``
:vartype DatasetName: str, optional
:var DatasetNameForced: default: ``False``
:vartype DatasetNameForced: bool, optional
:var OutputType: default: ``'SIM'``
:vartype OutputType: str, optional
**Multi-threading**
:var EnableHive: default: ``True``,
must be always set (for now)
:vartype EnableHive: bool, optional
:var ThreadPoolSize: default: ``1``
:vartype ThreadPoolSize: int, optional
:var EventSlots: default: ``1``
:vartype EventSlots: int, optional
:var FirstTimingEvent: default: ``1``
:vartype FirstTimingEvent: int, optional
**Other**
:var Debug: default: ``False``,
increase verbosity for the whole application
:vartype Debug: bool, optional
:var ReDecay: default: ``False``
:vartype ReDecay: bool, optional
:var ConvertEDM: default: ``False``
:vartype ConvertEDM: bool, optional
:var ParticleTable: default: ``'$GAUSSINOROOT/data/ParticleTable.txt'``
:vartype ParticleTable: str, optional
"""
__used_configurables__ = [
GaussinoGeneration,
GaussinoSimulation,
GaussinoGeometry,
GaussinoVisualization,
]
__slots__ = {
# MAIN
"EvtMax": -1,
"Phases": ["Generator", "Simulation"],
"FirstEventNumber": 1,
"RunNumber": 1,
# OUTPUT
"Histograms": "DEFAULT",
"DatasetName": "Gaussino",
"DatasetNameForced": False,
"OutputType": "SIM",
# Multi-threading
"EnableHive": True,
"ThreadPoolSize": 1,
"EventSlots": 1,
"FirstTimingEvent": 1,
# OTHER
"Debug": False,
"ReDecay": False,
"ConvertEDM": False,
"ParticleTable": "$GAUSSINOROOT/data/ParticleTable.txt",
# FIXME: Spillover not supported yet
# "SpilloverPaths": [],
# FIXME: FSR not supported yet
# "WriteFSR": True,
# "MergeGenFSR": False,
# ML options
"MLOptions": {},
}
[docs]
def __apply_configuration__(self):
"""Main configuration method for Gaussino. It is called as the first one, and
then propagates the properties to:
- :class:`GaussinoGeneration <Gaussino.Generation.GaussinoGeneration>`,
- :class:`GaussinoSimulation <Gaussino.Simulation.GaussinoSimulation>`,
- :class:`GaussinoGeometry <Gaussino.Geometry.GaussinoGeometry>`.
"""
self._set_debug_mode()
self._check_options_compatibility()
# MT options
self._setup_hive()
self._setup_geant4MT()
# Seed
self._configure_rnd_init()
# Services
self._configure_services()
# Phases
self._configure_generation_phase()
# self._configure_simulation_phase()
# EDM conversion
self._configure_edm_conversion()
# ML options
self._set_ML_options()
from Configurables import ApplicationMgr
ApplicationMgr().EvtMax = self.getProp("EvtMax")
ApplicationMgr().EvtSel = "NONE"
# ensure the configurables are called
for conf in self.__used_configurables__:
conf()
[docs]
def _set_debug_mode(self):
"""Sets up the debug mode in python logger and all the configurables."""
if self.getProp("Debug"):
log.setLevel("DEBUG")
def debug_all_configurables():
from Gaudi.Configuration import DEBUG
from GaudiKernel.Configurable import Configurable
for conf in Configurable.allConfigurables.values():
try:
conf.OutputLevel = DEBUG
except AttributeError:
pass
appendPostConfigAction(debug_all_configurables)
[docs]
def _check_options_compatibility(self):
"""Checks the general compatibility of the properties.
Raises:
ValueError: if ``EvtMax`` is not provided
"""
evtMax = self.getProp("EvtMax")
if evtMax <= 0:
raise ValueError("EvtMax must be > 0")
timing_event = self.getProp("FirstTimingEvent")
if timing_event != 1:
if timing_event >= evtMax:
raise ValueError("FirstTimingEvent must be < EvtMax")
if timing_event < self.getProp("ThreadPoolSize") + 2:
raise ValueError("FirstTimingEvent must be > ThreadPoolSize + 2")
[docs]
def _setup_hive(self):
"""Enables Hive event loop manager.
this is a very similar method as in LHCbApp
Raises:
ValueError: if ``EnableHive`` is disabled
"""
if not self.getProp("EnableHive"):
# FIXME: Running without GaudiHive has not been tested
# and may lead to unexpected behaviour
# this is disabled for now
log.error(
"EnableHive must be set. Running without "
"GaudiHive has not been tested and may lead to"
"unexpected behaviour"
)
raise ValueError("EnableHive must be set.")
from Configurables import (
ApplicationMgr,
AvalancheSchedulerSvc,
GenRndInit,
HiveSlimEventLoopMgr,
HiveWhiteBoard,
)
whiteboard = HiveWhiteBoard(
"EventDataSvc",
EventSlots=self.getProp("EventSlots"),
ForceLeaves=True,
RootCLID=1,
)
ApplicationMgr().ExtSvc.insert(0, whiteboard)
scheduler = AvalancheSchedulerSvc()
eventloopmgr = HiveSlimEventLoopMgr(SchedulerName=scheduler)
# initialize hive settings if not already set
self.propagateProperty("ThreadPoolSize", eventloopmgr)
scheduler.ThreadPoolSize = self.getProp("ThreadPoolSize")
ApplicationMgr().EventLoop = eventloopmgr
# propagate the barrier to GenRndInit
GenRndInit().FirstTimingEvent = self.getProp("FirstTimingEvent")
[docs]
def _setup_geant4MT(self):
"""Sets up the Geant4 multi-threading options."""
from Configurables import GiGaMT
GiGaMT().NumberOfWorkerThreads = self.getProp("ThreadPoolSize")
[docs]
def _set_particle_property_service(self):
"""Sets up the particle property service.
.. todo ::
LHCb project dependency!
"""
from Configurables import ApplicationMgr, LHCb__ParticlePropertySvc
log.debug("Configuring ParticlePropertySvc")
ppservice = LHCb__ParticlePropertySvc()
ppservice.ParticlePropertiesFile = self.getProp("ParticleTable")
ApplicationMgr().ExtSvc += [ppservice]
[docs]
def _set_auditor_service(self):
"""Sets up the auditor service: ``AuditorSvc``."""
from Configurables import ApplicationMgr, AuditorSvc
log.debug("Configuring AuditorSvc")
ApplicationMgr().ExtSvc += ["AuditorSvc"]
ApplicationMgr().AuditAlgorithms = True
AuditorSvc().Auditors += ["TimingAuditor"]
[docs]
def _set_redecay_service(self):
"""Sets up a dedicated service when using ReDecay: ``ReDecaySvc``."""
if not self.getProp("ReDecay"):
return
from Configurables import ApplicationMgr, ReDecaySvc
log.debug("Configuring ReDecaySvc")
redecaysvc = ReDecaySvc()
redecaysvc.EvtMax = self.getProp("EvtMax")
ApplicationMgr().ExtSvc += [redecaysvc]
[docs]
def _set_histogram_service(self):
"""Sets up the service responsible for producing histograms.
Raises:
ValueError: when unknown option in the list of ``Historgrams``
"""
from Configurables import (
ApplicationMgr,
HistogramPersistencySvc,
RootHistCnv__PersSvc,
)
log.debug("Configuring HistogramPersistencySvc")
ApplicationMgr().HistogramPersistency = "ROOT"
RootHistCnv__PersSvc().ForceAlphaIds = True
hist_opt = self.getProp("Histograms").upper()
if hist_opt not in ["NONE", "DEFAULT"]:
msg = f"Unknown Histograms option '{hist_opt}'."
log.error(msg)
raise ValueError(msg)
if hist_opt == "NONE":
log.warning("No histograms will be produced")
return
# Use a default histogram file name if not already set
hst_prs_svc = HistogramPersistencySvc()
if not hst_prs_svc.isPropertySet("OutputFile"):
histos_name = self._get_output_name() + "-histos.root"
hst_prs_svc.OutputFile = histos_name
[docs]
@staticmethod
def edm_algorithms(redecay=False):
"""Sets up a special algorithm responsible for linking ``MCParticles``
and ``MCVertices`` to ``MCHits`` via ``LinkedParticle``. It is either:
- ``ReDecayMCTruthToEDM`` when in ReDeacay mode,
- ``MCTruthToEDM`` otherwise.
Args:
redecay (bool, optional): Using Redecay or not. Defaults to False.
Returns:
list: list of needed EDM algorithms
"""
from Configurables import (
CheckMCStructure,
MCTruthMonitor,
MCTruthToEDM,
ReDecayMCTruthToEDM,
)
conv = MCTruthToEDM
if redecay:
conv = ReDecayMCTruthToEDM
return [
conv(),
CheckMCStructure(),
MCTruthMonitor("MainMCTruthMonitor", HistoProduce=True),
]
[docs]
def _get_output_name(self):
"""Build a name for the output file, based on input options.
Combines DatasetName, EventType, Number of events and Date
"""
output_name = self.getProp("DatasetName")
if self.getProp("DatasetNameForced"):
return output_name
if not output_name:
output_name = "Gaussino"
evt_type = GaussinoGeneration.eventType()
if evt_type:
output_name += "-" + evt_type
if self.getProp("EvtMax") > 0:
output_name += f"-{self.getProp('EvtMax')}ev"
return f"{output_name}-{time.strftime('%Y%m%d')}"
[docs]
def _set_ML_options(self):
"""Sets up the ML options."""
opts = self.getProp("MLOptions")
if not opts:
return
backend = opts.get("Backend")
if not backend:
raise ValueError("Backend must be provided in MLOptions.")
model_path = opts.get("ModelPath")
if not model_path:
raise ValueError("ModelPath must be provided in MLOptions.")
model_name = opts.get("ModelName")
if not model_name:
raise ValueError("ModelName must be provided in MLOptions.")
if backend not in ["torch", "onnx"]:
raise ValueError(f"Unknown backend '{backend}' in MLOptions.")
svc_name = None
if backend == "torch":
if not model_path.endswith(".pt"):
raise ValueError(
f"File '{model_path}' does not end with '.pt' in MLOptions."
)
svc_name = "Gsino__ML__Torch__ModelServerSvc"
elif backend == "onnx":
if not model_path.endswith(".onnx"):
raise ValueError(
f"File '{model_path}' does not end with '.onnx' in MLOptions."
)
svc_name = "Gsino__ML__ONNX__ModelServerSvc"
else:
raise ValueError(f"Unknown backend '{backend}' in MLOptions.")
import Configurables
from Configurables import ApplicationMgr
auto_input_types = opts.get("AutoInputTypes", True)
auto_output_types = opts.get("AutoOutputTypes", True)
interop_threads = opts.get("InterOpThreads", self.getProp("ThreadPoolSize"))
intraop_threads = opts.get("IntraOpThreads", 1)
svc = getattr(Configurables, svc_name)
# FIXME: for URLs, download the model to a temporary directory,
# and then use that path (THIS IS JUST A TEMPORARY SOLUTION)
if model_path.startswith("http"):
log.warning(
f"Downloading the model from '{model_path}' to a temporary directory. "
"This is just a temporary solution. To be removed in the future."
)
root_dir = tempfile.gettempdir()
model_file_name = model_path.split("/")[-1]
model_dir = os.path.join(root_dir, "GaussinoModels", model_name)
os.makedirs(model_dir, exist_ok=True)
new_model_path = os.path.join(model_dir, model_file_name)
if os.path.isfile(new_model_path):
log.info(
f"Model '{model_file_name}' already exists in '{model_dir}'."
"Skipping download."
)
else:
log.info(f"Downloading model '{model_file_name}' to '{model_dir}'.")
urllib.request.urlretrieve(model_path, new_model_path)
model_path = new_model_path
ApplicationMgr().ExtSvc.append(
svc(
ModelPath=model_path,
ModelName=model_name,
AutoInputTypes=auto_input_types,
AutoOutputTypes=auto_output_types,
IntraOpThreads=intraop_threads,
InterOpThreads=interop_threads,
)
)