1212#include " Generators/GeneratorHybrid.h"
1313#include < fairlogger/Logger.h>
1414#include < algorithm>
15+ #include < unordered_map>
1516#include < tbb/concurrent_queue.h>
1617#include < tbb/task_arena.h>
1718#include < tbb/parallel_for.h>
@@ -30,6 +31,20 @@ GeneratorHybrid& GeneratorHybrid::Instance(const std::string& inputgens)
3031 return instance;
3132}
3233
34+ namespace
35+ {
36+ // Canonical description of a parsed generator configuration
37+ template <typename T>
38+ std::string canonicalConfig (T const * conf)
39+ {
40+ if (!conf) {
41+ return {};
42+ }
43+ auto json = TBufferJSON::ToJSON (conf);
44+ return std::string (json.Data (), json.Length ());
45+ }
46+ } // namespace
47+
3348GeneratorHybrid::GeneratorHybrid (const std::string& inputgens)
3449{
3550 // This generator has trivial unit conversions
@@ -62,7 +77,7 @@ GeneratorHybrid::GeneratorHybrid(const std::string& inputgens)
6277 exit (1 );
6378 }
6479 mRandomize = GeneratorHybridParam::Instance ().randomize ;
65- if (mConfigs .size () != mInputGens .size ()) {
80+ if (mConfigs .size () != mInputGens .size () || mGenSignatures . size () != mInputGens . size () ) {
6681 LOG (fatal) << " Number of configurations does not match the number of generators" ;
6782 exit (1 );
6883 }
@@ -90,11 +105,30 @@ GeneratorHybrid::GeneratorHybrid(const std::string& inputgens)
90105 return ;
91106 }
92107 }
108+ // Only in sequential mode, generators which are configured in exactly the same way are
109+ // instantiated only once and the corresponding slots are served by that single instance.
110+ // If num_workers <= 1, the reuse of identical generators is automatically disabled.
111+ const bool reuseIdentical = (mGenerationMode == GenMode::kSeq ) && GeneratorHybridParam::Instance ().reuseIdenticalGenerators && GeneratorHybridParam::Instance ().num_workers <= 1 ;
112+ std::unordered_map<std::string, int > signatureToInstance;
93113 for (auto gen : mInputGens ) {
94114 // Search if the generator name is inside generatorNames (which is a vector of strings)
95115 LOG (info) << " Checking if generator " << gen << " is in the list of available generators \n " ;
96116 if (std::find (generatorNames.begin (), generatorNames.end (), gen) != generatorNames.end ()) {
97117 LOG (info) << " Found generator " << gen << " in the list of available generators \n " ;
118+ if (reuseIdentical) {
119+ auto const & signature = mGenSignatures [index];
120+ auto known = signatureToInstance.find (signature);
121+ if (known != signatureToInstance.end ()) {
122+ LOG (info) << " Generator " << gen << " (slot " << index << " ) is configured exactly as instance "
123+ << known->second << " : reusing it instead of creating a new one" ;
124+ mSlotToInstance .push_back (known->second );
125+ mGens .push_back (gen);
126+ index++;
127+ continue ;
128+ }
129+ // the instance about to be created will be the last one of 'gens'
130+ signatureToInstance[signature] = gens.size ();
131+ }
98132 if (gen.compare (" boxgen" ) == 0 ) {
99133 if (mConfigs [index].compare (" " ) == 0 ) {
100134 gens.push_back (std::make_shared<o2::eventgen::BoxGenerator>());
@@ -184,8 +218,17 @@ GeneratorHybrid::GeneratorHybrid(const std::string& inputgens)
184218 LOG (fatal) << " Generator " << gen << " not found in the list of available generators \n " ;
185219 exit (1 );
186220 }
221+ // a new instance has just been created for this slot
222+ mSlotToInstance .push_back (gens.size () - 1 );
187223 index++;
188224 }
225+ LOG (info) << " Hybrid generator: " << mInputGens .size () << " configured generator(s) served by "
226+ << gens.size () << " generator instance(s)" ;
227+ if (reuseIdentical) {
228+ for (size_t slot = 0 ; slot < mSlotToInstance .size (); ++slot) {
229+ LOG (info) << " slot " << slot << " (" << mInputGens [slot] << " ) -> instance " << mSlotToInstance [slot];
230+ }
231+ }
189232}
190233
191234GeneratorHybrid::~GeneratorHybrid ()
@@ -197,29 +240,39 @@ GeneratorHybrid::~GeneratorHybrid()
197240Bool_t GeneratorHybrid::Init ()
198241{
199242 // init all sub-gens
243+ mGenIsInitialized .resize (gens.size (), false );
200244 int count = 0 ;
201245 for (auto & gen : mGens ) {
246+ // slots sharing one instance must configure, initialise and equip it only once
247+ const auto instance = mSlotToInstance [count];
248+ if (mGenIsInitialized [instance]) {
249+ LOG (info) << " Generator " << gen << " (slot " << count << " ) is served by the already initialized instance " << instance;
250+ addSubGenerator (count, gen);
251+ count++;
252+ continue ;
253+ }
202254 if (gen == " pythia8pp" ) {
203255 auto config = std::string (std::getenv (" O2_ROOT" )) + " /share/Generators/egconfig/pythia8_inel.cfg" ;
204256 LOG (info) << " Setting \' Pythia8\' base configuration: " << config << std::endl;
205- dynamic_cast <o2::eventgen::GeneratorPythia8*>(gens[count ].get ())->setConfig (config);
257+ dynamic_cast <o2::eventgen::GeneratorPythia8*>(gens[instance ].get ())->setConfig (config);
206258 } else if (gen == " pythia8hf" ) {
207259 auto config = std::string (std::getenv (" O2_ROOT" )) + " /share/Generators/egconfig/pythia8_hf.cfg" ;
208260 LOG (info) << " Setting \' Pythia8\' base configuration: " << config << std::endl;
209- dynamic_cast <o2::eventgen::GeneratorPythia8*>(gens[count ].get ())->setConfig (config);
261+ dynamic_cast <o2::eventgen::GeneratorPythia8*>(gens[instance ].get ())->setConfig (config);
210262 } else if (gen == " pythia8hi" ) {
211263 auto config = std::string (std::getenv (" O2_ROOT" )) + " /share/Generators/egconfig/pythia8_hi.cfg" ;
212264 LOG (info) << " Setting \' Pythia8\' base configuration: " << config << std::endl;
213- dynamic_cast <o2::eventgen::GeneratorPythia8*>(gens[count ].get ())->setConfig (config);
265+ dynamic_cast <o2::eventgen::GeneratorPythia8*>(gens[instance ].get ())->setConfig (config);
214266 } else if (gen == " pythia8powheg" ) {
215267 auto config = std::string (std::getenv (" O2_ROOT" )) + " /share/Generators/egconfig/pythia8_powheg.cfg" ;
216268 LOG (info) << " Setting \' Pythia8\' base configuration: " << config << std::endl;
217- dynamic_cast <o2::eventgen::GeneratorPythia8*>(gens[count ].get ())->setConfig (config);
269+ dynamic_cast <o2::eventgen::GeneratorPythia8*>(gens[instance ].get ())->setConfig (config);
218270 }
219- gens[count]->Init (); // TODO: move this to multi-threaded
271+ gens[instance]->Init (); // TODO: move this to multi-threaded
272+ mGenIsInitialized [instance] = true ;
220273 addSubGenerator (count, gen);
221274 if (mTriggerModes [count] != o2::eventgen::Generator::kTriggerOFF ) {
222- gens[count ]->setTriggerMode (mTriggerModes [count]);
275+ gens[instance ]->setTriggerMode (mTriggerModes [count]);
223276 LOG (info) << " Setting Trigger mode of generator " << gen << " to: " << mTriggerModes [count];
224277 o2::eventgen::Trigger trigger = nullptr ;
225278 o2::eventgen::DeepTrigger deeptrigger = nullptr ;
@@ -244,9 +297,9 @@ Bool_t GeneratorHybrid::Init()
244297 LOG (info) << " Trigger " << trg << " of generator " << gen << " successfully set" ;
245298 }
246299 if (trigger) {
247- gens[count ]->addTrigger (trigger);
300+ gens[instance ]->addTrigger (trigger);
248301 } else {
249- gens[count ]->addDeepTrigger (deeptrigger);
302+ gens[instance ]->addDeepTrigger (deeptrigger);
250303 }
251304 }
252305 }
@@ -293,7 +346,6 @@ Bool_t GeneratorHybrid::Init()
293346 LOG (info) << " Generators will be used in sequence, following provided fractions" ;
294347 }
295348
296- mGenIsInitialized .resize (gens.size (), false );
297349 if (mGenerationMode == GenMode::kParallel ) {
298350 // in parallel mode we just use one queue --> collaboration
299351 mResultQueue .resize (1 );
@@ -411,24 +463,15 @@ bool GeneratorHybrid::generateEvent()
411463bool GeneratorHybrid::importParticles ()
412464{
413465 int genIndex = -1 ;
414- std::vector<int > subGenIndex = {};
415466 if (mIndex == -1 ) {
416467 // this means parallel mode ---> we have a common queue
417468 mResultQueue [0 ].pop (genIndex);
418- } else {
419- // need to pop from a particular queue
420- if (!mCocktailMode ) {
421- mResultQueue [mIndex ].pop (genIndex);
422- } else {
423- // in cocktail mode we need to pop from the group queue
424- subGenIndex.resize (mGroups [mIndex ].size ());
425- for (size_t pos = 0 ; pos < mGroups [mIndex ].size (); ++pos) {
426- int subIndex = mGroups [mIndex ][pos];
427- LOG (info) << " Getting generator " << mGens [subIndex] << " from cocktail group " << mIndex ;
428- mResultQueue [subIndex].pop (subGenIndex[pos]);
429- }
430- }
469+ } else if (!mCocktailMode ) {
470+ // need to pop from the queue of the instance serving this generator slot
471+ mResultQueue [mSlotToInstance [mIndex ]].pop (genIndex);
431472 }
473+ // in cocktail mode the events of the group members are popped one by one while they are
474+ // imported, so that one instance shared by two members of the same group can serve both of them.
432475
433476 auto unit_transformer = [](auto & p, auto pos_unit, auto time_unit, auto en_unit, auto mom_unit) {
434477 p.SetMomentum (p.Px () * mom_unit, p.Py () * mom_unit, p.Pz () * mom_unit, p.Energy () * en_unit);
@@ -456,9 +499,13 @@ bool GeneratorHybrid::importParticles()
456499 // this is important when using event pools where the full header information is forwarded from the generator
457500 // otherwise some events might have mixed header information from different generators
458501 mMCEventHeader .Reset ();
459- if (mCocktailMode ) {
502+ if (mCocktailMode && mIndex >= 0 ) {
460503 // in cocktail mode we need to merge the particles from the different generators
461- for (auto subIndex : subGenIndex) {
504+ for (size_t pos = 0 ; pos < mGroups [mIndex ].size (); ++pos) {
505+ const auto slotIndex = mGroups [mIndex ][pos];
506+ LOG (info) << " Getting generator " << mGens [slotIndex] << " from cocktail group " << mIndex ;
507+ int subIndex = -1 ;
508+ mResultQueue [mSlotToInstance [slotIndex]].pop (subIndex);
462509 LOG (info) << " Importing particles for task " << subIndex;
463510 auto subParticles = gens[subIndex]->getParticles ();
464511
@@ -550,27 +597,32 @@ Bool_t GeneratorHybrid::confSetter(const auto& gen)
550597{
551598 std::string name = gen[" name" ].GetString ();
552599 mInputGens .push_back (name);
600+ std::string cfgSignature{" default" }; // canonical description of the configuration parsed below
553601 if (gen.HasMember (" config" )) {
554602 if (name == " boxgen" ) {
555603 const auto & boxconf = gen[" config" ];
556604 auto boxConfig = TBufferJSON::FromJSON<o2::eventgen::BoxGenConfig>(jsonValueToString (boxconf).c_str ());
557605 mBoxGenConfigs .push_back (std::move (boxConfig));
558606 mConfigs .push_back (" boxgen_" + std::to_string (mBoxGenConfigs .size () - 1 ));
607+ cfgSignature = canonicalConfig (mBoxGenConfigs .back ().get ());
559608 } else if (name == " pythia8" ) {
560609 const auto & pythia8conf = gen[" config" ];
561610 auto pythia8Config = TBufferJSON::FromJSON<o2::eventgen::Pythia8GenConfig>(jsonValueToString (pythia8conf).c_str ());
562611 mPythia8GenConfigs .push_back (std::move (pythia8Config));
563612 mConfigs .push_back (" pythia8_" + std::to_string (mPythia8GenConfigs .size () - 1 ));
613+ cfgSignature = canonicalConfig (mPythia8GenConfigs .back ().get ());
564614 } else if (name == " evtpool" ) {
565615 const auto & o2kineconf = gen[" config" ];
566616 auto poolConfig = TBufferJSON::FromJSON<o2::eventgen::EventPoolGenConfig>(jsonValueToString (o2kineconf).c_str ());
567617 mEventPoolConfigs .push_back (*poolConfig);
568618 mConfigs .push_back (" evtpool_" + std::to_string (mEventPoolConfigs .size () - 1 ));
619+ cfgSignature = canonicalConfig (&mEventPoolConfigs .back ());
569620 } else if (name == " external" ) {
570621 const auto & extconf = gen[" config" ];
571622 auto extConfig = TBufferJSON::FromJSON<o2::eventgen::ExternalGenConfig>(jsonValueToString (extconf).c_str ());
572623 mExternalGenConfigs .push_back (std::move (extConfig));
573624 mConfigs .push_back (" external_" + std::to_string (mExternalGenConfigs .size () - 1 ));
625+ cfgSignature = canonicalConfig (mExternalGenConfigs .back ().get ());
574626 } else if (name == " hepmc" ) {
575627 const auto & genconf = gen[" config" ];
576628 const auto & cmdconf = genconf[" configcmd" ];
@@ -580,6 +632,7 @@ Bool_t GeneratorHybrid::confSetter(const auto& gen)
580632 mFileOrCmdGenConfigs .push_back (std::move (cmdConfig));
581633 mHepMCGenConfigs .push_back (std::move (hepmcConfig));
582634 mConfigs .push_back (" hepmc_" + std::to_string (mFileOrCmdGenConfigs .size () - 1 ));
635+ cfgSignature = canonicalConfig (mFileOrCmdGenConfigs .back ().get ()) + canonicalConfig (mHepMCGenConfigs .back ().get ());
583636 } else {
584637 mConfigs .push_back (" " );
585638 }
@@ -591,6 +644,12 @@ Bool_t GeneratorHybrid::confSetter(const auto& gen)
591644 mConfigs .push_back (" " );
592645 }
593646 }
647+ // Slots are served by one shared instance only when their signatures match, so a
648+ // configuration which could not be serialised must never compare equal to another one.
649+ if (cfgSignature.empty ()) {
650+ cfgSignature = " slot:" + std::to_string (mInputGens .size () - 1 );
651+ }
652+ mGenSignatures .push_back (name + " |cfg:" + cfgSignature);
594653 if (gen.HasMember (" triggers" )) {
595654 const auto & trigger = gen[" triggers" ];
596655 auto trigger_specs = [this , &trigger]() {
@@ -653,6 +712,14 @@ Bool_t GeneratorHybrid::confSetter(const auto& gen)
653712 mTriggerMacros .push_back ({" " });
654713 mTriggerFuncs .push_back ({" " });
655714 }
715+ // the triggers are attached to the generator instance, hence they are part of its identity
716+ mGenSignatures .back () += " |trg:" + std::to_string (static_cast <int >(mTriggerModes .back ()));
717+ for (size_t trg = 0 ; trg < mTriggerMacros .back ().size (); ++trg) {
718+ mGenSignatures .back () += " |" + mTriggerMacros .back ()[trg];
719+ if (trg < mTriggerFuncs .back ().size ()) {
720+ mGenSignatures .back () += " :" + mTriggerFuncs .back ()[trg];
721+ }
722+ }
656723 return true ;
657724}
658725
0 commit comments