Skip to content

Commit f86380c

Browse files
committed
Introducing simulation as a service
This commit brings the possibility to run "o2-sim" as service/daemon, in the sense that its lifetime extends that of a simple production of events. This has the advantage that we may initialize the simulator once for the simulation of many (different) batches of events. The main benefits are: a) Reduced initialization overhead for - transport of multiple timeframes - transport of multiple signal types (which can be combined in digitization) See example /run/SimExamples/SimAsService_basic b) Enabling efficient biasing with the stop-go mechanism. Use the service in multiple stages: - transport part of event - trigger on output - continue with rest of triggered events See example /run/SimExamples/SimAsService_biasing1 The new mechanism is used via: 1. Launch and init o2-sim in service mode: - `o2-sim --asservice -e TGeant4 ...` 2. A separate client (python) script that talks to the service (via ZMQ): - `o2-sim-client.py "-n 20 -g pythia8 -o foo` Some accompanying changes here are * restructuring of o2sim json setup * allowing to define hooks acting on generator trigger decisions * some restructuring of communication between actors (needs further improvement) * sim workers now have a notion of their ID Note that the feature can be used. But further improvements (stability, error handling) are needed and foreseen.
1 parent f94d7fb commit f86380c

File tree

23 files changed

+1729
-209
lines changed

23 files changed

+1729
-209
lines changed

Common/SimConfig/include/SimConfig/SimConfig.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ struct SimConfigData {
4949
long mTimestamp; // timestamp to anchor transport simulation to
5050
int mField; // L3 field setting in kGauss: +-2,+-5 and 0
5151
bool mUniformField = false; // uniform magnetic field
52+
bool mAsService = false; // if simulation should be run as service/deamon (does not exit after run)
5253

5354
ClassDefNV(SimConfigData, 4);
5455
};
@@ -86,6 +87,7 @@ class SimConfig
8687

8788
void resetFromConfigData(SimConfigData const& data) { mConfigData = data; }
8889
SimConfigData const& getConfigData() const { return mConfigData; }
90+
SimConfigData& getConfigData() { return mConfigData; }
8991

9092
// get MC engine
9193
std::string getMCEngine() const { return mConfigData.mMCEngine; }
@@ -111,12 +113,46 @@ class SimConfig
111113
int getStartSeed() const { return mConfigData.mStartSeed; }
112114
int getNSimWorkers() const { return mConfigData.mSimWorkers; }
113115
bool isFilterOutNoHitEvents() const { return mConfigData.mFilterNoHitEvents; }
116+
bool asService() const { return mConfigData.mAsService; }
114117

115118
private:
116119
SimConfigData mConfigData; //!
117120

118121
ClassDefNV(SimConfig, 1);
119122
};
123+
124+
// Configuration struct used for simulation reconfig (when processing
125+
// in batches and in "deamonized" mode. Note that in comparison to SimConfig,
126+
// fewer fields are offered (because many things are not easy to reconfigure).
127+
//! TODO: Make this a base class of SimConfigData?
128+
129+
struct SimReconfigData {
130+
std::string generator; // chosen VMC generator
131+
std::string trigger; // chosen VMC generator trigger
132+
unsigned int nEvents; // number of events to be simulated
133+
std::string extKinfileName; // file name of external kinematics file (needed for ext kinematics generator)
134+
std::string embedIntoFileName; // filename containing the reference events to be used for the embedding
135+
unsigned int startEvent = 0; // index of first event to be taken
136+
float mBMax; // maximum for impact parameter sampling
137+
std::string outputPrefix; // prefix to be used for output files
138+
std::string outputDir; // output directory
139+
std::string keyValueTokens; // a string holding arbitrary sequence of key-value tokens (for ConfigurableParams)
140+
// ** WE NEED TO BE CAREFUL: NOT EVERYTHING MAY BE RECONFIGURABLE VIA PARAMETER CHANGE **
141+
// Foo.parameter1=x,Bar.parameter2=y,Baz.paramter3=hello
142+
std::string configFile; // path to a JSON or INI config file (file extension is required to determine type).
143+
// values within the config file will override values set in code by the param classes
144+
// but will themselves be overridden by any values given in mKeyValueTokens.
145+
unsigned int primaryChunkSize; // defining max granularity for input primaries of a sim job
146+
int startSeed; // base for random number seeds
147+
bool stop; // to shut down the service
148+
149+
ClassDefNV(SimReconfigData, 1);
150+
};
151+
152+
// construct reconfig struct given a configuration string (boost program options format)
153+
// returns true if successful/ false otherwise
154+
bool parseSimReconfigFromString(std::string const& argumentstring, SimReconfigData& config);
155+
120156
} // namespace conf
121157
} // namespace o2
122158

