File indexing completed on 2026-09-27 09:14:57
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020 #ifndef LAGER_CORE_GENERATOR_LOADED
0021 #define LAGER_CORE_GENERATOR_LOADED
0022
0023 #include <TRandom.h>
0024 #include <algorithm>
0025 #include <lager/core/assert.hh>
0026 #include <lager/core/configuration.hh>
0027 #include <lager/core/factory.hh>
0028 #include <lager/core/interval.hh>
0029 #include <memory>
0030
0031 namespace lager {
0032
0033
0034
0035
0036 class generator_data {
0037 public:
0038 explicit generator_data(const double xs = 1.) : cross_section_{xs} {}
0039
0040 double cross_section() const { return cross_section_; }
0041 double jacobian() const { return jacobian_; }
0042
0043 void update_cross_section(const double xs) { cross_section_ *= xs; }
0044 void update_jacobian(const double j) { jacobian_ *= j; }
0045
0046 private:
0047 double cross_section_;
0048 double jacobian_{1.};
0049
0050 };
0051
0052
0053
0054
0055
0056
0057 template <class Data, class... Input> class generator {
0058 public:
0059 using data_type = Data;
0060
0061 generator(std::shared_ptr<TRandom> r) : rng_{std::move(r)} {}
0062
0063
0064 virtual data_type generate(const Input&... input) = 0;
0065 data_type operator()(const Input&... input) { return generate(input...); }
0066
0067
0068
0069 virtual double max_cross_section() const = 0;
0070 virtual double phase_space() const = 0;
0071
0072 protected:
0073
0074 std::shared_ptr<TRandom> rng() const { return rng_; }
0075
0076
0077
0078
0079
0080
0081
0082
0083 template <class Func1D>
0084 double rand_f(const interval<double>& range, Func1D f, double fmax) const {
0085 const double x = rng()->Uniform(range.min, range.max);
0086 const double test = rng()->Uniform(0, fmax);
0087 const double fx = f(x);
0088
0089 if (test > fx) {
0090 return rand_f(range, f, fmax);
0091 }
0092
0093 tassert(fx <= fmax, "fmax set too small in rand_f call");
0094
0095 return x;
0096 }
0097
0098 template <class Func2D>
0099 std::pair<double, double> rand_f(const interval<double>& range1,
0100 const interval<double>& range2, Func2D f,
0101 double fmax) const {
0102 const double x = rng()->Uniform(range1.min, range1.max);
0103 const double y = rng()->Uniform(range2.min, range2.max);
0104 const double test = rng()->Uniform(0, fmax);
0105 const double fxy = f(x, y);
0106
0107 if (test > fxy) {
0108 return rand_f(range1, range2, f, fmax);
0109 }
0110
0111 tassert(fxy <= fmax, "fmax set too small in rand_f call (" +
0112 std::to_string(fxy) + ">" + std::to_string(fmax) +
0113 ")");
0114 return {x, y};
0115 }
0116
0117 private:
0118 std::shared_ptr<TRandom> rng_;
0119 };
0120
0121
0122
0123
0124
0125
0126
0127
0128
0129
0130
0131 template <class Event, class InitialData>
0132 class process_generator : public generator<Event, InitialData> {
0133 public:
0134 using event_type = Event;
0135 using initial_type = InitialData;
0136 using base_type = generator<Event, InitialData>;
0137
0138 static factory<process_generator, const configuration&, const string_path&,
0139 std::shared_ptr<TRandom>>
0140 factory_instance;
0141
0142 process_generator(std::shared_ptr<TRandom> r) : base_type{std::move(r)} {}
0143
0144 virtual event_type generate(const initial_type&) = 0;
0145 };
0146
0147 template <class Event, class InitialData>
0148 factory<process_generator<Event, InitialData>, const configuration&,
0149 const string_path&, std::shared_ptr<TRandom>>
0150 process_generator<Event, InitialData>::factory_instance;
0151
0152
0153
0154
0155
0156
0157
0158
0159
0160 template <class Event> class event_processor : public generator<void> {
0161 public:
0162 using event_type = Event;
0163 using base_type = generator<void>;
0164
0165 event_processor(std::shared_ptr<TRandom> r) : base_type{std::move(r)} {}
0166
0167 virtual void process(event_type&) const = 0;
0168
0169 private:
0170 virtual void generate() {}
0171 virtual double max_cross_section() const { return -1.; }
0172 virtual double phase_space() const { return -1.; }
0173 };
0174
0175
0176
0177
0178
0179
0180
0181
0182
0183
0184
0185
0186
0187
0188
0189
0190
0191
0192
0193
0194
0195 template <class Event, class InitialData>
0196 class event_generator : public generator<std::vector<Event>>,
0197 public configurable {
0198 public:
0199 using event_type = Event;
0200 using initial_type = InitialData;
0201 using base_type = generator<std::vector<Event>>;
0202 using process_type = process_generator<event_type, initial_type>;
0203
0204 event_generator(const configuration& cf, const string_path& path,
0205 std::shared_ptr<TRandom> r)
0206 : base_type{std::move(r)}
0207 , configurable{cf, path}
0208 , penalty_{cf.get<double>(path / "advanced/penalty", 1.0)} {
0209 init_process_list();
0210 init_lumi(cf);
0211 LOG_INFO("event_generator",
0212 "advanced/penalty: " + std::to_string(penalty_));
0213 }
0214
0215 virtual std::vector<event_type> generate() {
0216
0217
0218
0219
0220
0221 std::vector<event_type> good_event_list;
0222 do {
0223
0224
0225 std::vector<event_type> event_list;
0226 do {
0227 n_trials_ += 1;
0228 auto initial = generate_initial();
0229
0230 if (initial.cross_section() <= 0) {
0231 LOG_JUNK("event_generator",
0232 "Initial cross section <= 0, abandoning trial cycle.");
0233 continue;
0234 }
0235 LOG_JUNK("event_generator",
0236 "Initial cross section: " +
0237 std::to_string(initial.cross_section()));
0238
0239 for (auto& process : process_list_) {
0240 LOG_JUNK(process.name, "Generating a trial event");
0241
0242
0243 if (proc_volume_ != process.vol &&
0244 this->rng()->Uniform(0, proc_volume_) > process.vol) {
0245 LOG_JUNK(process.name, "Skipping this trial cycle.");
0246 continue;
0247 }
0248
0249 auto event = process.gen->generate(initial);
0250
0251
0252 if (event.cross_section() <= 0) {
0253 LOG_JUNK(process.name,
0254 "Cross section <= 0, skipping this trial cycle");
0255 continue;
0256 } else if (event.cross_section() >
0257 initial_max_ * process.max * penalty_) {
0258 LOG_WARNING(
0259 process.name,
0260 "Cross section maximum exceeded (" +
0261 std::to_string(event.cross_section()) + " > " +
0262 std::to_string(initial_max_ * process.max * penalty_) +
0263 "), the distributions will be invalid if this "
0264 "happens too often.");
0265 LOG_WARNING(
0266 process.name,
0267 "To mitigate, either increase "
0268 "the configuration paramater generator/advanced/penalty "
0269 "(which gets multiplied with the cross section maximum "
0270 "during the accept-reject step), or fix the cross "
0271 "section maximum estimation in the actual Physics "
0272 "module.");
0273 }
0274
0275 LOG_JUNK(process.name,
0276 "Testing accept reject for xs: " +
0277 std::to_string(event.cross_section()) + " (max: " +
0278 std::to_string(process.max * initial_max_) + ")");
0279 if (this->rng()->Uniform(0, initial_max_ * process.max * penalty_) <
0280 event.cross_section()) {
0281 LOG_JUNK(process.name, "Event accepted!");
0282 event.update_process(process.id);
0283 event_list.push_back(event);
0284 n_gen_events_ += 1;
0285 } else {
0286 LOG_JUNK(process.name, "Event rejected.");
0287 }
0288 }
0289 } while (event_list.empty());
0290
0291 for (auto& event : event_list) {
0292 LOG_JUNK("generator", "Processing event (process " +
0293 std::to_string(event.process()) + ")");
0294 build_event(event);
0295 if (event.weight() > 0) {
0296 LOG_JUNK("generator",
0297 "Event accepted after event builder step (weight: " +
0298 std::to_string(event.weight()) + ", reset to 1)");
0299 n_events_ += 1;
0300
0301
0302
0303 branching_ratio_ = event.weight();
0304 event.update_stat(static_cast<size_t>(n_events_), cross_section());
0305 event.reset_weight();
0306 good_event_list.push_back(event);
0307 } else {
0308 LOG_JUNK("generator",
0309 "Event rejected after event builder step (weight: " +
0310 std::to_string(event.weight()) + ")");
0311 }
0312 }
0313
0314 } while (good_event_list.empty());
0315
0316 return good_event_list;
0317 }
0318
0319
0320
0321
0322
0323
0324
0325
0326
0327
0328
0329
0330
0331
0332 double cross_section() const {
0333
0334
0335 if (n_events_ < 50) {
0336 return volume_ * process_list_.size();
0337 }
0338
0339 return volume_ * n_events() / n_trials_;
0340 }
0341 double partial_cross_section() const {
0342 return cross_section() * branching_ratio_;
0343 }
0344 int64_t n_events() const { return n_events_; }
0345
0346
0347 double acceptance() const { return double(n_events_) / n_gen_events_; }
0348
0349
0350
0351 int64_t n_requested() const {
0352 return (n_requested_ > 0) ? n_requested_
0353 : static_cast<int64_t>(std::round(
0354 lumi_ * partial_cross_section()));
0355 }
0356
0357 bool finished() const { return (n_events() >= n_requested()); }
0358
0359 protected:
0360
0361
0362 virtual initial_type generate_initial() const = 0;
0363
0364 virtual void build_event(event_type&) const = 0;
0365
0366
0367
0368
0369 template <class InitialGen>
0370 void register_initial(const std::shared_ptr<InitialGen>& gen) {
0371 LOG_DEBUG("event_generator",
0372 "Registering phase space and cross section max");
0373 tassert(gen, "Requested generator is a null pointer");
0374 initial_ps_ *= gen->phase_space();
0375 initial_max_ *= gen->max_cross_section();
0376 LOG_DEBUG("event_generator",
0377 "New initial cross section max: " + std::to_string(initial_max_));
0378 LOG_DEBUG("event_generator",
0379 "New initial phase space: " + std::to_string(initial_ps_));
0380 update_volume();
0381 }
0382
0383 private:
0384 constexpr static const int N_MAX_PROC{10};
0385 constexpr static const char* PROC_KEY{"process_"};
0386 static std::string process_id(const int i) {
0387 return PROC_KEY + std::to_string(i);
0388 }
0389
0390
0391
0392
0393 virtual double max_cross_section() const { return -1; }
0394 virtual double phase_space() const { return -1; }
0395
0396
0397 void update_volume() {
0398 volume_ = initial_ps_ * initial_max_ * proc_volume_ * penalty_;
0399 }
0400
0401
0402
0403
0404 void init_process_list() {
0405 LOG_INFO("event_generator", "Initializing the process list");
0406 for (int i = 0; i < N_MAX_PROC; ++i) {
0407 const string_path path{PROC_KEY + std::to_string(i)};
0408
0409 const configuration& cf = conf();
0410
0411 auto type = cf.get_optional<std::string>(path / "type");
0412 if (type) {
0413 LOG_DEBUG(path.str(),
0414 "Creating a new process sub-generator (" + *type + ")");
0415 process_list_.push_back(
0416 {i, FACTORY_CREATE(process_type, cf, path, this->rng())});
0417 LOG_DEBUG(path.str(), "Cross section max: " +
0418 std::to_string(process_list_.back().max));
0419 LOG_DEBUG(path.str(),
0420 "Phase space: " + std::to_string(process_list_.back().ps));
0421
0422 const double volume = process_list_.back().vol;
0423 if (volume > proc_volume_) {
0424 LOG_DEBUG("event_generator",
0425 "Updating maximum process generation volume: " +
0426 std::to_string(volume));
0427 proc_volume_ = volume;
0428 update_volume();
0429 }
0430 } else {
0431 LOG_JUNK(path.str(), "Not requested");
0432 }
0433 }
0434 tassert(process_list_.size() > 0,
0435 "At least one process has to be specified");
0436 }
0437
0438
0439
0440 void init_lumi(const configuration& cf) {
0441 auto lumi = cf.get_optional<double>("lumi");
0442 if (lumi) {
0443 lumi_ = *lumi * 1e6;
0444 n_requested_ = -1;
0445 } else {
0446 n_requested_ = cf.get<int>("events");
0447 lumi_ = -1;
0448 }
0449 }
0450
0451 struct process_info {
0452 const int id;
0453 const std::string name;
0454 double ps{0};
0455 double max{0};
0456 double vol{0};
0457 double n_events{0};
0458 std::shared_ptr<process_type> gen;
0459 process_info(const int id, std::shared_ptr<process_type> g)
0460 : id{id}
0461 , name{process_id(id)}
0462 , ps{g->phase_space()}
0463 , max{g->max_cross_section()}
0464 , vol{max * ps}
0465 , gen{g} {}
0466 };
0467
0468
0469 const double penalty_;
0470
0471
0472 double initial_ps_{1.};
0473 double initial_max_{1.};
0474 double proc_volume_{-1.};
0475 double volume_{1.};
0476
0477 double n_trials_{0.};
0478 double n_events_{0};
0479 double n_gen_events_{1};
0480 double branching_ratio_{1.};
0481 std::vector<process_info> process_list_;
0482
0483 int64_t n_requested_{-1};
0484 double lumi_{-1};
0485 };
0486
0487 }
0488 #endif