diff --git a/Derviation/DerivationAlgs/CMakeLists.txt b/Derviation/DerivationAlgs/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..d5ef7b4b3e64410b5db7c39a08bbd16acec22562 --- /dev/null +++ b/Derviation/DerivationAlgs/CMakeLists.txt @@ -0,0 +1,15 @@ +################################################################################ +# Package: DerivationAlgs +################################################################################ + +# Declare the package name: +atlas_subdir( DerivationAlgs ) + +atlas_add_component( DerivationAlgs + src/*.cxx src/*.h + src/components/*.cxx + LINK_LIBRARIES ${ROOT_LIBRARIES} AthenaBaseComps GaudiKernel StoreGateLib DerivationToolsLib) + +atlas_install_python_modules( python/*.py ) + + diff --git a/Derviation/DerivationAlgs/python/DerivationAlgsConfig.py b/Derviation/DerivationAlgs/python/DerivationAlgsConfig.py new file mode 100644 index 0000000000000000000000000000000000000000..70164d27e32d613ccf3c86c54d638de966fce14a --- /dev/null +++ b/Derviation/DerivationAlgs/python/DerivationAlgsConfig.py @@ -0,0 +1,70 @@ +from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator +from AthenaConfiguration.ComponentFactory import CompFactory +from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg + +def DerivationAlgCfg(flags, stream, tools, **kwargs): + + acc = ComponentAccumulator() + + kwargs.setdefault("Tools", tools) + acc.addEventAlgo(CompFactory.Derive(stream + "_DerivationAlg", **kwargs)) + + return acc + +def DerivationOutputCfg(flags, stream, accept, items = [], extra_items = [], exclude_items = [], **kwargs): + + acc = ComponentAccumulator() + + if not items: + items = [ "xAOD::EventInfo#*" + , "xAOD::EventAuxInfo#*" + , "xAOD::FaserTriggerData#*" + , "xAOD::FaserTriggerDataAux#*" + , "FaserSiHitCollection#*" # Strip hits, do we want this? + , "FaserSCT_RDO_Container#*" + , "xAOD::WaveformHitContainer#*" + , "xAOD::WaveformHitAuxContainer#*" + , "xAOD::WaveformClock#*" + , "xAOD::WaveformClockAuxInfo#*" + ] + + if not items and extra_items: + items.append(extra_items) + + exclude_list = [] + if exclude_items: + for ex in exclude_items: + if ex in items: + exclude_list.append(ex) + + if ex.endswith("*"): + for it in items: + if it.startswith(ex.rstrip("*")): + exclude_list.append(it) + + items = list(set(items) - set(exclude_list)) + + + #flags.unlock() + #flags.addFlag(f"Output.AOD{stream}FileName", f"my.{stream}.xAOD.root") + #flags.lock() + + acc.merge(OutputStreamCfg(flags, stream, items)) + acc.getEventAlgo(f"OutputStream{stream}").AcceptAlgs = [accept] + ##acc.getEventAlgo(f"OutputStream{stream}").TakeItemsFromInput = True # crashes + + return acc + +def FullyConfiguredStream(flags, stream, tools, items = [], extra_items = [], **kwargs): + # TODO: + # - get items from input + why crash + + acc = ComponentAccumulator() + + acc.merge(DerivationAlgCfg(flags, stream, tools, **kwargs)) + acc.merge(DerivationOutputCfg(flags, stream, stream + "_DerivationAlg", items, extra_items)) + + return acc + + + diff --git a/Derviation/DerivationAlgs/share/runDerive.py b/Derviation/DerivationAlgs/share/runDerive.py new file mode 100644 index 0000000000000000000000000000000000000000..b8b22c187c15a39993a68aa722e5d6bae3362f7b --- /dev/null +++ b/Derviation/DerivationAlgs/share/runDerive.py @@ -0,0 +1,147 @@ + +from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator +from AthenaConfiguration.ComponentFactory import CompFactory +from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg + + +def DerivationAlgCfg(flags, name, frac, **kwargs): + + acc = ComponentAccumulator() + + tool = CompFactory.ExampleDerivationTool(name + "_TestTool", SaveFraction = frac) + print ("ZEBRA", tool.SaveFraction) + + kwargs.setdefault("Tools", [tool]) + acc.addEventAlgo(CompFactory.Derive(name, **kwargs)) + + return acc + +def DerivationAlgCfg2(flags, name, **kwargs): + + acc = ComponentAccumulator() + + tool = CompFactory.TriggerStreamTool(name + "_TriggerSteamTool") + kwargs.setdefault("Tools", [tool]) + acc.addEventAlgo(CompFactory.Derive(name, **kwargs)) + + return acc + + +if __name__ == "__main__": + + import sys + from AthenaCommon.Logging import log, logging + from AthenaCommon.Constants import DEBUG, VERBOSE, INFO + from AthenaCommon.Configurable import Configurable + from CalypsoConfiguration.AllConfigFlags import ConfigFlags + from AthenaConfiguration.TestDefaults import defaultTestFiles + from CalypsoConfiguration.MainServicesConfig import MainServicesCfg + from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg + from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg + + # Set up logging and new style config + log.setLevel(DEBUG) + Configurable.configurableRun3Behavior = True + + ConfigFlags.Input.Files = [ + "/eos/experiment/faser/rec/2022/p0008/007984/Faser-Physics-007984-00000-p0008-xAOD.root" + #"/bundle/data/FASER/Ti12data/filter/r0008/007983/Faser-Physics-007983-TrigMask08-r0008-xAOD.root" + ] + + ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02" # Always needed; must match FaserVersionS + ConfigFlags.IOVDb.DatabaseInstance = "OFLP200" # Use MC conditions for now + ConfigFlags.Input.ProjectName = "data21" # Needed to bypass autoconfig + ConfigFlags.Input.isMC = False # Needed to bypass autoconfig + ConfigFlags.GeoModel.FaserVersion = "FASERNU-03" # FASER geometry + ConfigFlags.Common.isOnline = False + ConfigFlags.GeoModel.Align.Dynamic = False + ConfigFlags.Beam.NumberOfCollisions = 0. + + ConfigFlags.Detector.GeometryFaserSCT = True + + ConfigFlags.addFlag("Output.AODSTREAM1FileName", "my.STREAM1.xAOD.root") + ConfigFlags.addFlag("Output.AODSTREAM2FileName", "my.STREAM2.xAOD.root") + ConfigFlags.addFlag("Output.AODSTREAM3FileName", "my.STREAM3.xAOD.root") + #ConfigFlags.Output.STREAM1FileName = fileName + + ConfigFlags.lock() + + # Core components + cfg = MainServicesCfg(ConfigFlags) + cfg.merge(PoolReadCfg(ConfigFlags)) + cfg.merge(PoolWriteCfg(ConfigFlags)) + + # Derivation alg + cfg.merge(DerivationAlgCfg(ConfigFlags, "DerivationAlg1", 10)) + cfg.merge(DerivationAlgCfg(ConfigFlags, "DerivationAlg2", 90)) + cfg.merge(DerivationAlgCfg2(ConfigFlags, "DerivationAlg3")) + + # Writing + from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg + streamName1 = "AODSTREAM1" # Needs to have AOD in name + itemList1 = [ "xAOD::EventInfo#*" + , "xAOD::EventAuxInfo#*" + , "xAOD::FaserTriggerData#*" + , "xAOD::FaserTriggerDataAux#*" + , "FaserSiHitCollection#*" # Strip hits, do we want this? + , "FaserSCT_RDO_Container#*" + , "xAOD::WaveformHitContainer#*" + , "xAOD::WaveformHitAuxContainer#*" + , "xAOD::WaveformClock#*" + , "xAOD::WaveformClockAuxInfo#*" +# , "FaserSCT_SpacePointContainer#*" # Crashes +# , "Tracker::FaserSCT_ClusterContainer#*" +# , "TrackCollection#*" + ] + + cfg.merge(OutputStreamCfg(ConfigFlags, streamName1, itemList1)) #, disableEventTag = True)) + cfg.getEventAlgo("OutputStreamAODSTREAM1").AcceptAlgs = ["DerivationAlg1"] + #cfg.getEventAlgo("OutputStreamAODSTREAM1").TakeItemsFromInput = True + + + # Writing + from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg + streamName2 = "AODSTREAM2" # Needs to have AOD in name + itemList2 = [ "xAOD::EventInfo#*" + , "xAOD::EventAuxInfo#*" + , "xAOD::FaserTriggerData#*" + , "xAOD::FaserTriggerDataAux#*" + ] + cfg.merge(OutputStreamCfg(ConfigFlags, streamName2, itemList2)) #, disableEventTag = True)) + cfg.getEventAlgo("OutputStreamAODSTREAM2").AcceptAlgs = ["DerivationAlg2"] + + # Writing + from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg + streamName3 = "AODSTREAM3" # Needs to have AOD in name + itemList3 = [ "xAOD::EventInfo#*" + , "xAOD::EventAuxInfo#*" + , "xAOD::FaserTriggerData#*" + , "xAOD::FaserTriggerDataAux#*" + ] + cfg.merge(OutputStreamCfg(ConfigFlags, streamName3, itemList3)) #, disableEventTag = True)) + cfg.getEventAlgo("OutputStreamAODSTREAM3").AcceptAlgs = ["DerivationAlg3"] + + + +# from OutputStreamAthenaPool.MultipleStreamManager import MSMgr +# streamName = "STREAM1" +# fileName = "streaming.STREAM1.root" +# testStream = MSMgr.NewPoolRootStream(streamName, fileName) +# testStream.AcceptAlgs(["DerivationAlg1"]) +# cfg.addEventAlgo(testStream) + +# # Hack to avoid problem with our use of MC databases when isMC = False +# replicaSvc = acc.getService("DBReplicaSvc") +# replicaSvc.COOLSQLiteVetoPattern = "" +# replicaSvc.UseCOOLSQLite = True +# replicaSvc.UseCOOLFrontier = False +# replicaSvc.UseGeomSQLite = True + + # Execute and finish + cfg.printConfig() + + + sc = cfg.run(maxEvents=1000) + + # Success should be 0 + sys.exit(not sc.isSuccess()) diff --git a/Derviation/DerivationAlgs/share/run_streaming.py b/Derviation/DerivationAlgs/share/run_streaming.py new file mode 100644 index 0000000000000000000000000000000000000000..98b4d053fe93ecdfda282c31bff7645f0b5cbedf --- /dev/null +++ b/Derviation/DerivationAlgs/share/run_streaming.py @@ -0,0 +1,91 @@ +from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator +from AthenaConfiguration.ComponentFactory import CompFactory +from MagFieldServices.MagFieldServicesConfig import MagneticFieldSvcCfg + +if __name__ == "__main__": + + import sys + from AthenaCommon.Logging import log, logging + from AthenaCommon.Constants import DEBUG, VERBOSE, INFO + from AthenaCommon.Configurable import Configurable + from CalypsoConfiguration.AllConfigFlags import ConfigFlags + from AthenaConfiguration.TestDefaults import defaultTestFiles + from CalypsoConfiguration.MainServicesConfig import MainServicesCfg + from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg + from AthenaPoolCnvSvc.PoolWriteConfig import PoolWriteCfg + + # Set up logging and new style config + log.setLevel(DEBUG) + Configurable.configurableRun3Behavior = True + + ConfigFlags.Input.Files = [ + "/eos/experiment/faser/rec/2022/p0008/007984/Faser-Physics-007984-00000-p0008-xAOD.root" + ] + + ConfigFlags.IOVDb.GlobalTag = "OFLCOND-FASER-02" # Always needed; must match FaserVersionS + ConfigFlags.IOVDb.DatabaseInstance = "OFLP200" # Use MC conditions for now + ConfigFlags.Input.ProjectName = "data21" # Needed to bypass autoconfig + ConfigFlags.Input.isMC = False # Needed to bypass autoconfig + ConfigFlags.GeoModel.FaserVersion = "FASERNU-03" # FASER geometry + ConfigFlags.Common.isOnline = False + ConfigFlags.GeoModel.Align.Dynamic = False + ConfigFlags.Beam.NumberOfCollisions = 0. + + ConfigFlags.Detector.GeometryFaserSCT = True + + for stream in ["STREAM1", "STREAM2", "STREAM3"]: + ConfigFlags.addFlag(f"Output.AOD{stream}FileName", f"my.{stream}.xAOD.root") + + ConfigFlags.lock() + + # Core components + cfg = MainServicesCfg(ConfigFlags) + cfg.merge(PoolReadCfg(ConfigFlags)) + cfg.merge(PoolWriteCfg(ConfigFlags)) + + # Derivations + + from DerivationAlgs.DerivationAlgsConfig import FullyConfiguredStream + + name = "STREAM1" + cfg.merge(FullyConfiguredStream(ConfigFlags, stream = name, + tools = [CompFactory.ExampleDerivationTool(name + "_TestTool", SaveFraction = 10.)]) + ) + + + name = "STREAM2" + cfg.merge(FullyConfiguredStream(ConfigFlags, stream = name, + tools = [CompFactory.ExampleDerivationTool(name + "_TestTool", SaveFraction = 90.)], + items = [ "xAOD::EventInfo#*" + , "xAOD::EventAuxInfo#*" + , "xAOD::FaserTriggerData#*" + , "xAOD::FaserTriggerDataAux#*" + ]) + ) + + name = "STREAM3" + cfg.merge(FullyConfiguredStream(ConfigFlags, stream = name, + tools = [CompFactory.TriggerStreamTool(name + "_TriggerTool")], + items = [ "xAOD::EventInfo#*" + , "xAOD::EventAuxInfo#*" + , "xAOD::FaserTriggerData#*" + , "xAOD::FaserTriggerDataAux#*" + ]) + ) + + +# # Hack to avoid problem with our use of MC databases when isMC = False +# replicaSvc = acc.getService("DBReplicaSvc") +# replicaSvc.COOLSQLiteVetoPattern = "" +# replicaSvc.UseCOOLSQLite = True +# replicaSvc.UseCOOLFrontier = False +# replicaSvc.UseGeomSQLite = True + + # Execute and finish + cfg.printConfig() + + + sc = cfg.run(maxEvents=1000) + + # Success should be 0 + sys.exit(not sc.isSuccess()) diff --git a/Derviation/DerivationAlgs/src/Derive.cxx b/Derviation/DerivationAlgs/src/Derive.cxx new file mode 100644 index 0000000000000000000000000000000000000000..10af3de102e32a0cbc04d7f1f0c9dda165228a50 --- /dev/null +++ b/Derviation/DerivationAlgs/src/Derive.cxx @@ -0,0 +1,53 @@ +#include "Derive.h" + + +Derive::Derive(const std::string& name, + ISvcLocator* pSvcLocator) + : AthFilterAlgorithm(name, pSvcLocator) { + + declareProperty("Tools", m_tools); + +} + +StatusCode +Derive::initialize() { + ATH_MSG_INFO(name() << "::initalize()" ); + + ATH_CHECK( m_tools.retrieve() ); + return StatusCode::SUCCESS; +} + +StatusCode +Derive::finalize() { + ATH_MSG_INFO(name() << "::finalize()"); + ATH_MSG_INFO("Derivation" << name() << " accepted " << m_passed << " out of " << m_events << " events"); + + return StatusCode::SUCCESS; +} + +StatusCode +Derive::execute() { + ATH_MSG_DEBUG("Executing ... "); + + m_events++; + + bool acceptEvent(true); + + for (auto& tool : m_tools) { + + // Skimming - remove events + if (!tool->passed()) acceptEvent = false; + + // Thinning - remove info from event + ATH_CHECK(tool->removeBranch()); + + // Augmenting - add info to an event + ATH_CHECK(tool->addBranch()); + + } + + setFilterPassed(acceptEvent); + if (acceptEvent) m_passed++; + + return StatusCode::SUCCESS; +} diff --git a/Derviation/DerivationAlgs/src/Derive.h b/Derviation/DerivationAlgs/src/Derive.h new file mode 100644 index 0000000000000000000000000000000000000000..43d0f59d5e29e30d722a867cb408667bb67d587d --- /dev/null +++ b/Derviation/DerivationAlgs/src/Derive.h @@ -0,0 +1,64 @@ +#ifndef DERIVATIONALGS_DERIVE_H +#define DERIVATIONALGS_DERIVE_H + +// Base class +#include "AthenaBaseComps/AthFilterAlgorithm.h" + +// FASER +#include "DerivationTools/IDerivationTool.h" + +// Gaudi +#include "GaudiKernel/ServiceHandle.h" +#include "GaudiKernel/ToolHandle.h" +#include "GaudiKernel/IAlgTool.h" + +// Athena +#include "xAODEventInfo/EventInfo.h" +#include "StoreGate/ReadHandleKey.h" + +// ROOT +#include <TRandom3.h> + +// STL + +class Derive : public AthFilterAlgorithm { + +public: + // Constructor + Derive(const std::string& name, ISvcLocator* pSvcLocator); + virtual ~Derive() = default; + + /** @name Usual algorithm methods */ + //@{ + virtual StatusCode initialize() override; + virtual StatusCode execute() override; + virtual StatusCode finalize() override; + //@} + + + private: + + /** @name Disallow default instantiation, copy, assignment */ + //@{ + Derive() = delete; + Derive(const Derive&) = delete; + Derive &operator=(const Derive&) = delete; + //@} + + /// + + /** @name Steerable tools */ + //@ + ToolHandleArray<IDerivationTool> m_tools; + //@} + + /** Number of events processed */ + int m_events {0}; + + /** Number of events selected */ + int m_passed {0}; + +}; + + +#endif // DERIVATIONALGS_DERIVE_H diff --git a/Derviation/DerivationAlgs/src/components/DerivationAlgs_entries.cxx b/Derviation/DerivationAlgs/src/components/DerivationAlgs_entries.cxx new file mode 100644 index 0000000000000000000000000000000000000000..c46a518fefa207cc65c90781eb1660a9438c8e32 --- /dev/null +++ b/Derviation/DerivationAlgs/src/components/DerivationAlgs_entries.cxx @@ -0,0 +1,3 @@ +#include "../Derive.h" + +DECLARE_COMPONENT( Derive ) diff --git a/Derviation/DerivationTools/CMakeLists.txt b/Derviation/DerivationTools/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..c167cb1598233f25fb51a5bcbea28c6f17a22c40 --- /dev/null +++ b/Derviation/DerivationTools/CMakeLists.txt @@ -0,0 +1,24 @@ +################################################################################ +# Package: DerivationTools +################################################################################ + +# Declare the package name: +atlas_subdir( DerivationTools ) + +# External dependencies: +find_package( ROOT ) + +# Component(s) in the package: +atlas_add_library( DerivationToolsLib + DerivationTools/*.h src/*.cxx src/*.h + PUBLIC_HEADERS DerivationTools + PRIVATE_INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} + LINK_LIBRARIES AthenaBaseComps AthenaKernel xAODFaserTrigger + PRIVATE_LINK_LIBRARIES ${ROOT_LIBRARIES} + ) + +atlas_add_component( DerivationTools + src/components/*.cxx + INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} + LINK_LIBRARIES ${ROOT_LIBRARIES} AthenaBaseComps GaudiKernel xAODEventInfo xAODFaserTrigger DerivationToolsLib) + diff --git a/Derviation/DerivationTools/DerivationTools/IDerivationTool.h b/Derviation/DerivationTools/DerivationTools/IDerivationTool.h new file mode 100644 index 0000000000000000000000000000000000000000..29970fe09230f02c11aea8b5c8663540bc8312fc --- /dev/null +++ b/Derviation/DerivationTools/DerivationTools/IDerivationTool.h @@ -0,0 +1,48 @@ +/* + Copyright (C) 2021 CERN for the benefit of the FASER collaboration +*/ + +/** + * @file IDerivationTool.h + * Header file for the IDerivationTool class + * @author Carl Gwilliam, 2021 + */ + + +#ifndef DERIVATIONTOOLS_IDERIVATIONTOOL_H +#define DERIVATIONTOOLS_IDERIVATIONTOOL_H + +// Gaudi +#include "GaudiKernel/IAlgTool.h" +#include "GaudiKernel/ToolHandle.h" + + +///Interface for derivation tools +class IDerivationTool : virtual public IAlgTool +{ +public: + + // InterfaceID + DeclareInterfaceID(IDerivationTool, 1, 0); + + virtual ~IDerivationTool() = default; + + // Apply skimming + virtual bool passed() = 0; + + /// Apply thinning + virtual StatusCode removeBranch() = 0; + + /// Apply augmentation + virtual StatusCode addBranch() = 0; + + +private: + // None + +}; + + + + +#endif //DERIVATIONTOOLS_IDERIVATIONTOOL_H diff --git a/Derviation/DerivationTools/src/ExampleDerivationTool.cxx b/Derviation/DerivationTools/src/ExampleDerivationTool.cxx new file mode 100644 index 0000000000000000000000000000000000000000..4c8f212d3622a2b843affe6a6d9cb70d720859d1 --- /dev/null +++ b/Derviation/DerivationTools/src/ExampleDerivationTool.cxx @@ -0,0 +1,46 @@ +/* + Copyright (C) 2021 CERN for the benefit of the FASER collaboration +*/ + +/** + * @file ExamplederivationTool.cxx + * Implementation file for the ExamplederivationTool class + * @ author C. Gwilliam, 2021 + **/ + +#include "ExampleDerivationTool.h" + +// Constructor +ExampleDerivationTool::ExampleDerivationTool(const std::string& type, const std::string& name, const IInterface* parent) : + base_class(type, name, parent) +{ +} + +// Initialization +StatusCode +ExampleDerivationTool::initialize() { + ATH_MSG_INFO( name() << "::initalize()" ); + + return StatusCode::SUCCESS; +} + +bool +ExampleDerivationTool::passed(){ + + bool accept(false); + + m_events++; + + float frac = ((float)(m_passed+1))/(float)m_events * 100.0; + + if (frac > m_fraction) { + ATH_MSG_INFO("Filter failed"); + accept = false; + } else { + ATH_MSG_INFO("Filter passed " << m_passed << " " << m_events << " " << frac << " " << m_fraction); + accept = true; + m_passed++; + } + + return accept; +} diff --git a/Derviation/DerivationTools/src/ExampleDerivationTool.h b/Derviation/DerivationTools/src/ExampleDerivationTool.h new file mode 100644 index 0000000000000000000000000000000000000000..06933482b0878a6fa346f80af4425d68bed46a9a --- /dev/null +++ b/Derviation/DerivationTools/src/ExampleDerivationTool.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2021 CERN for the benefit of the FASER collaboration +*/ + +/** @file TriggerStreamTool.h + * Header file for TriggerStreamTool.h + * + */ +#ifndef DERIVATIONTOOLS_EXAMPLEDERIVATIONTOOL_H +#define DERIVATIONTOOLS_EXAMPLEDERIVATIONTOOL_H + +//Athena +#include "AthenaBaseComps/AthAlgTool.h" +#include "DerivationTools/IDerivationTool.h" + +//Gaudi +#include "GaudiKernel/ToolHandle.h" + +//STL + +class ExampleDerivationTool: public extends<AthAlgTool, IDerivationTool> { + public: + + /// Normal constructor for an AlgTool; 'properties' are also declared here + ExampleDerivationTool(const std::string& type, + const std::string& name, const IInterface* parent); + + /// Retrieve the necessary services in initialize + StatusCode initialize(); + + // Apply skimming + bool passed(); + + /// Apply thinning + StatusCode removeBranch() {return StatusCode::SUCCESS;} + + /// Apply augmentation + StatusCode addBranch() {return StatusCode::SUCCESS;} + + private: + + Gaudi::Property<float> m_fraction {this, "SaveFraction", 100, "Fraction of events to save"}; + + /** Number of events processed */ + int m_events {0}; + + /** Number of events selected */ + int m_passed {0}; + +}; + +#endif // WAVEDIGITOOLS_WAVEFORMDIGITISATIONTOOL_H diff --git a/Derviation/DerivationTools/src/TriggerStreamTool.cxx b/Derviation/DerivationTools/src/TriggerStreamTool.cxx new file mode 100644 index 0000000000000000000000000000000000000000..dc37364d574f3d90a875da5cb8f5ee31b8188338 --- /dev/null +++ b/Derviation/DerivationTools/src/TriggerStreamTool.cxx @@ -0,0 +1,35 @@ +/* + Copyright (C) 2021 CERN for the benefit of the FASER collaboration +*/ + +/** + * @file TriggerStreamTool.cxx + * Implementation file for the TriggerStreamTool class + * @ author C. Gwilliam, 2021 + **/ + +#include "TriggerStreamTool.h" + +// Constructor +TriggerStreamTool::TriggerStreamTool(const std::string& type, const std::string& name, const IInterface* parent) : + base_class(type, name, parent) +{ +} + +// Initialization +StatusCode +TriggerStreamTool::initialize() { + ATH_MSG_INFO( name() << "::initalize()" ); + + ATH_CHECK( m_triggerDataKey.initialize() ); + + return StatusCode::SUCCESS; +} + +bool +TriggerStreamTool::passed() { + + SG::ReadHandle<xAOD::FaserTriggerData> triggerData(m_triggerDataKey); + + return triggerData->tap() & 0x8; +} diff --git a/Derviation/DerivationTools/src/TriggerStreamTool.h b/Derviation/DerivationTools/src/TriggerStreamTool.h new file mode 100644 index 0000000000000000000000000000000000000000..7ad45d1cc3748a8ce06c3aae50522ac57cffd02c --- /dev/null +++ b/Derviation/DerivationTools/src/TriggerStreamTool.h @@ -0,0 +1,53 @@ +/* + Copyright (C) 2021 CERN for the benefit of the FASER collaboration +*/ + +/** @file TriggerStreamTool.h + * Header file for TriggerStreamTool.h + * + */ +#ifndef DERIVATIONTOOLS_TRIGGERSTREAMTOOL_H +#define DERIVATIONTOOLS_TRIGGERSTREAMTOOL_H + + +// FASER +#include "StoreGate/ReadHandleKey.h" +#include "xAODFaserTrigger/FaserTriggerData.h" + +//Athena +#include "AthenaBaseComps/AthAlgTool.h" +#include "DerivationTools/IDerivationTool.h" + +//Gaudi +#include "GaudiKernel/ToolHandle.h" + +//STL + +class TriggerStreamTool: public extends<AthAlgTool, IDerivationTool> { + public: + + /// Normal constructor for an AlgTool; 'properties' are also declared here + TriggerStreamTool(const std::string& type, + const std::string& name, const IInterface* parent); + + /// Retrieve the necessary services in initialize + StatusCode initialize(); + + // Apply skimming + bool passed(); + + /// Apply thinning + StatusCode removeBranch() {return StatusCode::SUCCESS;} + + /// Apply augmentation + StatusCode addBranch() {return StatusCode::SUCCESS;} + + private: + + /// StoreGate key + SG::ReadHandleKey<xAOD::FaserTriggerData> m_triggerDataKey + { this, "FaserTriggerDataKey", "FaserTriggerData", "ReadHandleKey for xAOD::FaserTriggerData"}; + +}; + +#endif // WAVEDIGITOOLS_WAVEFORMDIGITISATIONTOOL_H diff --git a/Derviation/DerivationTools/src/components/DerivationTools_entries.cxx b/Derviation/DerivationTools/src/components/DerivationTools_entries.cxx new file mode 100644 index 0000000000000000000000000000000000000000..91e58e594e7864bd61ddf7d196f1625d361a4c8b --- /dev/null +++ b/Derviation/DerivationTools/src/components/DerivationTools_entries.cxx @@ -0,0 +1,5 @@ +#include "../TriggerStreamTool.h" +#include "../ExampleDerivationTool.h" + +DECLARE_COMPONENT( TriggerStreamTool ) +DECLARE_COMPONENT( ExampleDerivationTool ) diff --git a/Generators/DIFGenerator/python/DIFSampler.py b/Generators/DIFGenerator/python/DIFSampler.py index 8ec22459aba93a430093c4bd7b2a3f13e2d0f206..5badf599b5add0877a0202f453c22d54fd4f06e4 100644 --- a/Generators/DIFGenerator/python/DIFSampler.py +++ b/Generators/DIFGenerator/python/DIFSampler.py @@ -122,6 +122,11 @@ class DIFSampler(PG.ParticleSampler): self.daughter1.mom.Boost(Bx,By,Bz) self.daughter2.mom.Boost(Bx,By,Bz) + def report(self): + "Information to report in finalize" + self.mother_sampler.report() + return + def shoot(self): "Mother particle decays into two daughter particles" ## Shoot daughter 1 in a random direction in CM frame diff --git a/Generators/ForeseeGenerator/python/ForeseeSampler.py b/Generators/ForeseeGenerator/python/ForeseeSampler.py index 2f7d7241e560e552b8df3bcd9cb9b62e321f17ff..470e8b7396987e49ef4e038c7645b6b9d0d8d070 100644 --- a/Generators/ForeseeGenerator/python/ForeseeSampler.py +++ b/Generators/ForeseeGenerator/python/ForeseeSampler.py @@ -22,6 +22,7 @@ class ForeseeNumpySampler(PG.ParticleSampler): self.n = 1 self.distance = 480 # m self.mass_override = False + self.nshot = 0 if randomSeed is not None: print(f"Setting seed to {randomSeed}") @@ -29,8 +30,6 @@ class ForeseeNumpySampler(PG.ParticleSampler): else: self.rng = np.random.default_rng() - self.xs = 0 - self.read() def read(self): @@ -39,28 +38,40 @@ class ForeseeNumpySampler(PG.ParticleSampler): if self.path.endswith(".npy"): filename = self.path elif self.coupling is None: - filename = f"{self.path}/files/models/{self.modelname}/events/events_{self.energy}TeV_m{self.mass}GeV_to_{self.daughter1_pid}_{self.daughter2_pid}.npy" + filename = f"{self.path}/{self.modelname}/events_{self.energy}TeV_m{self.mass}GeV_to_{self.daughter1_pid}_{self.daughter2_pid}.npy" else: - filename = f"{self.path}/files/models/{self.modelname}/events/events_{self.energy}TeV_m{self.mass}GeV_c{self.coupling}to_{self.daughter1_pid}_{self.daughter2_pid}.npy" + filename = f"{self.path}/{self.modelname}/events_{self.energy}TeV_m{self.mass}GeV_c{self.coupling}to_{self.daughter1_pid}_{self.daughter2_pid}.npy" print(f"Reading data from file: {filename}") self.data = np.load(filename) # Create probablity for each mode as weight / sum(weights) self.prob = self.data[2]/np.sum(self.data[2]) - + + # Sum the weights + self.sum_of_weights = sum(self.data[2]) + return # def mass(self): # "Mass converted from GeV to MeV" # return self._mass * 1000 + + def report(self): + print(f"[ForeseeSampler] cross-section = {self.sum_of_weights / self.nshot} pb-1") + return + + def shoot(self): "Choose a random item from the data, based on the probability" + self.nshot += 1 + energy, theta, weight = self.rng.choice(self.data, axis = 1, p = self.prob) - self.xs += weight + # TODO: this needs dividing by the number of events when storing / printing + # self.xs += weight # Convert mass to MeV mass = self.mass * 1000 @@ -271,7 +282,7 @@ class ForeseeSampler(PG.MomSampler): prob = self.data[2]/np.sum(self.data[2]) # Choose a random item from the data, base on the probability - # TODO: what about reuse of events? + # TODO: what about reuse of events? How account for this in the cross-section theta_mother, e_mother, w = self.rng.choice(self.data, axis = 1, p = prob) self.xs += w diff --git a/Generators/ForeseeGenerator/python/Validate.py b/Generators/ForeseeGenerator/python/Validate.py index 8170bbd33b732f1972d3fbdedea626c61948d95d..f26040962b12525df5eb9f434e7bf6bfeb071dda 100644 --- a/Generators/ForeseeGenerator/python/Validate.py +++ b/Generators/ForeseeGenerator/python/Validate.py @@ -155,6 +155,7 @@ class EvgenValidation(EvgenAnalysisAlg): def execute(self): evt = self.events()[0] self.weight = evt.weights()[0] if evt.weights() else 1 + print(self.weight) # Loop over all particles in events momenta = [] @@ -163,8 +164,9 @@ class EvgenValidation(EvgenAnalysisAlg): llp_vtx = None for i, p in enumerate(evt.particles): - print("--- ", i) - p.print() + + if p.pdg_id() != -13: continue + self.fillDaughter(p) if self.mother_stored: @@ -185,14 +187,12 @@ class EvgenValidation(EvgenAnalysisAlg): if p.production_vertex(): vertices.append(p.production_vertex().point3d()) - if p.production_vertex(): - vertices.append(p.production_vertex().point3d()) - # Fill daughter plots for i in range(self.ndaughters): if i >= len(momenta): continue self.fillKin(f"d{i}", momenta[i]) + # Fill mother plots self.fillKin("M", mother, mass = True) diff --git a/Generators/ForeseeGenerator/share/generate_forsee_events.py b/Generators/ForeseeGenerator/share/generate_forsee_events.py index f02f4311f69d07a29a2de4bf26ac2ee93ea13206..355d52fed856dfe0df15083fe0d4f1c929124150 100644 --- a/Generators/ForeseeGenerator/share/generate_forsee_events.py +++ b/Generators/ForeseeGenerator/share/generate_forsee_events.py @@ -9,7 +9,7 @@ class ForeseeGenerator(object): Generate LLP particles within FASER acceptance from FORESEE """ - def __init__(self, modelname, energy, mass, couplings, daughter1_pid, daughter2_pid, outdir = None, path = '.', randomSeed = 12345): + def __init__(self, modelname, energy, mass, couplings, daughter1_pid, daughter2_pid, outdir = None, path = '.', randomSeed = 12345, t0 = 0, notime = False, suffix = ""): self.modelname = modelname self.energy = energy @@ -19,8 +19,12 @@ class ForeseeGenerator(object): self.daughter2_pid = daughter2_pid self.outdir = outdir self.path = path - self.version = 2 # Forsee "version": 2 is in testing + self.version = 3 # Forsee "version" self.seed = randomSeed + self.t0 = t0 + self.notime = notime + self.suffix = f"_{suffix}" if suffix else "" + self.nbinsample = 1 # Set decay mode ... @@ -42,8 +46,9 @@ class ForeseeGenerator(object): else: self.foresee = Foresee(path = self.path) + # Generate 6 cm high to account for translation from ATLAS to FASER coord. system # TODO: relax this a bit as daughters may enter even if mother doesn't - self.foresee.set_detector(selection="np.sqrt(x.x**2 + x.y**2)< 0.1", + self.foresee.set_detector(selection="np.sqrt(x.x**2 + (x.y)**2)< 0.1", channels=[self.mode], distance=480, length=1.5 , luminosity=1/1000.) # 1 pb-1 @@ -64,6 +69,8 @@ class ForeseeGenerator(object): def darkphoton(self): + self.nbinsample = 100 # resample bins to help with asymmetric detector + # Production modes self.model.add_production_2bodydecay( pid0 = "111", @@ -152,13 +159,15 @@ class ForeseeGenerator(object): def alp_W(self): + self.nbinsample = 100 # resample bins to help with smoothness + self.model.add_production_2bodydecay( pid0 = "5", pid1 = "321", br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))", generator = "Pythia8", energy = self.energy, - nsample = 20, # Vary over phi and theta + nsample = 500, # Vary over phi and theta -> increase number to improve smoothness for B ) self.model.add_production_2bodydecay( @@ -167,7 +176,7 @@ class ForeseeGenerator(object): br = "2.2e4 * coupling**2 * np.sqrt((1-(mass+0.495)**2/5.279**2)*(1-(mass-0.495)**2/5.279**2))", generator = "Pythia8", energy = self.energy, - nsample = 20, + nsample = 500, ) self.model.add_production_2bodydecay( @@ -176,7 +185,7 @@ class ForeseeGenerator(object): br = "4.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))", generator = "EPOSLHC", energy = self.energy, - nsample = 10, + nsample = 50, ) self.model.add_production_2bodydecay( @@ -185,9 +194,19 @@ class ForeseeGenerator(object): br = "10.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))", generator = "EPOSLHC", energy = self.energy, - nsample = 10, + nsample = 50, ) + self.model.add_production_2bodydecay( + pid0 = "-321", + pid1 = "211", + br = "10.5 * coupling**2 * np.sqrt((1-(mass+0.135)**2/0.495**2)*(1-(mass-0.135)**2/0.495**2))", + generator = "EPOSLHC", + energy = self.energy, + nsample = 50, + ) + + if self.version == 1: self.model.set_br_1d( modes = [self.mode], @@ -229,12 +248,24 @@ class ForeseeGenerator(object): # Get list of events within detector output = self.foresee.get_events(mass=self.mass, energy=self.energy, couplings=self.couplings) - coups, ctaus, nsigs, energies, weights, thetas = output - self.plot(flatten(thetas), flatten(energies), flatten(weights)) + if self.version >= 3: + coups, ctaus, nsigs, momenta, weights = output + fmomenta = flatten(momenta) + fweights = flatten(weights) + fenergies = [p.e for p in fmomenta] # Now in MeV ? + fthetas = [p.pt/p.pz for p in fmomenta] + + self.plot(fthetas, fenergies, fweights) + + # Return energy, theta and weights + return [fenergies, fthetas, fweights] + else: + coups, ctaus, nsigs, energies, weights, thetas = output + self.plot(flatten(thetas), flatten(energies), flatten(weights)) - # Return energy (converting to MeV), theta and weights - return [[e*1000 for e in flatten(energies)], flatten(thetas), flatten(weights)] + # Return energy (converting to MeV), theta and weights + return [[e*1000 for e in flatten(energies)], flatten(thetas), flatten(weights)] def plot(self, thetas, energies, weights): # Plot the results in Forsee format @@ -308,9 +339,9 @@ class ForeseeGenerator(object): elif not os.path.exists(self.outdir): os.mkdir(self.outdir) - filename = f"{self.outdir}/events_{self.energy}TeV_m{self.mass}GeV_c{self.couplings[0]}to_{self.daughter1_pid}_{self.daughter2_pid}.hepmc" + filename = f"{self.outdir}/events_{self.energy}TeV_m{self.mass}GeV_c{self.couplings[0]}to_{self.daughter1_pid}_{self.daughter2_pid}{self.suffix}.hepmc" - self.foresee.write_events(self.mass, self.couplings[0], self.energy, filename, nevents, zfront = -1.5, seed = self.seed, decaychannels = [self.mode]) + self.foresee.write_events(self.mass, self.couplings[0], self.energy, filename, nevents, zfront = -1.5, seed = self.seed, decaychannels = [self.mode], notime = self.notime, t0 = self.t0, nsample = self.nbinsample) cfgname = f"{self.foresee.dirpath}/Models/{self.modelname}/" + filename.replace(".hepmc", ".cfg") print(f"save config to file: {cfgname}") @@ -387,7 +418,10 @@ if __name__ == "__main__": parser.add_argument("--path", default = ".", help = "Path to foresee installation") parser.add_argument("--hepmc", action = "store_true", help = "Write HepMC events") parser.add_argument("--nevents", "-n", default = 10, type = int, help = "Number of HepMC events ") - parser.add_argument("--randomSeed", "-s", default = 1234, type = int, help = "Random seed for HepMC generation") + parser.add_argument("--randomSeed", "-s", default = 1234, type = int, help = "Random seed for HepMC generation") + parser.add_argument("--t0", "-t", default = 0, type = int, help = "Time offset for start of decay volume") + parser.add_argument("--notime", action = "store_true", help = "Set all vertex times to 0 rather than calculating from start of decay volume") + parser.add_argument("--suffix", default = "", help = "Filename suffix") args = parser.parse_args() add_to_python_path(f"{args.path}/src") @@ -405,7 +439,7 @@ if __name__ == "__main__": print(f" decay = {args.pid1} {args.pid2}") print(f" couplings = {couplings}") - f = ForeseeGenerator(args.model, args.Ecom, args.mass, couplings, args.pid1, args.pid2, outdir = args.outdir, path = args.path, randomSeed = args.randomSeed) + f = ForeseeGenerator(args.model, args.Ecom, args.mass, couplings, args.pid1, args.pid2, outdir = args.outdir, path = args.path, randomSeed = args.randomSeed, t0 = args.t0, notime = args.notime, suffix = args.suffix) if args.hepmc: f.write_hepmc(args.nevents) diff --git a/Generators/ForeseeGenerator/share/plot_validation.py b/Generators/ForeseeGenerator/share/plot_validation.py index 8429978bc6a091abd853456d1a10fa3d33ab200d..5bcc34d4faad754d7d0783ed91c2ad3653370867 100644 --- a/Generators/ForeseeGenerator/share/plot_validation.py +++ b/Generators/ForeseeGenerator/share/plot_validation.py @@ -88,7 +88,7 @@ if __name__ == "__main__": f = R.TFile.Open(args.file) for i in range(args.ndaughters): - config = [Hist(f"P_d{i}", logy = True, xtitle = "p^{0} [GeV]", ndiv = 5, r = 5), + config = [Hist(f"P_d{i}", logy = True, xtitle = "p^{0} [GeV]", ndiv = 5, r = 1), Hist(f"Theta_d{i}", xtitle = "#theta [rad]", ndiv = -4), Hist(f"Mass_d{i}", xtitle = "m^{0} [GeV]", xlo = 0, xhi = 0.1, ndiv = 4), Hist(f"Pt_d{i}", logy = True, xtitle = "p_{T}^{0} [GeV]", ndiv = 10, r = 5), @@ -98,15 +98,15 @@ if __name__ == "__main__": plotn(f, args, config, 3, 2, f"daug{i}") - config = [Hist("P_M", logy = True, xtitle = "p^{0} [GeV]", ndiv = 5, xlo = 0, xhi = 10000, r=10), - Hist("Theta_M", xtitle = "#theta [rad]", ndiv = -4, r = 10), - Hist("Mass_M", xtitle = "m^{0} [GeV]", xlo = 0, xhi = 1., ndiv = 5), - Hist("Pt_M", logy = True, xtitle = "p_{T}^{0} [GeV]", ndiv = 10, r = 50), - Hist("Phi_M", xtitle = "#phi [rad]", r = 2), - Hist("ThetaVsP_M", ytitle = "p^{0} [GeV]", xtitle = "#theta [rad]", logx = True, logy = True, d = "colz") - ] +# config = [Hist("P_M", logy = True, xtitle = "p^{0} [GeV]", ndiv = 5, xlo = 0, xhi = 10000, r=10), +# Hist("Theta_M", xtitle = "#theta [rad]", ndiv = -4, r = 10), +# Hist("Mass_M", xtitle = "m^{0} [GeV]", xlo = 0, xhi = 1., ndiv = 5), +# Hist("Pt_M", logy = True, xtitle = "p_{T}^{0} [GeV]", ndiv = 10, r = 50), +# Hist("Phi_M", xtitle = "#phi [rad]", r = 2), +# Hist("ThetaVsP_M", ytitle = "p^{0} [GeV]", xtitle = "#theta [rad]", logx = True, logy = True, d = "colz") +# ] - plotn(f, args, config, 3, 2, "mother") +# plotn(f, args, config, 3, 2, "mother") plotn(f, args, Hist("PIDs", xtitle="PDG Id"), 1, 1, "pid") @@ -118,14 +118,14 @@ if __name__ == "__main__": # # plotn(f, args, config, 2, 2, "twod") - config = [Hist("Vtx_X_LLP", xtitle = "x [mm]", r = 5), - Hist("Vtx_Y_LLP", xtitle = "y [mm]", r = 5), - Hist("Vtx_Z_LLP", xtitle = "z [mm]", r = 5, ndiv = 5), - Hist("Vtx_XY_LLP", xtitle = "x [mm]", ytitle = "y [mm]", d = "colz", r = (5,5)), - Hist("Vtx_R_LLP", xtitle = "r [mm]", r = 5, ndiv = 5) - ] - - plotn(f, args, config, 3, 2, "vtx_llp") +# config = [Hist("Vtx_X_LLP", xtitle = "x [mm]", r = 5), +# Hist("Vtx_Y_LLP", xtitle = "y [mm]", r = 5), +# Hist("Vtx_Z_LLP", xtitle = "z [mm]", r = 5, ndiv = 5), +# Hist("Vtx_XY_LLP", xtitle = "x [mm]", ytitle = "y [mm]", d = "colz", r = (5,5)), +# Hist("Vtx_R_LLP", xtitle = "r [mm]", r = 5, ndiv = 5) +# ] +# +# plotn(f, args, config, 3, 2, "vtx_llp") config = [Hist("Vtx_X_All", xtitle = "x [mm]", r = 5), diff --git a/Generators/ParticleGun/python/__init__.py b/Generators/ParticleGun/python/__init__.py index 4e88fc56c49679a210ad57b62055a6a7c0867ddd..f6179f0a3c017856e5d9c81dd084f67e24bf8faf 100644 --- a/Generators/ParticleGun/python/__init__.py +++ b/Generators/ParticleGun/python/__init__.py @@ -65,6 +65,19 @@ class ParticleGun(EvgenAlg): self.msg.error("ParticleGun: randomSeed property not set, and no %s random number service found", self.randomSvcName) return StatusCode.Failure + + def finalize(self): + """ + Report back information from the sampler to the log file if exists + """ + + for s in self.samplers: + try: + s.report() + except AttributeError: + pass + + return StatusCode.Success def fillEvent(self, evt): """ @@ -76,10 +89,27 @@ class ParticleGun(EvgenAlg): from AthenaPython.PyAthena import HepMC3 as HepMC except ImportError: from AthenaPython.PyAthena import HepMC as HepMC - evt.weights().push_back(1.0) ## Make and fill particles for s in self.samplers: + + # Add cross-section info if available + try: + xs, xserr = s.cross_section() + xsect = HepMC.GenCrossSection() + xsect.set_cross_section(xs) + xsect.set_cross_section_error(xserr) + evt.set_cross_section(xsect) + except AttributeError: + pass + + # Add weight info if available + try: + for k, v in s.weights().items(): + evt.weights()[k] = v + except AttributeError: + evt.weights().push_back(1.0) + particles = s.shoot() for p in particles: ## Debug printout of particle properties @@ -98,7 +128,10 @@ class ParticleGun(EvgenAlg): ## Make particle with status == 1 mom = HepMC.FourVector(p.mom.Px(), p.mom.Py(), p.mom.Pz(), p.mom.E()) gp = HepMC.GenParticle() - gp.set_status(1) + try: + gp.set_status(p.status) + except AttributeError: + gp.set_status(1) gp.set_pdg_id(p.pid) gp.set_momentum(mom) if p.mass is not None: diff --git a/Generators/ParticleGun/python/samplers.py b/Generators/ParticleGun/python/samplers.py index 90e9676a5ce20dc1b52e48c79f0b36a7da49f639..fe575cc2cc5d5d204e34a4fb64e473670a3161be 100644 --- a/Generators/ParticleGun/python/samplers.py +++ b/Generators/ParticleGun/python/samplers.py @@ -856,6 +856,7 @@ class SampledParticle(object): self.mom = mom self.pos = pos self.mass = None + self.status = 1 class ParticleSampler(Sampler): diff --git a/Simulation/G4Faser/G4FaserAlg/python/G4FaserAlgConfigNew.py b/Simulation/G4Faser/G4FaserAlg/python/G4FaserAlgConfigNew.py index e4b9721c03c8d697e101de48f9e279022051d7fb..7613d3e303f029578951dc0f68b2f7453199792f 100644 --- a/Simulation/G4Faser/G4FaserAlg/python/G4FaserAlgConfigNew.py +++ b/Simulation/G4Faser/G4FaserAlg/python/G4FaserAlgConfigNew.py @@ -9,7 +9,6 @@ from G4FaserServices.G4FaserUserActionConfigNew import UserActionSvcCfg from AthenaConfiguration.ComponentFactory import CompFactory from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg - def G4FaserAlgBasicCfg(ConfigFlags, name="G4FaserAlg", **kwargs): """Return ComponentAccumulator configured for Faser G4 simulation, without output""" # wihout output @@ -73,7 +72,7 @@ def G4FaserAlgBasicCfg(ConfigFlags, name="G4FaserAlg", **kwargs): #Write MetaData container # result.merge(writeSimulationParametersMetadata(ConfigFlags)) - + #User action services (Slow...) result.merge( UserActionSvcCfg(ConfigFlags) ) kwargs.setdefault("UserActionSvc", result.getService( "G4UA::UserActionSvc") )