Common/SimConfig/src/SimConfig.cxx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ void SimConfig::initOptions(boost::program_options::options_description& options
2727
"generator,g", bpo::value<std::string>()->default_value("boxgen"), "Event generator to be used.")(
2828
"trigger,t", bpo::value<std::string>()->default_value(""), "Event generator trigger to be used.")(
2929
"modules,m", bpo::value<std::vector<std::string>>()->multitoken()->default_value(std::vector<std::string>({"all"}), "all modules"), "list of detectors")(
30-
"skipModules", bpo::value<std::vector<std::string>>()->multitoken()->default_value(std::vector<std::string>({""}), ""), "list of detectors to skip (precendence over -m")("nEvents,n", bpo::value<unsigned int>()->default_value(1), "number of events")(
30+
"skipModules", bpo::value<std::vector<std::string>>()->multitoken()->default_value(std::vector<std::string>({""}), ""), "list of detectors to skip (precendence over -m")(
31+
"nEvents,n", bpo::value<unsigned int>()->default_value(1), "number of events")(
3132
"startEvent", bpo::value<unsigned int>()->default_value(0), "index of first event to be used (when applicable)")(
3233
"extKinFile", bpo::value<std::string>()->default_value("Kinematics.root"),
3334
"name of kinematics file for event generator from file (when applicable)")(
@@ -47,7 +48,8 @@ void SimConfig::initOptions(boost::program_options::options_description& options
4748
"nworkers,j", bpo::value<int>()->default_value(nsimworkersdefault), "number of parallel simulation workers (only for parallel mode)")(
4849
"noemptyevents", "only writes events with at least one hit")(
4950
"CCDBUrl", bpo::value<std::string>()->default_value("ccdb-test.cern.ch:8080"), "URL for CCDB to be used.")(
50-
"timestamp", bpo::value<long>()->default_value(-1), "global timestamp value (for anchoring) - default is now");
51+
"timestamp", bpo::value<long>()->default_value(-1), "global timestamp value (for anchoring) - default is now")(
52+
"asservice", bpo::value<bool>()->default_value(false), "run in service/server mode");
5153
}
5254

5355
bool SimConfig::resetFromParsedMap(boost::program_options::variables_map const& vm)
@@ -105,6 +107,7 @@ bool SimConfig::resetFromParsedMap(boost::program_options::variables_map const&
105107
mConfigData.mSimWorkers = vm["nworkers"].as<int>();
106108
mConfigData.mTimestamp = vm["timestamp"].as<long>();
107109
mConfigData.mCCDBUrl = vm["CCDBUrl"].as<std::string>();
110+
mConfigData.mAsService = vm["asservice"].as<bool>();
108111
if (vm.count("noemptyevents")) {
109112
mConfigData.mFilterNoHitEvents = true;
110113
}
@@ -144,4 +147,49 @@ bool SimConfig::resetFromArguments(int argc, char* argv[])
144147
return resetFromParsedMap(vm);
145148
}
146149

150+
namespace o2::conf
151+
{
152+
// returns a reconfig struct given a configuration string (boost program options format)
153+
bool parseSimReconfigFromString(std::string const& argumentstring, SimReconfigData& data)
154+
{
155+
namespace bpo = boost::program_options;
156+
157+
bpo::options_description options("Allowed options");
158+
159+
options.add_options()(
160+
"nEvents,n", bpo::value<unsigned int>(&data.nEvents)->default_value(1), "number of events")(
161+
"generator,g", bpo::value<std::string>(&data.generator)->default_value("boxgen"), "Event generator to be used.")(
162+
"trigger,t", bpo::value<std::string>(&data.trigger)->default_value(""), "Event generator trigger to be used.")(
163+
"startEvent", bpo::value<unsigned int>(&data.startEvent)->default_value(0), "index of first event to be used (when applicable)")(
164+
"extKinFile", bpo::value<std::string>(&data.extKinfileName)->default_value("Kinematics.root"),
165+
"name of kinematics file for event generator from file (when applicable)")(
166+
"embedIntoFile", bpo::value<std::string>(&data.embedIntoFileName)->default_value(""),
167+
"filename containing the reference events to be used for the embedding")(
168+
"bMax,b", bpo::value<float>(&data.mBMax)->default_value(0.), "maximum value for impact parameter sampling (when applicable)")(
169+
"outPrefix,o", bpo::value<std::string>(&data.outputPrefix)->default_value("o2sim"), "prefix of output files")(
170+
"outDir,d", bpo::value<std::string>(&data.outputDir), "directory where to put simulation output (created when non-existant")(
171+
"configKeyValues", bpo::value<std::string>(&data.keyValueTokens)->default_value(""), "semicolon separated key=value strings (e.g.: 'TPC.gasDensity=1;...")(
172+
"configFile", bpo::value<std::string>(&data.configFile)->default_value(""), "Path to an INI or JSON configuration file")(
173+
"chunkSize", bpo::value<unsigned int>(&data.primaryChunkSize)->default_value(500), "max size of primary chunk (subevent) distributed by server")(
174+
"seed", bpo::value<int>(&data.startSeed)->default_value(-1), "initial seed (default: -1 random)")(
175+
"stop", bpo::value<bool>(&data.stop)->default_value(false), "control command to shut down daemon");
176+
177+
bpo::variables_map vm;
178+
try {
179+
bpo::store(bpo::command_line_parser(bpo::split_unix(argumentstring))
180+
.options(options)
181+
.run(),
182+
vm);
183+
bpo::notify(vm);
184+
} catch (const bpo::error& e) {
185+
std::cerr << e.what() << "\n\n";
186+
std::cerr << "Error parsing ReConfig data; Available options:\n";
187+
std::cerr << options << std::endl;
188+
return false;
189+
}
190+
return true;
191+
}
192+
193+
} // namespace o2::conf
194+
147195
ClassImp(o2::conf::SimConfig);

Generators/include/Generators/Generator.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "FairGenerator.h"
1717
#include "TParticle.h"
1818
#include "Generators/Trigger.h"
19+
#include <functional>
1920
#include <vector>
2021

2122
namespace o2
@@ -90,6 +91,9 @@ class Generator : public FairGenerator
9091
/** notification methods **/
9192
virtual void notifyEmbedding(const o2::dataformats::MCEventHeader* eventHeader){};
9293

94+
void setTriggerOkHook(std::function<void(std::vector<TParticle> const& p, int eventCount)> f) { mTriggerOkHook = f; }
95+
void setTriggerFalseHook(std::function<void(std::vector<TParticle> const& p, int eventCount)> f) { mTriggerFalseHook = f; }
96+
9397
protected:
9498
/** copy constructor **/
9599
Generator(const Generator&);
@@ -113,6 +117,12 @@ class Generator : public FairGenerator
113117
std::vector<Trigger> mTriggers; //!
114118
std::vector<DeepTrigger> mDeepTriggers; //!
115119

120+
// we allow to register callbacks so as to take specific user actions when
121+
// a trigger was ok nor not
122+
std::function<void(std::vector<TParticle> const& p, int eventCount)> mTriggerOkHook = [](std::vector<TParticle> const& p, int eventCount) {};
123+
std::function<void(std::vector<TParticle> const& p, int eventCount)> mTriggerFalseHook = [](std::vector<TParticle> const& p, int eventCount) {};
124+
int mReadEventCounter = 0; // counting the number of times
125+
116126
/** conversion data members **/
117127
double mMomentumUnit = 1.; // [GeV/c]
118128
double mEnergyUnit = 1.; // [GeV/c]

Generators/src/Generator.cxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Bool_t
6262

6363
/** endless generate-and-trigger loop **/
6464
while (true) {
65+
mReadEventCounter++;
6566

6667
/** clear particle vector **/
6768
mParticles.clear();
@@ -78,7 +79,10 @@ Bool_t
7879

7980
/** trigger event **/
8081
if (triggerEvent()) {
82+
mTriggerOkHook(mParticles, mReadEventCounter);
8183
break;
84+
} else {
85+
mTriggerFalseHook(mParticles, mReadEventCounter);
8286
}
8387
}
8488

Steer/include/Steer/O2MCApplication.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class O2MCApplication : public O2MCApplicationBase
7575
void GeneratePrimaries() override
7676
{
7777
// ordinarily we would call the event generator ...
78-
LOG(INFO) << "Generate primaries " << mPrimaries.size() << "\n";
78+
LOG(DEBUG) << "O2MCApplication: Init primaries from external buffer " << mPrimaries.size();
7979
GetStack()->Reset();
8080
// but here we init the stack from
8181
// a vector of particles that someone sets externally

run/CMakeLists.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ o2_add_executable(serial
5656

5757
o2_add_executable(sim
5858
SOURCES o2sim_parallel.cxx
59-
PUBLIC_LINK_LIBRARIES internal::allsim O2::Version)
59+
PUBLIC_LINK_LIBRARIES internal::allsim O2::Version
60+
TARGETVARNAME simdriver)
61+
6062

6163
o2_add_executable(primary-server-device-runner
6264
COMPONENT_NAME sim
@@ -72,7 +74,7 @@ o2_add_executable(hit-merger-runner
7274
SOURCES O2HitMergerRunner.cxx
7375
PUBLIC_LINK_LIBRARIES internal::allsim)
7476

75-
o2_data_file(COPY o2simtopology.json DESTINATION config)
77+
o2_data_file(COPY o2simtopology_template.json DESTINATION config)
7678

7779
# * # add a complex simulation as a unit test (if simulation was enabled)
7880
# perform
@@ -235,3 +237,5 @@ o2_add_test_wrapper(NAME o2sim_G4_checklogs
235237

236238
set_tests_properties(o2sim_G3_checklogs
237239
PROPERTIES FIXTURES_REQUIRED G4)
240+
241+
install(FILES o2-sim-client.py PERMISSIONS GROUP_READ GROUP_EXECUTE OWNER_EXECUTE OWNER_WRITE OWNER_READ WORLD_EXECUTE WORLD_READ DESTINATION ${CMAKE_INSTALL_BINDIR})

0 commit comments

Comments
 (0)