Version: SMASH-3.1
experiment.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2013-2024
3  * SMASH Team
4  *
5  * GNU General Public License (GPLv3 or later)
6  */
7 #ifndef SRC_INCLUDE_SMASH_EXPERIMENT_H_
8 #define SRC_INCLUDE_SMASH_EXPERIMENT_H_
9 
10 #include <algorithm>
11 #include <limits>
12 #include <memory>
13 #include <set>
14 #include <string>
15 #include <utility>
16 #include <vector>
17 
18 #include "actionfinderfactory.h"
19 #include "actions.h"
20 #include "bremsstrahlungaction.h"
21 #include "chrono.h"
22 #include "decayactionsfinder.h"
24 #include "energymomentumtensor.h"
25 #include "fields.h"
26 #include "fourvector.h"
27 #include "grandcan_thermalizer.h"
28 #include "grid.h"
30 #include "outputparameters.h"
31 #include "pauliblocking.h"
32 #include "potential_globals.h"
33 #include "potentials.h"
34 #include "propagation.h"
35 #include "quantumnumbers.h"
36 #include "scatteractionphoton.h"
37 #include "scatteractionsfinder.h"
38 #include "stringprocess.h"
39 #include "thermalizationaction.h"
40 // Output
41 #include "binaryoutput.h"
42 #ifdef SMASH_USE_HEPMC
43 #include "hepmcoutput.h"
44 #endif
45 #ifdef SMASH_USE_RIVET
46 #include "rivetoutput.h"
47 #endif
48 #include "icoutput.h"
49 #include "oscaroutput.h"
51 #include "thermodynamicoutput.h"
52 #ifdef SMASH_USE_ROOT
53 #include "rootoutput.h"
54 #endif
55 #include "freeforallaction.h"
56 #include "vtkoutput.h"
57 #include "wallcrossingaction.h"
58 
59 namespace std {
70 template <typename T, typename Ratio>
71 static ostream &operator<<(ostream &out,
72  const chrono::duration<T, Ratio> &seconds) {
73  using Seconds = chrono::duration<double>;
74  using Minutes = chrono::duration<double, std::ratio<60>>;
75  using Hours = chrono::duration<double, std::ratio<60 * 60>>;
76  constexpr Minutes threshold_for_minutes{10};
77  constexpr Hours threshold_for_hours{3};
78  if (seconds < threshold_for_minutes) {
79  return out << Seconds(seconds).count() << " [s]";
80  }
81  if (seconds < threshold_for_hours) {
82  return out << Minutes(seconds).count() << " [min]";
83  }
84  return out << Hours(seconds).count() << " [h]";
85 }
86 } // namespace std
87 
88 namespace smash {
89 static constexpr int LMain = LogArea::Main::id;
90 static constexpr int LInitialConditions = LogArea::InitialConditions::id;
91 
100  public:
101  ExperimentBase() = default;
106  virtual ~ExperimentBase() = default;
107 
128  static std::unique_ptr<ExperimentBase> create(
129  Configuration &config, const std::filesystem::path &output_path);
130 
137  virtual void run() = 0;
138 
144  struct InvalidModusRequest : public std::invalid_argument {
145  using std::invalid_argument::invalid_argument;
146  };
147 
153  struct NonExistingOutputPathRequest : public std::invalid_argument {
154  using std::invalid_argument::invalid_argument;
155  };
156 };
157 
158 template <typename Modus>
159 class Experiment;
160 template <typename Modus>
161 std::ostream &operator<<(std::ostream &out, const Experiment<Modus> &e);
162 
187 template <typename Modus>
188 class Experiment : public ExperimentBase {
189  friend class ExperimentBase;
190 
191  public:
198  void run() override;
199 
213  explicit Experiment(Configuration &config,
214  const std::filesystem::path &output_path);
215 
221  void initialize_new_event();
222 
240  void run_time_evolution(const double t_end, ParticleList &&add_plist = {},
241  ParticleList &&remove_plist = {});
242 
248  void do_final_decays();
249 
251  void final_output();
252 
259  std::vector<Particles> *all_ensembles() { return &ensembles_; }
260 
265  Modus *modus() { return &modus_; }
266 
271  void increase_event_number();
272 
273  private:
286  bool perform_action(Action &action, int i_ensemble,
287  bool include_pauli_blocking = true);
296  void create_output(const std::string &format, const std::string &content,
297  const std::filesystem::path &output_path,
298  const OutputParameters &par);
299 
307  void propagate_and_shine(double to_time, Particles &particles);
308 
321  void run_time_evolution_timestepless(Actions &actions, int i_ensemble,
322  const double end_time_propagation);
323 
325  void intermediate_output();
326 
328  void update_potentials();
329 
338  double compute_min_cell_length(double dt) const {
341  }
342  return std::sqrt(4 * dt * dt + max_transverse_distance_sqr_);
343  }
344 
346  double next_output_time() const {
347  return parameters_.outputclock->next_time();
348  }
349 
355 
361  bool is_finished();
362 
369 
372 
377  Modus modus_;
378 
380  std::vector<Particles> ensembles_;
381 
386  std::unique_ptr<Potentials> potentials_;
387 
392  std::unique_ptr<PauliBlocker> pauli_blocker_;
393 
398  OutputsList outputs_;
399 
401  OutputPtr dilepton_output_;
402 
404  OutputPtr photon_output_;
405 
410  std::vector<bool> projectile_target_interact_;
411 
417  std::vector<FourVector> beam_momentum_ = {};
418 
420  std::vector<std::unique_ptr<ActionFinderInterface>> action_finders_;
421 
423  std::unique_ptr<DecayActionsFinderDilepton> dilepton_finder_;
424 
426  std::unique_ptr<ActionFinderInterface> photon_finder_;
427 
430 
432  std::unique_ptr<DensityLattice> j_QBS_lat_;
433 
435  std::unique_ptr<DensityLattice> jmu_B_lat_;
436 
438  std::unique_ptr<DensityLattice> jmu_I3_lat_;
439 
441  std::unique_ptr<DensityLattice> jmu_el_lat_;
442 
444  std::unique_ptr<FieldsLattice> fields_lat_;
445 
453  std::unique_ptr<DensityLattice> jmu_custom_lat_;
454 
457 
462  std::unique_ptr<RectangularLattice<FourVector>> UB_lat_ = nullptr;
463 
468  std::unique_ptr<RectangularLattice<FourVector>> UI3_lat_ = nullptr;
469 
474  std::unique_ptr<RectangularLattice<std::pair<ThreeVector, ThreeVector>>>
476 
478  std::unique_ptr<RectangularLattice<std::pair<ThreeVector, ThreeVector>>>
480 
482  std::unique_ptr<RectangularLattice<std::pair<ThreeVector, ThreeVector>>>
484 
486  std::unique_ptr<RectangularLattice<EnergyMomentumTensor>> Tmn_;
487 
489  std::unique_ptr<RectangularLattice<FourVector>> old_jmu_auxiliary_;
491  std::unique_ptr<RectangularLattice<FourVector>> new_jmu_auxiliary_;
493  std::unique_ptr<RectangularLattice<std::array<FourVector, 4>>>
495 
497  std::unique_ptr<RectangularLattice<FourVector>> old_fields_auxiliary_;
499  std::unique_ptr<RectangularLattice<FourVector>> new_fields_auxiliary_;
501  std::unique_ptr<RectangularLattice<std::array<FourVector, 4>>>
503 
505  bool printout_rho_eckart_ = false;
506 
508  bool printout_tmn_ = false;
509 
511  bool printout_tmn_landau_ = false;
512 
514  bool printout_v_landau_ = false;
515 
517  bool printout_j_QBS_ = false;
518 
520  bool printout_lattice_td_ = false;
521 
525 
527  std::unique_ptr<GrandCanThermalizer> thermalizer_;
528 
534 
549  const int nevents_ = 0;
550 
560 
568 
570  int event_ = 0;
571 
574 
579  int max_events_ = 0;
580 
582  const double end_time_;
583 
589  const double delta_time_startup_;
590 
595  const bool force_decays_;
596 
598  const bool use_grid_;
599 
602 
604  const bool dileptons_switch_;
605 
607  const bool photons_switch_;
608 
611 
613  const bool IC_output_switch_;
614 
617 
622  double max_transverse_distance_sqr_ = std::numeric_limits<double>::max();
623 
632 
638 
640  SystemTimePoint time_start_ = SystemClock::now();
641 
644 
649  uint64_t interactions_total_ = 0;
650 
656 
661  uint64_t wall_actions_total_ = 0;
662 
668 
673  uint64_t total_pauli_blocked_ = 0;
674 
680 
686 
690  double total_energy_removed_ = 0.0;
691 
696 
699 
701  int64_t seed_ = -1;
702 
708  friend std::ostream &operator<<<>(std::ostream &out, const Experiment &e);
709 };
710 
712 template <typename Modus>
713 std::ostream &operator<<(std::ostream &out, const Experiment<Modus> &e) {
714  out << "End time: " << e.end_time_ << " fm\n";
715  out << e.modus_;
716  return out;
717 }
718 
719 template <typename Modus>
720 void Experiment<Modus>::create_output(const std::string &format,
721  const std::string &content,
722  const std::filesystem::path &output_path,
723  const OutputParameters &out_par) {
724  logg[LExperiment].info() << "Adding output " << content << " of format "
725  << format << std::endl;
726 
727  if (format == "VTK" && content == "Particles") {
728  outputs_.emplace_back(
729  std::make_unique<VtkOutput>(output_path, content, out_par));
730  } else if (format == "Root") {
731 #ifdef SMASH_USE_ROOT
732  if (content == "Initial_Conditions") {
733  outputs_.emplace_back(
734  std::make_unique<RootOutput>(output_path, "SMASH_IC", out_par));
735  } else {
736  outputs_.emplace_back(
737  std::make_unique<RootOutput>(output_path, content, out_par));
738  }
739 #else
740  logg[LExperiment].error(
741  "Root output requested, but Root support not compiled in");
742 #endif
743  } else if (format == "Binary") {
744  if (content == "Collisions" || content == "Dileptons" ||
745  content == "Photons") {
746  outputs_.emplace_back(std::make_unique<BinaryOutputCollisions>(
747  output_path, content, out_par));
748  } else if (content == "Particles") {
749  outputs_.emplace_back(std::make_unique<BinaryOutputParticles>(
750  output_path, content, out_par));
751  } else if (content == "Initial_Conditions") {
752  outputs_.emplace_back(std::make_unique<BinaryOutputInitialConditions>(
753  output_path, content, out_par));
754  }
755  } else if (format == "Oscar1999" || format == "Oscar2013") {
756  outputs_.emplace_back(
757  create_oscar_output(format, content, output_path, out_par));
758  } else if (content == "Thermodynamics" && format == "ASCII") {
759  outputs_.emplace_back(
760  std::make_unique<ThermodynamicOutput>(output_path, content, out_par));
761  } else if (content == "Thermodynamics" &&
762  (format == "Lattice_ASCII" || format == "Lattice_Binary")) {
763  printout_full_lattice_any_td_ = true;
764  outputs_.emplace_back(std::make_unique<ThermodynamicLatticeOutput>(
765  output_path, content, out_par, format == "Lattice_ASCII",
766  format == "Lattice_Binary"));
767  } else if (content == "Thermodynamics" && format == "VTK") {
768  printout_lattice_td_ = true;
769  outputs_.emplace_back(
770  std::make_unique<VtkOutput>(output_path, content, out_par));
771  } else if (content == "Initial_Conditions" && format == "ASCII") {
772  outputs_.emplace_back(
773  std::make_unique<ICOutput>(output_path, "SMASH_IC", out_par));
774  } else if ((format == "HepMC") || (format == "HepMC_asciiv3") ||
775  (format == "HepMC_treeroot")) {
776 #ifdef SMASH_USE_HEPMC
777  if (content == "Particles") {
778  if ((format == "HepMC") || (format == "HepMC_asciiv3")) {
779  outputs_.emplace_back(std::make_unique<HepMcOutput>(
780  output_path, "SMASH_HepMC_particles", false, "asciiv3"));
781  } else if (format == "HepMC_treeroot") {
782 #ifdef SMASH_USE_HEPMC_ROOTIO
783  outputs_.emplace_back(std::make_unique<HepMcOutput>(
784  output_path, "SMASH_HepMC_particles", false, "root"));
785 #else
786  logg[LExperiment].error(
787  "Requested HepMC_treeroot output not available, "
788  "ROOT or HepMC3 ROOTIO missing or not found by cmake.");
789 #endif
790  }
791  } else if (content == "Collisions") {
792  if ((format == "HepMC") || (format == "HepMC_asciiv3")) {
793  outputs_.emplace_back(std::make_unique<HepMcOutput>(
794  output_path, "SMASH_HepMC_collisions", true, "asciiv3"));
795  } else if (format == "HepMC_treeroot") {
796 #ifdef SMASH_USE_HEPMC_ROOTIO
797  outputs_.emplace_back(std::make_unique<HepMcOutput>(
798  output_path, "SMASH_HepMC_collisions", true, "root"));
799 #else
800  logg[LExperiment].error(
801  "Requested HepMC_treeroot output not available, "
802  "ROOT or HepMC3 ROOTIO missing or not found by cmake.");
803 #endif
804  }
805  } else {
806  logg[LExperiment].error(
807  "HepMC only available for Particles and "
808  "Collisions content. Requested for " +
809  content + ".");
810  }
811 #else
812  logg[LExperiment].error(
813  "HepMC output requested, but HepMC support not compiled in");
814 #endif
815  } else if (content == "Coulomb" && format == "VTK") {
816  outputs_.emplace_back(
817  std::make_unique<VtkOutput>(output_path, "Fields", out_par));
818  } else if (content == "Rivet") {
819 #ifdef SMASH_USE_RIVET
820  // flag to ensure that the Rivet format has not been already assigned
821  static bool rivet_format_already_selected = false;
822  // if the next check is true, then we are trying to assign the format twice
823  if (rivet_format_already_selected) {
824  logg[LExperiment].warn(
825  "Rivet output format can only be one, either YODA or YODA-full. "
826  "Only your first valid choice will be used.");
827  return;
828  }
829  if (format == "YODA") {
830  outputs_.emplace_back(std::make_unique<RivetOutput>(
831  output_path, "SMASH_Rivet", false, out_par.rivet_parameters));
832  rivet_format_already_selected = true;
833  } else if (format == "YODA-full") {
834  outputs_.emplace_back(std::make_unique<RivetOutput>(
835  output_path, "SMASH_Rivet_full", true, out_par.rivet_parameters));
836  rivet_format_already_selected = true;
837  } else {
838  logg[LExperiment].error("Rivet format " + format +
839  "not one of YODA or YODA-full");
840  }
841 #else
842  logg[LExperiment].error(
843  "Rivet output requested, but Rivet support not compiled in");
844 #endif
845  } else {
846  logg[LExperiment].error()
847  << "Unknown combination of format (" << format << ") and content ("
848  << content << "). Fix the config.";
849  }
850 }
851 
860 
861 template <typename Modus>
863  const std::filesystem::path &output_path)
864  : parameters_(create_experiment_parameters(config)),
865  density_param_(DensityParameters(parameters_)),
866  modus_(std::invoke([&]() {
867  /* This immediately invoked lambda is a work-around to cope with the
868  * fact that the "Collisions_Within_Nucleus" key belongs to the
869  * "Collider" section, but is used by the ScatterActionsFinder through
870  * the ScatterActionsFinderParameters member. Here that key is taken
871  * from the main configuration and put there back after the "Collider"
872  * section is extracted. If this were not done in this way, the
873  * sub-configuration given to ColliderModus would be deleted at the end
874  * of its constructor not empty and this would throw an exception.*/
875  const std::initializer_list<const char *> key_labels = {
876  "Modi", "Collider", "Collisions_Within_Nucleus"};
877  const bool restore_key = config.has_value(key_labels);
878  const bool temporary_taken_key = config.take(key_labels, false);
879  auto modus_config = config.extract_sub_configuration({"Modi"});
880  if (restore_key) {
881  config.set_value(key_labels, temporary_taken_key);
882  }
883  return Modus{std::move(modus_config), parameters_};
884  })),
885  ensembles_(parameters_.n_ensembles),
886  nevents_(config.take({"General", "Nevents"}, 0)),
887  end_time_(config.take({"General", "End_Time"})),
888  delta_time_startup_(parameters_.labclock->timestep_duration()),
889  force_decays_(
890  config.take({"Collision_Term", "Force_Decays_At_End"}, true)),
891  use_grid_(config.take({"General", "Use_Grid"}, true)),
892  metric_(
893  config.take({"General", "Metric_Type"}, ExpansionMode::NoExpansion),
894  config.take({"General", "Expansion_Rate"}, 0.1)),
895  dileptons_switch_(
896  config.take({"Collision_Term", "Dileptons", "Decays"}, false)),
897  photons_switch_(config.take(
898  {"Collision_Term", "Photons", "2to2_Scatterings"}, false)),
899  bremsstrahlung_switch_(
900  config.take({"Collision_Term", "Photons", "Bremsstrahlung"}, false)),
901  IC_output_switch_(config.has_value({"Output", "Initial_Conditions"})),
902  time_step_mode_(
903  config.take({"General", "Time_Step_Mode"}, TimeStepMode::Fixed)) {
904  logg[LExperiment].info() << *this;
905 
906  if (config.has_value({"General", "Minimum_Nonempty_Ensembles"})) {
907  if (config.has_value({"General", "Nevents"})) {
908  throw std::invalid_argument(
909  "Please specify either Nevents or Minimum_Nonempty_Ensembles.");
910  }
911  event_counting_ = EventCounting::MinimumNonEmpty;
912  minimum_nonempty_ensembles_ =
913  config.take({"General", "Minimum_Nonempty_Ensembles", "Number"});
914  int max_ensembles = config.take(
915  {"General", "Minimum_Nonempty_Ensembles", "Maximum_Ensembles_Run"});
916  max_events_ =
917  std::ceil(static_cast<double>(max_ensembles) / parameters_.n_ensembles);
918  } else {
919  event_counting_ = EventCounting::FixedNumber;
920  }
921 
922  // covariant derivatives can only be done with covariant smearing
923  if (parameters_.derivatives_mode == DerivativesMode::CovariantGaussian &&
924  parameters_.smearing_mode != SmearingMode::CovariantGaussian) {
925  throw std::invalid_argument(
926  "Covariant Gaussian derivatives only make sense for Covariant Gaussian "
927  "smearing!");
928  }
929 
930  // for triangular smearing:
931  // the weight needs to be larger than 1./7. for the center cell to contribute
932  // more than the surrounding cells
933  if (parameters_.smearing_mode == SmearingMode::Discrete &&
934  parameters_.discrete_weight < (1. / 7.)) {
935  throw std::invalid_argument(
936  "The central weight for discrete smearing should be >= 1./7.");
937  }
938 
939  if (parameters_.coll_crit == CollisionCriterion::Stochastic &&
940  (time_step_mode_ != TimeStepMode::Fixed || !use_grid_)) {
941  throw std::invalid_argument(
942  "The stochastic criterion can only be employed for fixed time step "
943  "mode and with a grid!");
944  }
945 
946  if (modus_.is_box() && (time_step_mode_ != TimeStepMode::Fixed)) {
947  throw std::invalid_argument(
948  "The box modus can only be used with the fixed time step mode!");
949  }
950 
951  logg[LExperiment].info("Using ", parameters_.testparticles,
952  " testparticles per particle.");
953  logg[LExperiment].info("Using ", parameters_.n_ensembles,
954  " parallel ensembles.");
955 
956  if (modus_.is_box() &&
957  config.read({"Collision_Term", "Total_Cross_Section_Strategy"},
960  logg[LExperiment].warn(
961  "To preserve detailed balance in a box simulation, it is recommended "
962  "to use the bottom-up strategy for evaluating total cross sections.\n"
963  "Consider adding the following line to the 'Collision_Term' section "
964  "in your configuration file:\n"
965  " Total_Cross_Section_Strategy: \"BottomUp\"");
966  }
967  if (modus_.is_box() &&
968  config.read({"Collision_Term", "Pseudoresonance"},
971  logg[LExperiment].warn(
972  "To preserve detailed balance in a box simulation, it is recommended "
973  "to not include the pseudoresonances,\nas they artificially increase "
974  "the resonance production without changing the corresponding "
975  "decay.\nConsider adding the following line to the 'Collision_Term' "
976  "section in your configuration file:\n Pseudoresonance: \"None\"");
977  }
978 
979  /* In collider setup with sqrts >= 200 GeV particles don't form continuously
980  *
981  * NOTE: This key has to be taken before the ScatterActionsFinder is created
982  * because there the "String_Parameters" is extracted as sub-config and
983  * all parameters but this one are taken. If this one is still there
984  * the configuration temporary object will be destroyed not empty, hence
985  * throwing an exception.
986  */
988  {"Collision_Term", "String_Parameters", "Power_Particle_Formation"},
989  modus_.sqrt_s_NN() >= 200. ? -1. : 1.);
990 
991  // create finders
992  if (dileptons_switch_) {
993  dilepton_finder_ = std::make_unique<DecayActionsFinderDilepton>();
994  }
995  if (photons_switch_ || bremsstrahlung_switch_) {
996  n_fractional_photons_ =
997  config.take({"Collision_Term", "Photons", "Fractional_Photons"}, 100);
998  }
999  if (parameters_.two_to_one) {
1000  if (parameters_.res_lifetime_factor < 0.) {
1001  throw std::invalid_argument(
1002  "Resonance lifetime modifier cannot be negative!");
1003  }
1004  if (parameters_.res_lifetime_factor < really_small) {
1005  logg[LExperiment].warn(
1006  "Resonance lifetime set to zero. Make sure resonances cannot "
1007  "interact",
1008  "inelastically (e.g. resonance chains), else SMASH is known to "
1009  "hang.");
1010  }
1011  action_finders_.emplace_back(std::make_unique<DecayActionsFinder>(
1012  parameters_.res_lifetime_factor, parameters_.do_weak_decays));
1013  }
1014  bool no_coll = config.take({"Collision_Term", "No_Collisions"}, false);
1015  if ((parameters_.two_to_one || parameters_.included_2to2.any() ||
1016  parameters_.included_multi.any() || parameters_.strings_switch) &&
1017  !no_coll) {
1018  parameters_.use_monash_tune_default =
1019  (modus_.is_collider() && modus_.sqrt_s_NN() >= 200.);
1020  auto scat_finder =
1021  std::make_unique<ScatterActionsFinder>(config, parameters_);
1022  max_transverse_distance_sqr_ =
1023  scat_finder->max_transverse_distance_sqr(parameters_.testparticles);
1024  process_string_ptr_ = scat_finder->get_process_string_ptr();
1025  action_finders_.emplace_back(std::move(scat_finder));
1026  } else {
1027  max_transverse_distance_sqr_ =
1028  parameters_.maximum_cross_section / M_PI * fm2_mb;
1029  process_string_ptr_ = NULL;
1030  }
1031  if (modus_.is_box()) {
1032  action_finders_.emplace_back(
1033  std::make_unique<WallCrossActionsFinder>(parameters_.box_length));
1034  }
1035  if (IC_output_switch_) {
1036  if (!modus_.is_collider()) {
1037  throw std::runtime_error(
1038  "Initial conditions can only be extracted in collider modus.");
1039  }
1040  double proper_time;
1041  if (config.has_value({"Output", "Initial_Conditions", "Proper_Time"})) {
1042  // Read in proper time from config
1043  proper_time =
1044  config.take({"Output", "Initial_Conditions", "Proper_Time"});
1045  } else {
1046  // Default proper time is the passing time of the two nuclei
1047  double default_proper_time = modus_.nuclei_passing_time();
1048  double lower_bound =
1049  config.take({"Output", "Initial_Conditions", "Lower_Bound"}, 0.5);
1050  if (default_proper_time >= lower_bound) {
1051  proper_time = default_proper_time;
1052  } else {
1053  logg[LInitialConditions].warn()
1054  << "Nuclei passing time is too short, hypersurface proper time set "
1055  "to tau = "
1056  << lower_bound << " fm.";
1057  proper_time = lower_bound;
1058  }
1059  }
1060 
1061  double rapidity_cut = 0.0;
1062  if (config.has_value({"Output", "Initial_Conditions", "Rapidity_Cut"})) {
1063  rapidity_cut =
1064  config.take({"Output", "Initial_Conditions", "Rapidity_Cut"});
1065  if (rapidity_cut <= 0.0) {
1066  logg[LInitialConditions].fatal()
1067  << "Rapidity cut for initial conditions configured as abs(y) < "
1068  << rapidity_cut << " is unreasonable. \nPlease choose a positive, "
1069  << "non-zero value or employ SMASH without rapidity cut.";
1070  throw std::runtime_error(
1071  "Kinematic cut for initial conditions malconfigured.");
1072  }
1073  }
1074 
1075  if (modus_.calculation_frame_is_fixed_target() && rapidity_cut != 0.0) {
1076  throw std::runtime_error(
1077  "Rapidity cut for initial conditions output is not implemented "
1078  "in the fixed target calculation frame. \nPlease use "
1079  "\"center of velocity\" or \"center of mass\" as a "
1080  "\"Calculation_Frame\" instead.");
1081  }
1082 
1083  double transverse_momentum_cut = 0.0;
1084  if (config.has_value({"Output", "Initial_Conditions", "pT_Cut"})) {
1085  transverse_momentum_cut =
1086  config.take({"Output", "Initial_Conditions", "pT_Cut"});
1087  if (transverse_momentum_cut <= 0.0) {
1088  logg[LInitialConditions].fatal()
1089  << "transverse momentum cut for initial conditions configured as "
1090  "pT < "
1091  << rapidity_cut << " is unreasonable. \nPlease choose a positive, "
1092  << "non-zero value or employ SMASH without pT cut.";
1093  throw std::runtime_error(
1094  "Kinematic cut for initial conditions misconfigured.");
1095  }
1096  }
1097 
1098  if (rapidity_cut > 0.0 || transverse_momentum_cut > 0.0) {
1099  kinematic_cuts_for_IC_output_ = true;
1100  }
1101 
1102  if (rapidity_cut > 0.0 && transverse_momentum_cut > 0.0) {
1103  logg[LInitialConditions].info()
1104  << "Extracting initial conditions in kinematic range: "
1105  << -rapidity_cut << " <= y <= " << rapidity_cut
1106  << "; pT <= " << transverse_momentum_cut << " GeV.";
1107  } else if (rapidity_cut > 0.0) {
1108  logg[LInitialConditions].info()
1109  << "Extracting initial conditions in kinematic range: "
1110  << -rapidity_cut << " <= y <= " << rapidity_cut << ".";
1111  } else if (transverse_momentum_cut > 0.0) {
1112  logg[LInitialConditions].info()
1113  << "Extracting initial conditions in kinematic range: pT <= "
1114  << transverse_momentum_cut << " GeV.";
1115  } else {
1116  logg[LInitialConditions].info()
1117  << "Extracting initial conditions without kinematic cuts.";
1118  }
1119 
1120  action_finders_.emplace_back(
1121  std::make_unique<HyperSurfaceCrossActionsFinder>(
1122  proper_time, rapidity_cut, transverse_momentum_cut));
1123  }
1124 
1125  if (config.has_value({"Collision_Term", "Pauli_Blocking"})) {
1126  logg[LExperiment].info() << "Pauli blocking is ON.";
1127  pauli_blocker_ = std::make_unique<PauliBlocker>(
1128  config.extract_sub_configuration({"Collision_Term", "Pauli_Blocking"}),
1129  parameters_);
1130  }
1131 
1371  // create outputs
1373  " create OutputInterface objects");
1374  dens_type_ = config.take({"Output", "Density_Type"}, DensityType::None);
1375  logg[LExperiment].debug()
1376  << "Density type printed to headers: " << dens_type_;
1377 
1378  /* Parse configuration about output contents and formats, doing all logical
1379  * checks about specified formats, creating all needed output objects. */
1380  auto output_conf = config.extract_sub_configuration(
1381  {"Output"}, Configuration::GetEmpty::Yes);
1382  if (output_path == "") {
1383  throw std::invalid_argument(
1384  "Invalid empty output path provided to Experiment constructor.");
1385  } else if (!std::filesystem::exists(output_path)) {
1386  logg[LExperiment].fatal(
1387  "Output path \"" + output_path.string() +
1388  "\" used to create an Experiment object does not exist.");
1389  throw NonExistingOutputPathRequest("Attempt to use not existing path.");
1390  } else if (!std::filesystem::is_directory(output_path)) {
1391  logg[LExperiment].fatal("Output path \"" + output_path.string() +
1392  "\" used to create an Experiment object "
1393  "exists, but it is not a directory.");
1394  throw std::logic_error("Attempt to use invalid existing path.");
1395  }
1396  if (output_conf.is_empty()) {
1397  logg[LExperiment].warn() << "No \"Output\" section found in the input "
1398  "file. No output file will be produced.";
1399  }
1400  const std::vector<std::string> output_contents =
1401  output_conf.list_upmost_nodes();
1402  std::vector<std::vector<std::string>> list_of_formats(output_contents.size());
1403  std::transform(
1404  output_contents.cbegin(), output_contents.cend(), list_of_formats.begin(),
1405  [&output_conf](std::string content) -> std::vector<std::string> {
1406  /* Use here a default value for "Format" even though it is a required
1407  * key, just because then here below the error for the user is more
1408  * informative, if the key was not given in the input file. */
1409  return output_conf.take({content.c_str(), "Format"},
1410  std::vector<std::string>{});
1411  });
1412  const OutputParameters output_parameters(std::move(output_conf));
1413  std::size_t total_number_of_requested_formats = 0;
1414  auto abort_because_of_invalid_input_file = []() {
1415  throw std::invalid_argument("Invalid configuration input file.");
1416  };
1417  for (std::size_t i = 0; i < output_contents.size(); ++i) {
1418  if (list_of_formats[i].empty()) {
1419  logg[LExperiment].fatal()
1420  << "Empty or unspecified list of formats for "
1421  << std::quoted(output_contents[i]) << " content.";
1422  abort_because_of_invalid_input_file();
1423  } else if (std::find(list_of_formats[i].begin(), list_of_formats[i].end(),
1424  "None") != list_of_formats[i].end()) {
1425  if (list_of_formats[i].size() > 1) {
1426  logg[LExperiment].fatal()
1427  << "Use of \"None\" output format together with other formats is "
1428  "not allowed.\nInvalid \"Format\" key for "
1429  << std::quoted(output_contents[i]) << " content.";
1430  abort_because_of_invalid_input_file();
1431  } else {
1432  // Clear vector so that the for below is skipped and no output created
1433  list_of_formats[i].clear();
1434  }
1435  } else if (std::set<std::string> tmp_set(list_of_formats[i].begin(),
1436  list_of_formats[i].end());
1437  list_of_formats[i].size() != tmp_set.size()) {
1438  auto join_container = [](const auto &container) {
1439  std::string result{};
1440  std::for_each(container.cbegin(), container.cend(),
1441  [&result](const std::string s) {
1442  result += (result == "") ? s : ", " + s;
1443  });
1444  return result;
1445  };
1446  const std::string old_formats = join_container(list_of_formats[i]),
1447  new_formats = join_container(tmp_set);
1448  logg[LExperiment].warn()
1449  << "Found the same output format multiple times for "
1450  << std::quoted(output_contents[i])
1451  << " content. Duplicates will be ignored:\n 'Format: [" << old_formats
1452  << "] -> [" << new_formats << "]'";
1453  list_of_formats[i].assign(tmp_set.begin(), tmp_set.end());
1454  }
1455  for (const auto &format : list_of_formats[i]) {
1456  create_output(format, output_contents[i], output_path, output_parameters);
1457  ++total_number_of_requested_formats;
1458  }
1459  }
1460  if (outputs_.size() != total_number_of_requested_formats) {
1461  logg[LExperiment].fatal()
1462  << "At least one invalid output format has been provided.";
1463  abort_because_of_invalid_input_file();
1464  }
1465 
1466  /* We can take away the Fermi motion flag, because the collider modus is
1467  * already initialized. We only need it when potentials are enabled, but we
1468  * always have to take it, otherwise SMASH will complain about unused
1469  * options. We have to provide a default value for modi other than Collider.
1470  */
1471  if (config.has_value({"Potentials"})) {
1472  if (time_step_mode_ == TimeStepMode::None) {
1473  logg[LExperiment].error() << "Potentials only work with time steps!";
1474  throw std::invalid_argument("Can't use potentials without time steps!");
1475  }
1476  if (modus_.fermi_motion() == FermiMotion::Frozen) {
1477  logg[LExperiment].error()
1478  << "Potentials don't work with frozen Fermi momenta! "
1479  "Use normal Fermi motion instead.";
1480  throw std::invalid_argument(
1481  "Can't use potentials "
1482  "with frozen Fermi momenta!");
1483  }
1484  logg[LExperiment].info() << "Potentials are ON. Timestep is "
1485  << parameters_.labclock->timestep_duration();
1486  // potentials need density calculation parameters from parameters_
1487  potentials_ = std::make_unique<Potentials>(
1488  config.extract_sub_configuration({"Potentials"}), parameters_);
1489  // make sure that vdf potentials are not used together with Skyrme
1490  // or symmetry potentials
1491  if (potentials_->use_skyrme() && potentials_->use_vdf()) {
1492  throw std::runtime_error(
1493  "Can't use Skyrme and VDF potentials at the same time!");
1494  }
1495  if (potentials_->use_symmetry() && potentials_->use_vdf()) {
1496  throw std::runtime_error(
1497  "Can't use symmetry and VDF potentials at the same time!");
1498  }
1499  if (potentials_->use_skyrme()) {
1500  logg[LExperiment].info() << "Skyrme potentials are:\n";
1501  logg[LExperiment].info()
1502  << "\t\tSkyrme_A [MeV] = " << potentials_->skyrme_a() << "\n";
1503  logg[LExperiment].info()
1504  << "\t\tSkyrme_B [MeV] = " << potentials_->skyrme_b() << "\n";
1505  logg[LExperiment].info()
1506  << "\t\t Skyrme_tau = " << potentials_->skyrme_tau() << "\n";
1507  }
1508  if (potentials_->use_symmetry()) {
1509  logg[LExperiment].info()
1510  << "Symmetry potential is:"
1511  << "\n S_pot [MeV] = " << potentials_->symmetry_S_pot() << "\n";
1512  }
1513  if (potentials_->use_vdf()) {
1514  logg[LExperiment].info() << "VDF potential parameters are:\n";
1515  logg[LExperiment].info() << "\t\tsaturation density [fm^-3] = "
1516  << potentials_->saturation_density() << "\n";
1517  for (int i = 0; i < potentials_->number_of_terms(); i++) {
1518  logg[LExperiment].info()
1519  << "\t\tCoefficient_" << i + 1 << " = "
1520  << 1000.0 * (potentials_->coeffs())[i] << " [MeV] \t Power_"
1521  << i + 1 << " = " << (potentials_->powers())[i] << "\n";
1522  }
1523  }
1524  // if potentials are on, derivatives need to be calculated
1525  if (parameters_.derivatives_mode == DerivativesMode::Off &&
1526  parameters_.field_derivatives_mode == FieldDerivativesMode::ChainRule) {
1527  throw std::invalid_argument(
1528  "Derivatives are necessary for running with potentials.\n"
1529  "Derivatives_Mode: \"Off\" only makes sense for "
1530  "Field_Derivatives_Mode: \"Direct\"!\nUse \"Covariant Gaussian\" or "
1531  "\"Finite difference\".");
1532  }
1533  // for computational efficiency, we want to turn off the derivatives of jmu
1534  // and the rest frame density derivatives if direct derivatives are used
1535  if (parameters_.field_derivatives_mode == FieldDerivativesMode::Direct) {
1536  parameters_.derivatives_mode = DerivativesMode::Off;
1537  parameters_.rho_derivatives_mode = RestFrameDensityDerivativesMode::Off;
1538  }
1539  switch (parameters_.derivatives_mode) {
1541  logg[LExperiment].info() << "Covariant Gaussian derivatives are ON";
1542  break;
1544  logg[LExperiment].info() << "Finite difference derivatives are ON";
1545  break;
1546  case DerivativesMode::Off:
1547  logg[LExperiment].info() << "Gradients of baryon current are OFF";
1548  break;
1549  }
1550  switch (parameters_.rho_derivatives_mode) {
1552  logg[LExperiment].info() << "Rest frame density derivatives are ON";
1553  break;
1555  logg[LExperiment].info() << "Rest frame density derivatives are OFF";
1556  break;
1557  }
1558  // direct or chain rule derivatives only make sense for the VDF potentials
1559  if (potentials_->use_vdf()) {
1560  switch (parameters_.field_derivatives_mode) {
1562  logg[LExperiment].info() << "Chain rule field derivatives are ON";
1563  break;
1565  logg[LExperiment].info() << "Direct field derivatives are ON";
1566  break;
1567  }
1568  }
1569  /*
1570  * Necessary safety checks
1571  */
1572  // VDF potentials need derivatives of rest frame density or fields
1573  if (potentials_->use_vdf() && (parameters_.rho_derivatives_mode ==
1575  parameters_.field_derivatives_mode ==
1577  throw std::runtime_error(
1578  "Can't use VDF potentials without rest frame density derivatives or "
1579  "direct field derivatives!");
1580  }
1581  // potentials require using gradients
1582  if (parameters_.derivatives_mode == DerivativesMode::Off &&
1583  parameters_.field_derivatives_mode == FieldDerivativesMode::ChainRule) {
1584  throw std::runtime_error(
1585  "Can't use potentials without gradients of baryon current (Skyrme, "
1586  "VDF)"
1587  " or direct field derivatives (VDF)!");
1588  }
1589  // direct field derivatives only make sense for the VDF potentials
1590  if (!(potentials_->use_vdf()) &&
1591  parameters_.field_derivatives_mode == FieldDerivativesMode::Direct) {
1592  throw std::invalid_argument(
1593  "Field_Derivatives_Mode: \"Direct\" only makes sense for the VDF "
1594  "potentials!\nUse Field_Derivatives_Mode: \"Chain Rule\" or comment "
1595  "this option out (Chain Rule is default)");
1596  }
1597  }
1598 
1599  // information about the type of smearing
1600  switch (parameters_.smearing_mode) {
1602  logg[LExperiment].info() << "Smearing type: Covariant Gaussian";
1603  break;
1605  logg[LExperiment].info() << "Smearing type: Discrete with weight = "
1606  << parameters_.discrete_weight;
1607  break;
1609  logg[LExperiment].info() << "Smearing type: Triangular with range = "
1610  << parameters_.triangular_range;
1611  break;
1612  }
1613 
1614  // Create lattices
1615  if (config.has_value({"Lattice"})) {
1616  bool automatic = config.take({"Lattice", "Automatic"}, false);
1617  bool all_geometrical_properties_specified =
1618  config.has_value({"Lattice", "Cell_Number"}) &&
1619  config.has_value({"Lattice", "Origin"}) &&
1620  config.has_value({"Lattice", "Sizes"});
1621  if (!automatic && !all_geometrical_properties_specified) {
1622  throw std::invalid_argument(
1623  "The lattice was requested to be manually generated, but some\n"
1624  "lattice geometrical property was not specified. Be sure to provide\n"
1625  "both \"Cell_Number\" and \"Origin\" and \"Sizes\".");
1626  }
1627  if (automatic && all_geometrical_properties_specified) {
1628  throw std::invalid_argument(
1629  "The lattice was requested to be automatically generated, but all\n"
1630  "lattice geometrical properties were specified. In this case you\n"
1631  "need to set \"Automatic: False\".");
1632  }
1633  bool periodic = config.take({"Lattice", "Periodic"}, modus_.is_box());
1634  const auto [l, n, origin] = [&config, automatic, this]() {
1635  if (!automatic) {
1636  return std::make_tuple<std::array<double, 3>, std::array<int, 3>,
1637  std::array<double, 3>>(
1638  config.take({"Lattice", "Sizes"}),
1639  config.take({"Lattice", "Cell_Number"}),
1640  config.take({"Lattice", "Origin"}));
1641  } else {
1642  std::array<double, 3> l_default{20., 20., 20.};
1643  std::array<int, 3> n_default{10, 10, 10};
1644  std::array<double, 3> origin_default{-20., -20., -20.};
1645  if (modus_.is_collider() || (modus_.is_list() && !modus_.is_box())) {
1646  // Estimates on how far particles could get in x, y, z. The
1647  // default lattice is currently not contracted for afterburner runs
1648  const double gam = modus_.is_collider()
1649  ? modus_.sqrt_s_NN() / (2.0 * nucleon_mass)
1650  : 1.0;
1651  const double max_z = 5.0 / gam + end_time_;
1652  const double estimated_max_transverse_velocity = 0.7;
1653  const double max_xy =
1654  5.0 + estimated_max_transverse_velocity * end_time_;
1655  origin_default = {-max_xy, -max_xy, -max_z};
1656  l_default = {2 * max_xy, 2 * max_xy, 2 * max_z};
1657  // Go for approximately 0.8 fm cell size and contract
1658  // lattice in z by gamma factor
1659  const int n_xy = std::ceil(2 * max_xy / 0.8);
1660  int nz = std::ceil(2 * max_z / 0.8);
1661  // Contract lattice by gamma factor in case of smearing where
1662  // smearing length is bound to the lattice cell length
1663  if (parameters_.smearing_mode == SmearingMode::Discrete ||
1664  parameters_.smearing_mode == SmearingMode::Triangular) {
1665  nz = static_cast<int>(std::ceil(2 * max_z / 0.8 * gam));
1666  }
1667  n_default = {n_xy, n_xy, nz};
1668  } else if (modus_.is_box()) {
1669  origin_default = {0., 0., 0.};
1670  const double bl = modus_.length();
1671  l_default = {bl, bl, bl};
1672  const int n_xyz = std::ceil(bl / 0.5);
1673  n_default = {n_xyz, n_xyz, n_xyz};
1674  } else if (modus_.is_sphere()) {
1675  // Maximal distance from (0, 0, 0) at which a particle
1676  // may be found at the end of the simulation
1677  const double max_d = modus_.radius() + end_time_;
1678  origin_default = {-max_d, -max_d, -max_d};
1679  l_default = {2 * max_d, 2 * max_d, 2 * max_d};
1680  // Go for approximately 0.8 fm cell size
1681  const int n_xyz = std::ceil(2 * max_d / 0.8);
1682  n_default = {n_xyz, n_xyz, n_xyz};
1683  }
1684  // Take lattice properties from config to assign them to all lattices
1685  return std::make_tuple<std::array<double, 3>, std::array<int, 3>,
1686  std::array<double, 3>>(
1687  config.take({"Lattice", "Sizes"}, l_default),
1688  config.take({"Lattice", "Cell_Number"}, n_default),
1689  config.take({"Lattice", "Origin"}, origin_default));
1690  }
1691  }();
1692 
1693  logg[LExperiment].info()
1694  << "Lattice is ON. Origin = (" << origin[0] << "," << origin[1] << ","
1695  << origin[2] << "), sizes = (" << l[0] << "," << l[1] << "," << l[2]
1696  << "), number of cells = (" << n[0] << "," << n[1] << "," << n[2]
1697  << "), periodic = " << std::boolalpha << periodic;
1698 
1699  if (printout_lattice_td_ || printout_full_lattice_any_td_) {
1700  dens_type_lattice_printout_ = output_parameters.td_dens_type;
1701  printout_rho_eckart_ = output_parameters.td_rho_eckart;
1702  printout_tmn_ = output_parameters.td_tmn;
1703  printout_tmn_landau_ = output_parameters.td_tmn_landau;
1704  printout_v_landau_ = output_parameters.td_v_landau;
1705  printout_j_QBS_ = output_parameters.td_jQBS;
1706  }
1707  if (printout_tmn_ || printout_tmn_landau_ || printout_v_landau_) {
1708  Tmn_ = std::make_unique<RectangularLattice<EnergyMomentumTensor>>(
1709  l, n, origin, periodic, LatticeUpdate::AtOutput);
1710  }
1711  if (printout_j_QBS_) {
1712  j_QBS_lat_ = std::make_unique<DensityLattice>(l, n, origin, periodic,
1713  LatticeUpdate::AtOutput);
1714  }
1715  /* Create baryon and isospin density lattices regardless of config
1716  if potentials are on. This is because they allow to compute
1717  potentials faster */
1718  if (potentials_) {
1719  // Create auxiliary lattices for baryon four-current calculation
1720  old_jmu_auxiliary_ = std::make_unique<RectangularLattice<FourVector>>(
1721  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1722  new_jmu_auxiliary_ = std::make_unique<RectangularLattice<FourVector>>(
1723  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1724  four_gradient_auxiliary_ =
1725  std::make_unique<RectangularLattice<std::array<FourVector, 4>>>(
1726  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1727 
1728  if (potentials_->use_skyrme()) {
1729  jmu_B_lat_ = std::make_unique<DensityLattice>(
1730  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1731  UB_lat_ = std::make_unique<RectangularLattice<FourVector>>(
1732  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1733  FB_lat_ = std::make_unique<
1734  RectangularLattice<std::pair<ThreeVector, ThreeVector>>>(
1735  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1736  }
1737  if (potentials_->use_symmetry()) {
1738  jmu_I3_lat_ = std::make_unique<DensityLattice>(
1739  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1740  UI3_lat_ = std::make_unique<RectangularLattice<FourVector>>(
1741  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1742  FI3_lat_ = std::make_unique<
1743  RectangularLattice<std::pair<ThreeVector, ThreeVector>>>(
1744  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1745  }
1746  if (potentials_->use_coulomb()) {
1747  jmu_el_lat_ = std::make_unique<DensityLattice>(
1748  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1749  EM_lat_ = std::make_unique<
1750  RectangularLattice<std::pair<ThreeVector, ThreeVector>>>(
1751  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1752  }
1753  if (potentials_->use_vdf()) {
1754  jmu_B_lat_ = std::make_unique<DensityLattice>(
1755  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1756  UB_lat_ = std::make_unique<RectangularLattice<FourVector>>(
1757  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1758  FB_lat_ = std::make_unique<
1759  RectangularLattice<std::pair<ThreeVector, ThreeVector>>>(
1760  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1761  }
1762  if (parameters_.field_derivatives_mode == FieldDerivativesMode::Direct) {
1763  // Create auxiliary lattices for field calculation
1764  old_fields_auxiliary_ =
1765  std::make_unique<RectangularLattice<FourVector>>(
1766  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1767  new_fields_auxiliary_ =
1768  std::make_unique<RectangularLattice<FourVector>>(
1769  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1770  fields_four_gradient_auxiliary_ =
1771  std::make_unique<RectangularLattice<std::array<FourVector, 4>>>(
1772  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1773 
1774  // Create the fields lattice
1775  fields_lat_ = std::make_unique<FieldsLattice>(
1776  l, n, origin, periodic, LatticeUpdate::EveryTimestep);
1777  }
1778  }
1779  if (dens_type_lattice_printout_ == DensityType::Baryon && !jmu_B_lat_) {
1780  jmu_B_lat_ = std::make_unique<DensityLattice>(l, n, origin, periodic,
1781  LatticeUpdate::AtOutput);
1782  }
1783  if (dens_type_lattice_printout_ == DensityType::BaryonicIsospin &&
1784  !jmu_I3_lat_) {
1785  jmu_I3_lat_ = std::make_unique<DensityLattice>(l, n, origin, periodic,
1786  LatticeUpdate::AtOutput);
1787  }
1788  if (dens_type_lattice_printout_ != DensityType::None &&
1789  dens_type_lattice_printout_ != DensityType::BaryonicIsospin &&
1790  dens_type_lattice_printout_ != DensityType::Baryon) {
1791  jmu_custom_lat_ = std::make_unique<DensityLattice>(
1792  l, n, origin, periodic, LatticeUpdate::AtOutput);
1793  }
1794  } else if (printout_lattice_td_ || printout_full_lattice_any_td_) {
1795  logg[LExperiment].error(
1796  "If you want Therm. VTK or Lattice output, configure a lattice for "
1797  "it.");
1798  } else if (potentials_ && potentials_->use_coulomb()) {
1799  logg[LExperiment].error(
1800  "Coulomb potential requires a lattice. Please add one to the "
1801  "configuration");
1802  }
1803 
1804  // Warning for the mean field calculation if lattice is not on.
1805  if ((potentials_ != nullptr) && (jmu_B_lat_ == nullptr)) {
1806  logg[LExperiment].warn() << "Lattice is NOT used. Mean-field energy is "
1807  << "not going to be calculated.";
1808  }
1809 
1810  // Store pointers to potential and lattice accessible for Action
1811  if (parameters_.potential_affect_threshold) {
1812  UB_lat_pointer = UB_lat_.get();
1813  UI3_lat_pointer = UI3_lat_.get();
1814  pot_pointer = potentials_.get();
1815  }
1816 
1817  // Throw fatal if DerivativesMode == FiniteDifference and lattice is not on.
1818  if ((parameters_.derivatives_mode == DerivativesMode::FiniteDifference) &&
1819  (jmu_B_lat_ == nullptr)) {
1820  throw std::runtime_error(
1821  "Lattice is necessary to calculate finite difference gradients.");
1822  }
1823 
1824  // Create forced thermalizer
1825  if (config.has_value({"Forced_Thermalization"})) {
1826  Configuration th_conf =
1827  config.extract_sub_configuration({"Forced_Thermalization"});
1828  thermalizer_ = modus_.create_grandcan_thermalizer(th_conf);
1829  }
1830 
1831  /* Take the seed setting only after the configuration was stored to a file
1832  * in smash.cc */
1833  seed_ = config.take({"General", "Randomseed"});
1834 }
1835 
1837 const std::string hline(113, '-');
1838 
1863 std::string format_measurements(const std::vector<Particles> &ensembles,
1864  uint64_t scatterings_this_interval,
1865  const QuantumNumbers &conserved_initial,
1866  SystemTimePoint time_start, double time,
1867  double E_mean_field,
1868  double E_mean_field_initial);
1883  const Potentials &potentials,
1885  RectangularLattice<std::pair<ThreeVector, ThreeVector>> *em_lattice,
1886  const ExperimentParameters &parameters);
1887 
1904 EventInfo fill_event_info(const std::vector<Particles> &ensembles,
1905  double E_mean_field, double modus_impact_parameter,
1906  const ExperimentParameters &parameters,
1907  bool projectile_target_interact,
1908  bool kinematic_cut_for_SMASH_IC);
1909 
1910 template <typename Modus>
1912  random::set_seed(seed_);
1913  logg[LExperiment].info() << "random number seed: " << seed_;
1914  /* Set seed for the next event. It has to be positive, so it can be entered
1915  * in the config.
1916  *
1917  * We have to be careful about the minimal integer, whose absolute value
1918  * cannot be represented. */
1919  int64_t r = random::advance();
1920  while (r == INT64_MIN) {
1921  r = random::advance();
1922  }
1923  seed_ = std::abs(r);
1924  /* Set the random seed used in PYTHIA hadronization
1925  * to be same with the SMASH one.
1926  * In this way we ensure that the results are reproducible
1927  * for every event if one knows SMASH random seed. */
1928  if (process_string_ptr_ != NULL) {
1929  process_string_ptr_->init_pythia_hadron_rndm();
1930  }
1931 
1932  for (Particles &particles : ensembles_) {
1933  particles.reset();
1934  }
1935 
1936  // Sample particles according to the initial conditions
1937  double start_time = -1.0;
1938 
1939  // Sample impact parameter only once per all ensembles
1940  // It should be the same for all ensembles
1941  if (modus_.is_collider()) {
1942  modus_.sample_impact();
1943  logg[LExperiment].info("Impact parameter = ", modus_.impact_parameter(),
1944  " fm");
1945  }
1946  for (Particles &particles : ensembles_) {
1947  start_time = modus_.initial_conditions(&particles, parameters_);
1948  }
1949  /* For box modus make sure that particles are in the box. In principle, after
1950  * a correct initialization they should be, so this is just playing it safe.
1951  */
1952  for (Particles &particles : ensembles_) {
1953  modus_.impose_boundary_conditions(&particles, outputs_);
1954  }
1955  // Reset the simulation clock
1956  double timestep = delta_time_startup_;
1957 
1958  switch (time_step_mode_) {
1959  case TimeStepMode::Fixed:
1960  break;
1961  case TimeStepMode::None:
1962  timestep = end_time_ - start_time;
1963  // Take care of the box modus + timestepless propagation
1964  const double max_dt = modus_.max_timestep(max_transverse_distance_sqr_);
1965  if (max_dt > 0. && max_dt < timestep) {
1966  timestep = max_dt;
1967  }
1968  break;
1969  }
1970  std::unique_ptr<UniformClock> clock_for_this_event;
1971  if (modus_.is_list() && (timestep < 0.0)) {
1972  throw std::runtime_error(
1973  "Timestep for the given event is negative. \n"
1974  "This might happen if the formation times of the input particles are "
1975  "larger than the specified end time of the simulation.");
1976  }
1977  clock_for_this_event =
1978  std::make_unique<UniformClock>(start_time, timestep, end_time_);
1979  parameters_.labclock = std::move(clock_for_this_event);
1980 
1981  // Reset the output clock
1982  parameters_.outputclock->reset(start_time, true);
1983  // remove time before starting time in case of custom output times.
1984  parameters_.outputclock->remove_times_in_past(start_time);
1985 
1986  logg[LExperiment].debug(
1987  "Lab clock: t_start = ", parameters_.labclock->current_time(),
1988  ", dt = ", parameters_.labclock->timestep_duration());
1989 
1990  /* Save the initial conserved quantum numbers and total momentum in
1991  * the system for conservation checks */
1992  conserved_initial_ = QuantumNumbers(ensembles_);
1993  wall_actions_total_ = 0;
1994  previous_wall_actions_total_ = 0;
1995  interactions_total_ = 0;
1996  previous_interactions_total_ = 0;
1997  discarded_interactions_total_ = 0;
1998  total_pauli_blocked_ = 0;
1999  projectile_target_interact_.assign(parameters_.n_ensembles, false);
2000  total_hypersurface_crossing_actions_ = 0;
2001  total_energy_removed_ = 0.0;
2002  total_energy_violated_by_Pythia_ = 0.0;
2003  // Print output headers
2004  logg[LExperiment].info() << hline;
2005  logg[LExperiment].info() << "Time[fm] Ekin[GeV] E_MF[GeV] ETotal[GeV] "
2006  << "ETot/N[GeV] D(ETot/N)[GeV] Scatt&Decays "
2007  << "Particles Comp.Time";
2008  logg[LExperiment].info() << hline;
2009  double E_mean_field = 0.0;
2010  if (potentials_) {
2011  // update_potentials();
2012  // if (parameters.outputclock->current_time() == 0.0 )
2013  // using the lattice is necessary
2014  if ((jmu_B_lat_ != nullptr)) {
2015  update_lattice(jmu_B_lat_.get(), old_jmu_auxiliary_.get(),
2016  new_jmu_auxiliary_.get(), four_gradient_auxiliary_.get(),
2017  LatticeUpdate::EveryTimestep, DensityType::Baryon,
2018  density_param_, ensembles_,
2019  parameters_.labclock->timestep_duration(), true);
2020  // Because there was no lattice at t=-Delta_t, the time derivatives
2021  // drho_dt and dj^mu/dt at t=0 are huge, while they shouldn't be; we
2022  // overwrite the time derivative to zero by hand.
2023  for (auto &node : *jmu_B_lat_) {
2024  node.overwrite_drho_dt_to_zero();
2025  node.overwrite_djmu_dt_to_zero();
2026  }
2027  E_mean_field = calculate_mean_field_energy(*potentials_, *jmu_B_lat_,
2028  EM_lat_.get(), parameters_);
2029  }
2030  }
2031  initial_mean_field_energy_ = E_mean_field;
2033  ensembles_, 0u, conserved_initial_, time_start_,
2034  parameters_.labclock->current_time(), E_mean_field,
2035  initial_mean_field_energy_);
2036 
2037  // Output at event start
2038  for (const auto &output : outputs_) {
2039  for (int i_ens = 0; i_ens < parameters_.n_ensembles; i_ens++) {
2040  auto event_info = fill_event_info(
2041  ensembles_, E_mean_field, modus_.impact_parameter(), parameters_,
2042  projectile_target_interact_[i_ens], kinematic_cuts_for_IC_output_);
2043  output->at_eventstart(ensembles_[i_ens],
2044  // Pretend each ensemble is an independent event
2045  event_ * parameters_.n_ensembles + i_ens,
2046  event_info);
2047  }
2048  // For thermodynamic output
2049  output->at_eventstart(ensembles_, event_);
2050  // For thermodynamic lattice output
2051  if (printout_full_lattice_any_td_) {
2052  if (printout_rho_eckart_) {
2053  switch (dens_type_lattice_printout_) {
2054  case DensityType::Baryon:
2055  output->at_eventstart(event_, ThermodynamicQuantity::EckartDensity,
2056  DensityType::Baryon, *jmu_B_lat_);
2057  break;
2058  case DensityType::BaryonicIsospin:
2059  output->at_eventstart(event_, ThermodynamicQuantity::EckartDensity,
2060  DensityType::BaryonicIsospin, *jmu_I3_lat_);
2061  break;
2062  case DensityType::None:
2063  break;
2064  default:
2065  output->at_eventstart(event_, ThermodynamicQuantity::EckartDensity,
2066  DensityType::BaryonicIsospin,
2067  *jmu_custom_lat_);
2068  }
2069  }
2070  if (printout_tmn_) {
2071  output->at_eventstart(event_, ThermodynamicQuantity::Tmn,
2072  dens_type_lattice_printout_, *Tmn_);
2073  }
2074  if (printout_tmn_landau_) {
2075  output->at_eventstart(event_, ThermodynamicQuantity::TmnLandau,
2076  dens_type_lattice_printout_, *Tmn_);
2077  }
2078  if (printout_v_landau_) {
2079  output->at_eventstart(event_, ThermodynamicQuantity::LandauVelocity,
2080  dens_type_lattice_printout_, *Tmn_);
2081  }
2082  if (printout_j_QBS_) {
2083  output->at_eventstart(event_, ThermodynamicQuantity::j_QBS,
2084  dens_type_lattice_printout_, *j_QBS_lat_);
2085  }
2086  }
2087  }
2088 
2089  /* In the ColliderModus, if Fermi motion is frozen, assign the beam momenta
2090  * to the nucleons in both the projectile and the target. Every ensemble
2091  * gets the same beam momenta, so no need to create beam_momenta_ vector
2092  * for every ensemble.
2093  */
2094  if (modus_.is_collider() && modus_.fermi_motion() == FermiMotion::Frozen) {
2095  for (ParticleData &particle : ensembles_[0]) {
2096  const double m = particle.effective_mass();
2097  double v_beam = 0.0;
2098  if (particle.belongs_to() == BelongsTo::Projectile) {
2099  v_beam = modus_.velocity_projectile();
2100  } else if (particle.belongs_to() == BelongsTo::Target) {
2101  v_beam = modus_.velocity_target();
2102  }
2103  const double gamma = 1.0 / std::sqrt(1.0 - v_beam * v_beam);
2104  beam_momentum_.emplace_back(
2105  FourVector(gamma * m, 0.0, 0.0, gamma * v_beam * m));
2106  } // loop over particles
2107  }
2108 }
2109 
2110 template <typename Modus>
2111 bool Experiment<Modus>::perform_action(Action &action, int i_ensemble,
2112  bool include_pauli_blocking) {
2113  Particles &particles = ensembles_[i_ensemble];
2114  // Make sure to skip invalid and Pauli-blocked actions.
2115  if (!action.is_valid(particles)) {
2116  discarded_interactions_total_++;
2117  logg[LExperiment].debug(~einhard::DRed(), "✘ ", action,
2118  " (discarded: invalid)");
2119  return false;
2120  }
2121  try {
2122  action.generate_final_state();
2123  } catch (Action::StochasticBelowEnergyThreshold &) {
2124  return false;
2125  }
2126  logg[LExperiment].debug("Process Type is: ", action.get_type());
2127  if (include_pauli_blocking && pauli_blocker_ &&
2128  action.is_pauli_blocked(ensembles_, *pauli_blocker_)) {
2129  total_pauli_blocked_++;
2130  return false;
2131  }
2132 
2133  // Prepare projectile_target_interact_, it's used for output
2134  // to signal that there was some interaction in this event
2135  if (modus_.is_collider()) {
2136  int count_target = 0, count_projectile = 0;
2137  for (const auto &incoming : action.incoming_particles()) {
2138  if (incoming.belongs_to() == BelongsTo::Projectile) {
2139  count_projectile++;
2140  } else if (incoming.belongs_to() == BelongsTo::Target) {
2141  count_target++;
2142  }
2143  }
2144  if (count_target > 0 && count_projectile > 0) {
2145  projectile_target_interact_[i_ensemble] = true;
2146  }
2147  }
2148 
2149  /* Make sure to pick a non-zero integer, because 0 is reserved for "no
2150  * interaction yet". */
2151  const auto id_process = static_cast<uint32_t>(interactions_total_ + 1);
2152  // we perform the action and collect possible energy violations by Pythia
2153  total_energy_violated_by_Pythia_ += action.perform(&particles, id_process);
2154 
2155  interactions_total_++;
2156  if (action.get_type() == ProcessType::Wall) {
2157  wall_actions_total_++;
2158  }
2159  if (action.get_type() == ProcessType::HyperSurfaceCrossing) {
2160  total_hypersurface_crossing_actions_++;
2161  total_energy_removed_ += action.incoming_particles()[0].momentum().x0();
2162  }
2163  // Calculate Eckart rest frame density at the interaction point
2164  double rho = 0.0;
2165  if (dens_type_ != DensityType::None) {
2166  const FourVector r_interaction = action.get_interaction_point();
2167  constexpr bool compute_grad = false;
2168  const bool smearing = true;
2169  // todo(oliiny): it's a rough density estimate from a single ensemble.
2170  // It might actually be appropriate for output. Discuss.
2171  rho = std::get<0>(current_eckart(r_interaction.threevec(), particles,
2172  density_param_, dens_type_, compute_grad,
2173  smearing));
2174  }
2190  for (const auto &output : outputs_) {
2191  if (!output->is_dilepton_output() && !output->is_photon_output()) {
2192  if (output->is_IC_output() &&
2193  action.get_type() == ProcessType::HyperSurfaceCrossing) {
2194  output->at_interaction(action, rho);
2195  } else if (!output->is_IC_output()) {
2196  output->at_interaction(action, rho);
2197  }
2198  }
2199  }
2200 
2201  // At every collision photons can be produced.
2202  // Note: We rely here on the lazy evaluation of the arguments to if.
2203  // It may happen that in a wall-crossing-action sqrt_s raises an exception.
2204  // Therefore we first have to check if the incoming particles can undergo
2205  // an em-interaction.
2206  if (photons_switch_ &&
2207  ScatterActionPhoton::is_photon_reaction(action.incoming_particles()) &&
2208  ScatterActionPhoton::is_kinematically_possible(
2209  action.sqrt_s(), action.incoming_particles())) {
2210  /* Time in the action constructor is relative to
2211  * current time of incoming */
2212  constexpr double action_time = 0.;
2213  ScatterActionPhoton photon_act(action.incoming_particles(), action_time,
2214  n_fractional_photons_,
2215  action.get_total_weight());
2216 
2227  photon_act.add_dummy_hadronic_process(action.get_total_weight());
2228 
2229  // Now add the actual photon reaction channel.
2230  photon_act.add_single_process();
2231 
2232  photon_act.perform_photons(outputs_);
2233  }
2234 
2235  if (bremsstrahlung_switch_ &&
2236  BremsstrahlungAction::is_bremsstrahlung_reaction(
2237  action.incoming_particles())) {
2238  /* Time in the action constructor is relative to
2239  * current time of incoming */
2240  constexpr double action_time = 0.;
2241 
2242  BremsstrahlungAction brems_act(action.incoming_particles(), action_time,
2243  n_fractional_photons_,
2244  action.get_total_weight());
2245 
2257  brems_act.add_dummy_hadronic_process(action.get_total_weight());
2258 
2259  // Now add the actual bremsstrahlung reaction channel.
2260  brems_act.add_single_process();
2261 
2262  brems_act.perform_bremsstrahlung(outputs_);
2263  }
2264 
2265  logg[LExperiment].debug(~einhard::Green(), "✔ ", action);
2266  return true;
2267 }
2268 
2279 void validate_and_adjust_particle_list(ParticleList &particle_list);
2280 
2281 template <typename Modus>
2283  ParticleList &&add_plist,
2284  ParticleList &&remove_plist) {
2285  if (!add_plist.empty() || !remove_plist.empty()) {
2286  if (ensembles_.size() > 1) {
2287  throw std::runtime_error(
2288  "Adding or removing particles from SMASH is only possible when one "
2289  "ensemble is used.");
2290  }
2291  const double action_time = parameters_.labclock->current_time();
2292  /* Use two if statements. The first one is to check if the particles are
2293  * valid. Since this might remove all particles, a second if statement is
2294  * needed to avoid executing the action in that case.*/
2295  if (!add_plist.empty()) {
2297  }
2298  if (!add_plist.empty()) {
2299  // Create and perform action to add particle(s)
2300  auto action_add_particles = std::make_unique<FreeforallAction>(
2301  ParticleList{}, add_plist, action_time);
2302  perform_action(*action_add_particles, 0);
2303  }
2304  // Also here 2 if statements are needed as above.
2305  if (!remove_plist.empty()) {
2306  validate_and_adjust_particle_list(remove_plist);
2307  }
2308  if (!remove_plist.empty()) {
2309  ParticleList found_particles_to_remove;
2310  for (const auto &particle_to_remove : remove_plist) {
2311  const auto iterator_to_particle_to_be_removed_in_ensemble =
2312  std::find_if(
2313  ensembles_[0].begin(), ensembles_[0].end(),
2314  [&particle_to_remove, &action_time](const ParticleData &p) {
2316  particle_to_remove, p, action_time);
2317  });
2318  if (iterator_to_particle_to_be_removed_in_ensemble !=
2319  ensembles_[0].end())
2320  found_particles_to_remove.push_back(
2321  *iterator_to_particle_to_be_removed_in_ensemble);
2322  }
2323  // Sort the particles found to be removed according to their id and look
2324  // for duplicates (sorting is needed to call std::adjacent_find).
2325  std::sort(found_particles_to_remove.begin(),
2326  found_particles_to_remove.end(),
2327  [](const ParticleData &p1, const ParticleData &p2) {
2328  return p1.id() < p2.id();
2329  });
2330  const auto iterator_to_first_duplicate = std::adjacent_find(
2331  found_particles_to_remove.begin(), found_particles_to_remove.end(),
2332  [](const ParticleData &p1, const ParticleData &p2) {
2333  return p1.id() == p2.id();
2334  });
2335  if (iterator_to_first_duplicate != found_particles_to_remove.end()) {
2336  logg[LExperiment].error() << "The same particle has been asked to be "
2337  "removed multiple times:\n"
2338  << *iterator_to_first_duplicate;
2339  throw std::logic_error("Particle cannot be removed twice!");
2340  }
2341  if (auto delta = remove_plist.size() - found_particles_to_remove.size();
2342  delta > 0) {
2343  logg[LExperiment].warn(
2344  "When trying to remove particle(s) at the beginning ",
2345  "of the system evolution,\n", delta,
2346  " particle(s) could not be found and will be ignored.");
2347  }
2348  if (!found_particles_to_remove.empty()) {
2349  [[maybe_unused]] const auto number_particles_before_removal =
2350  ensembles_[0].size();
2351  // Create and perform action to remove particles
2352  auto action_remove_particles = std::make_unique<FreeforallAction>(
2353  found_particles_to_remove, ParticleList{}, action_time);
2354  perform_action(*action_remove_particles, 0);
2355 
2356  assert(number_particles_before_removal -
2357  found_particles_to_remove.size() ==
2358  ensembles_[0].size());
2359  }
2360  }
2361  }
2362 
2363  if (t_end > end_time_) {
2364  logg[LExperiment].fatal()
2365  << "Evolution asked to be run until " << t_end << " > " << end_time_
2366  << " and this cannot be done (because of how the clock works).";
2367  throw std::logic_error(
2368  "Experiment cannot evolve the system beyond End_Time.");
2369  }
2370  while (*(parameters_.labclock) < t_end) {
2371  const double dt = parameters_.labclock->timestep_duration();
2372  logg[LExperiment].debug("Timestepless propagation for next ", dt, " fm.");
2373 
2374  // Perform forced thermalization if required
2375  if (thermalizer_ &&
2376  thermalizer_->is_time_to_thermalize(parameters_.labclock)) {
2377  const bool ignore_cells_under_treshold = true;
2378  // Thermodynamics in thermalizer is computed from all ensembles,
2379  // but thermalization actions act on each ensemble independently
2380  thermalizer_->update_thermalizer_lattice(ensembles_, density_param_,
2381  ignore_cells_under_treshold);
2382  const double current_t = parameters_.labclock->current_time();
2383  for (int i_ens = 0; i_ens < parameters_.n_ensembles; i_ens++) {
2384  thermalizer_->thermalize(ensembles_[i_ens], current_t,
2385  parameters_.testparticles);
2386  ThermalizationAction th_act(*thermalizer_, current_t);
2387  if (th_act.any_particles_thermalized()) {
2388  perform_action(th_act, i_ens);
2389  }
2390  }
2391  }
2392 
2393  std::vector<Actions> actions(parameters_.n_ensembles);
2394  for (int i_ens = 0; i_ens < parameters_.n_ensembles; i_ens++) {
2395  actions[i_ens].clear();
2396  if (ensembles_[i_ens].size() > 0 && action_finders_.size() > 0) {
2397  /* (1.a) Create grid. */
2398  const double min_cell_length = compute_min_cell_length(dt);
2399  logg[LExperiment].debug("Creating grid with minimal cell length ",
2400  min_cell_length);
2401  /* For the hyper-surface-crossing actions also unformed particles are
2402  * searched and therefore needed on the grid. */
2403  const bool include_unformed_particles = IC_output_switch_;
2404  const auto &grid =
2405  use_grid_ ? modus_.create_grid(ensembles_[i_ens], min_cell_length,
2406  dt, parameters_.coll_crit,
2407  include_unformed_particles)
2408  : modus_.create_grid(ensembles_[i_ens], min_cell_length,
2409  dt, parameters_.coll_crit,
2410  include_unformed_particles,
2411  CellSizeStrategy::Largest);
2412 
2413  const double gcell_vol = grid.cell_volume();
2414  /* (1.b) Iterate over cells and find actions. */
2415  grid.iterate_cells(
2416  [&](const ParticleList &search_list) {
2417  for (const auto &finder : action_finders_) {
2418  actions[i_ens].insert(finder->find_actions_in_cell(
2419  search_list, dt, gcell_vol, beam_momentum_));
2420  }
2421  },
2422  [&](const ParticleList &search_list,
2423  const ParticleList &neighbors_list) {
2424  for (const auto &finder : action_finders_) {
2425  actions[i_ens].insert(finder->find_actions_with_neighbors(
2426  search_list, neighbors_list, dt, beam_momentum_));
2427  }
2428  });
2429  }
2430  }
2431 
2432  /* \todo (optimizations) Adapt timestep size here */
2433 
2434  /* (2) Propagate from action to action until next output or timestep end */
2435  const double end_timestep_time = parameters_.labclock->next_time();
2436  while (next_output_time() < end_timestep_time) {
2437  for (int i_ens = 0; i_ens < parameters_.n_ensembles; i_ens++) {
2438  run_time_evolution_timestepless(actions[i_ens], i_ens,
2439  next_output_time());
2440  }
2441  ++(*parameters_.outputclock);
2442 
2443  intermediate_output();
2444  }
2445  for (int i_ens = 0; i_ens < parameters_.n_ensembles; i_ens++) {
2446  run_time_evolution_timestepless(actions[i_ens], i_ens, end_timestep_time);
2447  }
2448 
2449  /* (3) Update potentials (if computed on the lattice) and
2450  * compute new momenta according to equations of motion */
2451  if (potentials_) {
2452  update_potentials();
2453  update_momenta(ensembles_, parameters_.labclock->timestep_duration(),
2454  *potentials_, FB_lat_.get(), FI3_lat_.get(), EM_lat_.get(),
2455  jmu_B_lat_.get());
2456  }
2457 
2458  /* (4) Expand universe if non-minkowskian metric; updates
2459  * positions and momenta according to the selected expansion */
2460  if (metric_.mode_ != ExpansionMode::NoExpansion) {
2461  for (Particles &particles : ensembles_) {
2462  expand_space_time(&particles, parameters_, metric_);
2463  }
2464  }
2465 
2466  ++(*parameters_.labclock);
2467 
2468  /* (5) Check conservation laws.
2469  *
2470  * Check conservation of conserved quantities if potentials and string
2471  * fragmentation are off. If potentials are on then momentum is conserved
2472  * only in average. If string fragmentation is on, then energy and
2473  * momentum are only very roughly conserved in high-energy collisions. */
2474  if (!potentials_ && !parameters_.strings_switch &&
2475  metric_.mode_ == ExpansionMode::NoExpansion && !IC_output_switch_) {
2476  std::string err_msg = conserved_initial_.report_deviations(ensembles_);
2477  if (!err_msg.empty()) {
2478  logg[LExperiment].error() << err_msg;
2479  throw std::runtime_error("Violation of conserved quantities!");
2480  }
2481  }
2482  }
2483 
2484  if (pauli_blocker_) {
2485  logg[LExperiment].info(
2486  "Interactions: Pauli-blocked/performed = ", total_pauli_blocked_, "/",
2487  interactions_total_ - wall_actions_total_);
2488  }
2489 }
2490 
2491 template <typename Modus>
2493  Particles &particles) {
2494  const double dt =
2495  propagate_straight_line(&particles, to_time, beam_momentum_);
2496  if (dilepton_finder_ != nullptr) {
2497  for (const auto &output : outputs_) {
2498  dilepton_finder_->shine(particles, output.get(), dt);
2499  }
2500  }
2501 }
2502 
2510 inline void check_interactions_total(uint64_t interactions_total) {
2511  constexpr uint64_t max_uint32 = std::numeric_limits<uint32_t>::max();
2512  if (interactions_total >= max_uint32) {
2513  throw std::runtime_error("Integer overflow in total interaction number!");
2514  }
2515 }
2516 
2517 template <typename Modus>
2519  Actions &actions, int i_ensemble, const double end_time_propagation) {
2520  Particles &particles = ensembles_[i_ensemble];
2521  logg[LExperiment].debug(
2522  "Timestepless propagation: ", "Actions size = ", actions.size(),
2523  ", end time = ", end_time_propagation);
2524 
2525  // iterate over all actions
2526  while (!actions.is_empty()) {
2527  if (actions.earliest_time() > end_time_propagation) {
2528  break;
2529  }
2530  // get next action
2531  ActionPtr act = actions.pop();
2532  if (!act->is_valid(particles)) {
2533  discarded_interactions_total_++;
2534  logg[LExperiment].debug(~einhard::DRed(), "✘ ", act,
2535  " (discarded: invalid)");
2536  continue;
2537  }
2538  logg[LExperiment].debug(~einhard::Green(), "✔ ", act,
2539  ", action time = ", act->time_of_execution());
2540 
2541  /* (1) Propagate to the next action. */
2542  propagate_and_shine(act->time_of_execution(), particles);
2543 
2544  /* (2) Perform action.
2545  *
2546  * Update the positions of the incoming particles, because the information
2547  * in the action object will be outdated as the particles have been
2548  * propagated since the construction of the action. */
2549  act->update_incoming(particles);
2550  const bool performed = perform_action(*act, i_ensemble);
2551 
2552  /* No need to update actions for outgoing particles
2553  * if the action is not performed. */
2554  if (!performed) {
2555  continue;
2556  }
2557 
2558  /* (3) Update actions for newly-produced particles. */
2559 
2560  const double end_time_timestep = parameters_.labclock->next_time();
2561  // New actions are always search until the end of the current timestep
2562  const double time_left = end_time_timestep - act->time_of_execution();
2563  const ParticleList &outgoing_particles = act->outgoing_particles();
2564  // Grid cell volume set to zero, since there is no grid
2565  const double gcell_vol = 0.0;
2566  for (const auto &finder : action_finders_) {
2567  // Outgoing particles can still decay, cross walls...
2568  actions.insert(finder->find_actions_in_cell(outgoing_particles, time_left,
2569  gcell_vol, beam_momentum_));
2570  // ... and collide with other particles.
2571  actions.insert(finder->find_actions_with_surrounding_particles(
2572  outgoing_particles, particles, time_left, beam_momentum_));
2573  }
2574 
2575  check_interactions_total(interactions_total_);
2576  }
2577 
2578  propagate_and_shine(end_time_propagation, particles);
2579 }
2580 
2581 template <typename Modus>
2583  const uint64_t wall_actions_this_interval =
2584  wall_actions_total_ - previous_wall_actions_total_;
2585  previous_wall_actions_total_ = wall_actions_total_;
2586  const uint64_t interactions_this_interval = interactions_total_ -
2587  previous_interactions_total_ -
2588  wall_actions_this_interval;
2589  previous_interactions_total_ = interactions_total_;
2590  double E_mean_field = 0.0;
2593  double computational_frame_time = 0.0;
2594  if (potentials_) {
2595  // using the lattice is necessary
2596  if ((jmu_B_lat_ != nullptr)) {
2597  E_mean_field = calculate_mean_field_energy(*potentials_, *jmu_B_lat_,
2598  EM_lat_.get(), parameters_);
2599  /*
2600  * Mean field calculated in a box should remain approximately constant if
2601  * the system is in equilibrium, and so deviations from its original value
2602  * may signal a phase transition or other dynamical process. This
2603  * comparison only makes sense in the Box Modus, hence the condition.
2604  */
2605  if (modus_.is_box()) {
2606  double tmp = (E_mean_field - initial_mean_field_energy_) /
2607  (E_mean_field + initial_mean_field_energy_);
2608  /*
2609  * This is displayed when the system evolves away from its initial
2610  * configuration (which is when the total mean field energy in the box
2611  * deviates from its initial value).
2612  */
2613  if (std::abs(tmp) > 0.01) {
2614  logg[LExperiment].info()
2615  << "\n\n\n\t The mean field at t = "
2616  << parameters_.outputclock->current_time()
2617  << " [fm] differs from the mean field at t = 0:"
2618  << "\n\t\t initial_mean_field_energy_ = "
2619  << initial_mean_field_energy_ << " [GeV]"
2620  << "\n\t\t abs[(E_MF - E_MF(t=0))/(E_MF + E_MF(t=0))] = "
2621  << std::abs(tmp)
2622  << "\n\t\t E_MF/E_MF(t=0) = "
2623  << E_mean_field / initial_mean_field_energy_ << "\n\n";
2624  }
2625  }
2626  }
2627  }
2628 
2630  ensembles_, interactions_this_interval, conserved_initial_, time_start_,
2631  parameters_.outputclock->current_time(), E_mean_field,
2632  initial_mean_field_energy_);
2633  const LatticeUpdate lat_upd = LatticeUpdate::AtOutput;
2634 
2635  // save evolution data
2636  if (!(modus_.is_box() && parameters_.outputclock->current_time() <
2637  modus_.equilibration_time())) {
2638  for (const auto &output : outputs_) {
2639  if (output->is_dilepton_output() || output->is_photon_output() ||
2640  output->is_IC_output()) {
2641  continue;
2642  }
2643  for (int i_ens = 0; i_ens < parameters_.n_ensembles; i_ens++) {
2644  auto event_info = fill_event_info(
2645  ensembles_, E_mean_field, modus_.impact_parameter(), parameters_,
2646  projectile_target_interact_[i_ens], kinematic_cuts_for_IC_output_);
2647 
2648  output->at_intermediate_time(ensembles_[i_ens], parameters_.outputclock,
2649  density_param_, event_info);
2650  computational_frame_time = event_info.current_time;
2651  }
2652  // For thermodynamic output
2653  output->at_intermediate_time(ensembles_, parameters_.outputclock,
2654  density_param_);
2655 
2656  // Thermodynamic output on the lattice versus time
2657  if (printout_rho_eckart_) {
2658  switch (dens_type_lattice_printout_) {
2659  case DensityType::Baryon:
2660  update_lattice(jmu_B_lat_.get(), lat_upd, DensityType::Baryon,
2661  density_param_, ensembles_, false);
2662  output->thermodynamics_output(ThermodynamicQuantity::EckartDensity,
2663  DensityType::Baryon, *jmu_B_lat_);
2664  output->thermodynamics_lattice_output(*jmu_B_lat_,
2665  computational_frame_time);
2666  break;
2667  case DensityType::BaryonicIsospin:
2668  update_lattice(jmu_I3_lat_.get(), lat_upd,
2669  DensityType::BaryonicIsospin, density_param_,
2670  ensembles_, false);
2671  output->thermodynamics_output(ThermodynamicQuantity::EckartDensity,
2672  DensityType::BaryonicIsospin,
2673  *jmu_I3_lat_);
2674  output->thermodynamics_lattice_output(*jmu_I3_lat_,
2675  computational_frame_time);
2676  break;
2677  case DensityType::None:
2678  break;
2679  default:
2680  update_lattice(jmu_custom_lat_.get(), lat_upd,
2681  dens_type_lattice_printout_, density_param_,
2682  ensembles_, false);
2683  output->thermodynamics_output(ThermodynamicQuantity::EckartDensity,
2684  dens_type_lattice_printout_,
2685  *jmu_custom_lat_);
2686  output->thermodynamics_lattice_output(*jmu_custom_lat_,
2687  computational_frame_time);
2688  }
2689  }
2690  if (printout_tmn_ || printout_tmn_landau_ || printout_v_landau_) {
2691  update_lattice(Tmn_.get(), lat_upd, dens_type_lattice_printout_,
2692  density_param_, ensembles_, false);
2693  if (printout_tmn_) {
2694  output->thermodynamics_output(ThermodynamicQuantity::Tmn,
2695  dens_type_lattice_printout_, *Tmn_);
2696  output->thermodynamics_lattice_output(
2697  ThermodynamicQuantity::Tmn, *Tmn_, computational_frame_time);
2698  }
2699  if (printout_tmn_landau_) {
2700  output->thermodynamics_output(ThermodynamicQuantity::TmnLandau,
2701  dens_type_lattice_printout_, *Tmn_);
2702  output->thermodynamics_lattice_output(
2704  computational_frame_time);
2705  }
2706  if (printout_v_landau_) {
2707  output->thermodynamics_output(ThermodynamicQuantity::LandauVelocity,
2708  dens_type_lattice_printout_, *Tmn_);
2709  output->thermodynamics_lattice_output(
2711  computational_frame_time);
2712  }
2713  }
2714  if (EM_lat_) {
2715  output->fields_output("Efield", "Bfield", *EM_lat_);
2716  }
2717  if (printout_j_QBS_) {
2718  output->thermodynamics_lattice_output(
2719  *j_QBS_lat_, computational_frame_time, ensembles_, density_param_);
2720  }
2721 
2722  if (thermalizer_) {
2723  output->thermodynamics_output(*thermalizer_);
2724  }
2725  }
2726  }
2727 }
2728 
2729 template <typename Modus>
2731  if (potentials_) {
2732  if (potentials_->use_symmetry() && jmu_I3_lat_ != nullptr) {
2733  update_lattice(jmu_I3_lat_.get(), old_jmu_auxiliary_.get(),
2734  new_jmu_auxiliary_.get(), four_gradient_auxiliary_.get(),
2735  LatticeUpdate::EveryTimestep, DensityType::BaryonicIsospin,
2736  density_param_, ensembles_,
2737  parameters_.labclock->timestep_duration(), true);
2738  }
2739  if ((potentials_->use_skyrme() || potentials_->use_symmetry()) &&
2740  jmu_B_lat_ != nullptr) {
2741  update_lattice(jmu_B_lat_.get(), old_jmu_auxiliary_.get(),
2742  new_jmu_auxiliary_.get(), four_gradient_auxiliary_.get(),
2743  LatticeUpdate::EveryTimestep, DensityType::Baryon,
2744  density_param_, ensembles_,
2745  parameters_.labclock->timestep_duration(), true);
2746  const size_t UBlattice_size = UB_lat_->size();
2747  for (size_t i = 0; i < UBlattice_size; i++) {
2748  auto jB = (*jmu_B_lat_)[i];
2749  const FourVector flow_four_velocity_B =
2750  std::abs(jB.rho()) > very_small_double ? jB.jmu_net() / jB.rho()
2751  : FourVector();
2752  double baryon_density = jB.rho();
2753  ThreeVector baryon_grad_j0 = jB.grad_j0();
2754  ThreeVector baryon_dvecj_dt = jB.dvecj_dt();
2755  ThreeVector baryon_curl_vecj = jB.curl_vecj();
2756  if (potentials_->use_skyrme()) {
2757  (*UB_lat_)[i] =
2758  flow_four_velocity_B * potentials_->skyrme_pot(baryon_density);
2759  (*FB_lat_)[i] =
2760  potentials_->skyrme_force(baryon_density, baryon_grad_j0,
2761  baryon_dvecj_dt, baryon_curl_vecj);
2762  }
2763  if (potentials_->use_symmetry() && jmu_I3_lat_ != nullptr) {
2764  auto jI3 = (*jmu_I3_lat_)[i];
2765  const FourVector flow_four_velocity_I3 =
2766  std::abs(jI3.rho()) > very_small_double
2767  ? jI3.jmu_net() / jI3.rho()
2768  : FourVector();
2769  (*UI3_lat_)[i] = flow_four_velocity_I3 *
2770  potentials_->symmetry_pot(jI3.rho(), baryon_density);
2771  (*FI3_lat_)[i] = potentials_->symmetry_force(
2772  jI3.rho(), jI3.grad_j0(), jI3.dvecj_dt(), jI3.curl_vecj(),
2773  baryon_density, baryon_grad_j0, baryon_dvecj_dt,
2774  baryon_curl_vecj);
2775  }
2776  }
2777  }
2778  if (potentials_->use_coulomb()) {
2779  update_lattice(jmu_el_lat_.get(), LatticeUpdate::EveryTimestep,
2780  DensityType::Charge, density_param_, ensembles_, true);
2781  for (size_t i = 0; i < EM_lat_->size(); i++) {
2782  ThreeVector electric_field = {0., 0., 0.};
2783  ThreeVector position = jmu_el_lat_->cell_center(i);
2784  jmu_el_lat_->integrate_volume(electric_field,
2785  Potentials::E_field_integrand,
2786  potentials_->coulomb_r_cut(), position);
2787  ThreeVector magnetic_field = {0., 0., 0.};
2788  jmu_el_lat_->integrate_volume(magnetic_field,
2789  Potentials::B_field_integrand,
2790  potentials_->coulomb_r_cut(), position);
2791  (*EM_lat_)[i] = std::make_pair(electric_field, magnetic_field);
2792  }
2793  } // if ((potentials_->use_skyrme() || ...
2794  if (potentials_->use_vdf() && jmu_B_lat_ != nullptr) {
2795  update_lattice(jmu_B_lat_.get(), old_jmu_auxiliary_.get(),
2796  new_jmu_auxiliary_.get(), four_gradient_auxiliary_.get(),
2797  LatticeUpdate::EveryTimestep, DensityType::Baryon,
2798  density_param_, ensembles_,
2799  parameters_.labclock->timestep_duration(), true);
2800  if (parameters_.field_derivatives_mode == FieldDerivativesMode::Direct) {
2802  fields_lat_.get(), old_fields_auxiliary_.get(),
2803  new_fields_auxiliary_.get(), fields_four_gradient_auxiliary_.get(),
2804  jmu_B_lat_.get(), LatticeUpdate::EveryTimestep, *potentials_,
2805  parameters_.labclock->timestep_duration());
2806  }
2807  const size_t UBlattice_size = UB_lat_->size();
2808  for (size_t i = 0; i < UBlattice_size; i++) {
2809  auto jB = (*jmu_B_lat_)[i];
2810  (*UB_lat_)[i] = potentials_->vdf_pot(jB.rho(), jB.jmu_net());
2811  switch (parameters_.field_derivatives_mode) {
2813  (*FB_lat_)[i] = potentials_->vdf_force(
2814  jB.rho(), jB.drho_dxnu().x0(), jB.drho_dxnu().threevec(),
2815  jB.grad_rho_cross_vecj(), jB.jmu_net().x0(), jB.grad_j0(),
2816  jB.jmu_net().threevec(), jB.dvecj_dt(), jB.curl_vecj());
2817  break;
2819  auto Amu = (*fields_lat_)[i];
2820  (*FB_lat_)[i] = potentials_->vdf_force(
2821  Amu.grad_A0(), Amu.dvecA_dt(), Amu.curl_vecA());
2822  break;
2823  }
2824  } // for (size_t i = 0; i < UBlattice_size; i++)
2825  } // if potentials_->use_vdf()
2826  }
2827 }
2828 
2829 template <typename Modus>
2831  /* At end of time evolution: Force all resonances to decay. In order to handle
2832  * decay chains, we need to loop until no further actions occur. */
2833  bool actions_performed, decays_found;
2834  uint64_t interactions_old;
2835  do {
2836  decays_found = false;
2837  interactions_old = interactions_total_;
2838  for (int i_ens = 0; i_ens < parameters_.n_ensembles; i_ens++) {
2839  Actions actions;
2840 
2841  // Dileptons: shining of remaining resonances
2842  if (dilepton_finder_ != nullptr) {
2843  for (const auto &output : outputs_) {
2844  dilepton_finder_->shine_final(ensembles_[i_ens], output.get(), true);
2845  }
2846  }
2847  // Find actions.
2848  for (const auto &finder : action_finders_) {
2849  auto found_actions = finder->find_final_actions(ensembles_[i_ens]);
2850  if (!found_actions.empty()) {
2851  actions.insert(std::move(found_actions));
2852  decays_found = true;
2853  }
2854  }
2855  // Perform actions.
2856  while (!actions.is_empty()) {
2857  perform_action(*actions.pop(), i_ens, false);
2858  }
2859  }
2860  actions_performed = interactions_total_ > interactions_old;
2861  // Throw an error if actions were found but not performed
2862  if (decays_found && !actions_performed) {
2863  throw std::runtime_error("Final decays were found but not performed.");
2864  }
2865  // loop until no more decays occur
2866  } while (actions_performed);
2867 
2868  // Dileptons: shining of stable particles at the end
2869  if (dilepton_finder_ != nullptr) {
2870  for (const auto &output : outputs_) {
2871  for (Particles &particles : ensembles_) {
2872  dilepton_finder_->shine_final(particles, output.get(), false);
2873  }
2874  }
2875  }
2876 }
2877 
2878 template <typename Modus>
2880  /* make sure the experiment actually ran (note: we should compare this
2881  * to the start time, but we don't know that. Therefore, we check that
2882  * the time is positive, which should heuristically be the same). */
2883  double E_mean_field = 0.0;
2884  if (likely(parameters_.labclock > 0)) {
2885  const uint64_t wall_actions_this_interval =
2886  wall_actions_total_ - previous_wall_actions_total_;
2887  const uint64_t interactions_this_interval = interactions_total_ -
2888  previous_interactions_total_ -
2889  wall_actions_this_interval;
2890  if (potentials_) {
2891  // using the lattice is necessary
2892  if ((jmu_B_lat_ != nullptr)) {
2893  E_mean_field = calculate_mean_field_energy(*potentials_, *jmu_B_lat_,
2894  EM_lat_.get(), parameters_);
2895  }
2896  }
2897  if (std::abs(parameters_.labclock->current_time() - end_time_) >
2898  really_small) {
2899  logg[LExperiment].warn()
2900  << "SMASH not propagated until configured end time. Current time = "
2901  << parameters_.labclock->current_time()
2902  << "fm. End time = " << end_time_ << "fm.";
2903  } else {
2905  ensembles_, interactions_this_interval, conserved_initial_,
2906  time_start_, end_time_, E_mean_field, initial_mean_field_energy_);
2907  }
2908  int total_particles = 0;
2909  for (const Particles &particles : ensembles_) {
2910  total_particles += particles.size();
2911  }
2912  if (IC_output_switch_ && (total_particles == 0)) {
2913  const double initial_system_energy_plus_Pythia_violations =
2914  conserved_initial_.momentum().x0() + total_energy_violated_by_Pythia_;
2915  const double fraction_of_total_system_energy_removed =
2916  initial_system_energy_plus_Pythia_violations / total_energy_removed_;
2917  // Verify there is no more energy in the system if all particles were
2918  // removed when crossing the hypersurface
2919  if (std::fabs(fraction_of_total_system_energy_removed - 1.) >
2920  really_small) {
2921  throw std::runtime_error(
2922  "There is remaining energy in the system although all particles "
2923  "were removed.\n"
2924  "E_remain = " +
2925  std::to_string((initial_system_energy_plus_Pythia_violations -
2926  total_energy_removed_)) +
2927  " [GeV]");
2928  } else {
2929  logg[LExperiment].info() << hline;
2930  logg[LExperiment].info()
2931  << "Time real: " << SystemClock::now() - time_start_;
2932  logg[LExperiment].info()
2933  << "Interactions before reaching hypersurface: "
2934  << interactions_total_ - wall_actions_total_ -
2935  total_hypersurface_crossing_actions_;
2936  logg[LExperiment].info()
2937  << "Total number of particles removed on hypersurface: "
2938  << total_hypersurface_crossing_actions_;
2939  }
2940  } else {
2941  const double precent_discarded =
2942  interactions_total_ > 0
2943  ? static_cast<double>(discarded_interactions_total_) * 100.0 /
2944  interactions_total_
2945  : 0.0;
2946  std::stringstream msg_discarded;
2947  msg_discarded
2948  << "Discarded interaction number: " << discarded_interactions_total_
2949  << " (" << precent_discarded
2950  << "% of the total interaction number including wall crossings)";
2951 
2952  logg[LExperiment].info() << hline;
2953  logg[LExperiment].info()
2954  << "Time real: " << SystemClock::now() - time_start_;
2955  logg[LExperiment].debug() << msg_discarded.str();
2956 
2957  if (parameters_.coll_crit == CollisionCriterion::Stochastic &&
2958  precent_discarded > 1.0) {
2959  // The choosen threshold of 1% is a heuristical value
2960  logg[LExperiment].warn()
2961  << msg_discarded.str()
2962  << "\nThe number of discarded interactions is large, which means "
2963  "the assumption for the stochastic criterion of\n1 interaction "
2964  "per particle per timestep is probably violated. Consider "
2965  "reducing the timestep size.";
2966  }
2967 
2968  logg[LExperiment].info() << "Final interaction number: "
2969  << interactions_total_ - wall_actions_total_;
2970  }
2971 
2972  // Check if there are unformed particles
2973  int unformed_particles_count = 0;
2974  for (const Particles &particles : ensembles_) {
2975  for (const ParticleData &particle : particles) {
2976  if (particle.formation_time() > end_time_) {
2977  unformed_particles_count++;
2978  }
2979  }
2980  }
2981  if (unformed_particles_count > 0) {
2982  logg[LExperiment].warn(
2983  "End time might be too small. ", unformed_particles_count,
2984  " unformed particles were found at the end of the evolution.");
2985  }
2986  }
2987 
2988  // Keep track of how many ensembles had interactions
2989  count_nonempty_ensembles();
2990 
2991  for (const auto &output : outputs_) {
2992  for (int i_ens = 0; i_ens < parameters_.n_ensembles; i_ens++) {
2993  auto event_info = fill_event_info(
2994  ensembles_, E_mean_field, modus_.impact_parameter(), parameters_,
2995  projectile_target_interact_[i_ens], kinematic_cuts_for_IC_output_);
2996  output->at_eventend(ensembles_[i_ens],
2997  // Pretend each ensemble is an independent event
2998  event_ * parameters_.n_ensembles + i_ens, event_info);
2999  }
3000  // For thermodynamic output
3001  output->at_eventend(ensembles_, event_);
3002 
3003  // For thermodynamic lattice output
3004  if (printout_rho_eckart_) {
3005  if (dens_type_lattice_printout_ != DensityType::None) {
3006  output->at_eventend(event_, ThermodynamicQuantity::EckartDensity,
3007  dens_type_lattice_printout_);
3008  output->at_eventend(ThermodynamicQuantity::EckartDensity);
3009  }
3010  }
3011  if (printout_tmn_) {
3012  output->at_eventend(event_, ThermodynamicQuantity::Tmn,
3013  dens_type_lattice_printout_);
3014  output->at_eventend(ThermodynamicQuantity::Tmn);
3015  }
3016  if (printout_tmn_landau_) {
3017  output->at_eventend(event_, ThermodynamicQuantity::TmnLandau,
3018  dens_type_lattice_printout_);
3019  output->at_eventend(ThermodynamicQuantity::TmnLandau);
3020  }
3021  if (printout_v_landau_) {
3022  output->at_eventend(event_, ThermodynamicQuantity::LandauVelocity,
3023  dens_type_lattice_printout_);
3024  output->at_eventend(ThermodynamicQuantity::LandauVelocity);
3025  }
3026  if (printout_j_QBS_) {
3027  output->at_eventend(ThermodynamicQuantity::j_QBS);
3028  }
3029  }
3030 }
3031 
3032 template <typename Modus>
3034  for (bool has_interaction : projectile_target_interact_) {
3035  if (has_interaction) {
3036  nonempty_ensembles_++;
3037  }
3038  }
3039 }
3040 
3041 template <typename Modus>
3043  if (event_counting_ == EventCounting::FixedNumber) {
3044  return event_ >= nevents_;
3045  }
3046  if (event_counting_ == EventCounting::MinimumNonEmpty) {
3047  if (nonempty_ensembles_ >= minimum_nonempty_ensembles_) {
3048  return true;
3049  }
3050  if (event_ >= max_events_) {
3051  logg[LExperiment].warn()
3052  << "Maximum number of events (" << max_events_
3053  << ") exceeded. Stopping calculation. "
3054  << "The fraction of empty ensembles is "
3055  << (1.0 - static_cast<double>(nonempty_ensembles_) /
3056  (event_ * parameters_.n_ensembles))
3057  << ". If this fraction is expected, try increasing the "
3058  "Maximum_Ensembles_Run.";
3059  return true;
3060  }
3061  return false;
3062  }
3063  throw std::runtime_error("Event counting option is invalid");
3064  return false;
3065 }
3066 
3067 template <typename Modus>
3069  event_++;
3070 }
3071 
3072 template <typename Modus>
3074  const auto &mainlog = logg[LMain];
3075  for (event_ = 0; !is_finished(); event_++) {
3076  mainlog.info() << "Event " << event_;
3077 
3078  // Sample initial particles, start clock, some printout and book-keeping
3079  initialize_new_event();
3080 
3081  run_time_evolution(end_time_);
3082 
3083  if (force_decays_) {
3084  do_final_decays();
3085  }
3086 
3087  // Output at event end
3088  final_output();
3089  }
3090 }
3091 
3092 } // namespace smash
3093 
3094 #endif // SRC_INCLUDE_SMASH_EXPERIMENT_H_
Collection of useful type aliases to measure and output the (real) runtime.
A stream modifier that allows to colorize the log output.
Definition: einhard.hpp:143
Action is the base class for a generic process that takes a number of incoming particles and transfor...
Definition: action.h:35
virtual ProcessType get_type() const
Get the process type.
Definition: action.h:131
virtual double get_total_weight() const =0
Return the total weight value, which is mainly used for the weight output entry.
virtual double perform(Particles *particles, uint32_t id_process)
Actually perform the action, e.g.
Definition: action.cc:128
const ParticleList & incoming_particles() const
Get the list of particles that go into the action.
Definition: action.cc:58
virtual void generate_final_state()=0
Generate the final state for this action.
double sqrt_s() const
Determine the total energy in the center-of-mass frame [GeV].
Definition: action.h:271
FourVector get_interaction_point() const
Get the interaction point.
Definition: action.cc:68
bool is_valid(const Particles &particles) const
Check whether the action still applies.
Definition: action.cc:29
bool is_pauli_blocked(const std::vector< Particles > &ensembles, const PauliBlocker &p_bl) const
Check if the action is Pauli-blocked.
Definition: action.cc:35
The Actions class abstracts the storage and manipulation of actions.
Definition: actions.h:29
ActionPtr pop()
Return the first action in the list and removes it from the list.
Definition: actions.h:59
double earliest_time() const
Return time of execution of earliest action.
Definition: actions.h:70
ActionList::size_type size() const
Definition: actions.h:98
void insert(ActionList &&new_acts)
Insert a list of actions into this object.
Definition: actions.h:79
bool is_empty() const
Definition: actions.h:52
Interface to the SMASH configuration files.
bool has_value(std::initializer_list< const char * > keys) const
Return whether there is a non-empty value behind the requested keys.
void set_value(std::initializer_list< const char * > keys, T &&value)
Overwrite the value of the specified YAML node.
Configuration extract_sub_configuration(std::initializer_list< const char * > keys, Configuration::GetEmpty empty_if_not_existing=Configuration::GetEmpty::No)
Create a new configuration from a then-removed section of the present object.
Value take(std::initializer_list< const char * > keys)
The default interface for SMASH to read configuration values.
Value read(std::initializer_list< const char * > keys) const
Additional interface for SMASH to read configuration values without removing them.
A class to pre-calculate and store parameters relevant for density calculation.
Definition: density.h:108
Non-template interface to Experiment<Modus>.
Definition: experiment.h:99
static std::unique_ptr< ExperimentBase > create(Configuration &config, const std::filesystem::path &output_path)
Factory method that creates and initializes a new Experiment<Modus>.
Definition: experiment.cc:22
virtual ~ExperimentBase()=default
The virtual destructor avoids undefined behavior when destroying derived objects.
virtual void run()=0
Runs the experiment.
The main class, where the simulation of an experiment is executed.
Definition: experiment.h:188
const bool bremsstrahlung_switch_
This indicates whether bremsstrahlung is switched on.
Definition: experiment.h:610
void propagate_and_shine(double to_time, Particles &particles)
Propagate all particles until time to_time without any interactions and shine dileptons.
Definition: experiment.h:2492
Experiment(Configuration &config, const std::filesystem::path &output_path)
Create a new Experiment.
const ExpansionProperties metric_
This struct contains information on the metric to be used.
Definition: experiment.h:601
std::unique_ptr< ActionFinderInterface > photon_finder_
The (Scatter) Actions Finder for Direct Photons.
Definition: experiment.h:426
const bool force_decays_
This indicates whether we force all resonances to decay in the last timestep.
Definition: experiment.h:595
double initial_mean_field_energy_
The initial total mean field energy in the system.
Definition: experiment.h:637
void create_output(const std::string &format, const std::string &content, const std::filesystem::path &output_path, const OutputParameters &par)
Create a list of output files.
Definition: experiment.h:720
std::vector< std::unique_ptr< ActionFinderInterface > > action_finders_
The Action finder objects.
Definition: experiment.h:420
bool printout_tmn_
Whether to print the energy-momentum tensor.
Definition: experiment.h:508
QuantumNumbers conserved_initial_
The conserved quantities of the system.
Definition: experiment.h:631
DensityParameters density_param_
Structure to precalculate and hold parameters for density computations.
Definition: experiment.h:371
double next_output_time() const
Shortcut for next output time.
Definition: experiment.h:346
double total_energy_removed_
Total energy removed from the system in hypersurface crossing actions.
Definition: experiment.h:690
DensityType dens_type_lattice_printout_
Type of density for lattice printout.
Definition: experiment.h:456
double max_transverse_distance_sqr_
Maximal distance at which particles can interact in case of the geometric criterion,...
Definition: experiment.h:622
bool printout_j_QBS_
Whether to print the Q, B, S 4-currents.
Definition: experiment.h:517
std::unique_ptr< GrandCanThermalizer > thermalizer_
Instance of class used for forced thermalization.
Definition: experiment.h:527
void count_nonempty_ensembles()
Counts the number of ensembles in wich interactions took place at the end of an event.
Definition: experiment.h:3033
const TimeStepMode time_step_mode_
This indicates whether to use time steps.
Definition: experiment.h:616
std::unique_ptr< DensityLattice > jmu_custom_lat_
Custom density on the lattices.
Definition: experiment.h:453
bool printout_full_lattice_any_td_
Whether to print the thermodynamics quantities evaluated on the lattices, point by point,...
Definition: experiment.h:524
void increase_event_number()
Increases the event number by one.
Definition: experiment.h:3068
void run_time_evolution_timestepless(Actions &actions, int i_ensemble, const double end_time_propagation)
Performs all the propagations and actions during a certain time interval neglecting the influence of ...
Definition: experiment.h:2518
Particles * first_ensemble()
Provides external access to SMASH particles.
Definition: experiment.h:257
void run_time_evolution(const double t_end, ParticleList &&add_plist={}, ParticleList &&remove_plist={})
Runs the time evolution of an event with fixed-size time steps or without timesteps,...
Definition: experiment.h:2282
int minimum_nonempty_ensembles_
The number of ensembles, in which interactions take place, to be calculated.
Definition: experiment.h:559
std::unique_ptr< DensityLattice > jmu_B_lat_
Baryon density on the lattice.
Definition: experiment.h:435
DensityType dens_type_
Type of density to be written to collision headers.
Definition: experiment.h:643
const int nevents_
Number of events.
Definition: experiment.h:549
void intermediate_output()
Intermediate output during an event.
Definition: experiment.h:2582
std::vector< FourVector > beam_momentum_
The initial nucleons in the ColliderModus propagate with beam_momentum_, if Fermi motion is frozen.
Definition: experiment.h:417
std::unique_ptr< RectangularLattice< std::pair< ThreeVector, ThreeVector > > > FI3_lat_
Lattices for the electric and magnetic component of the symmetry force.
Definition: experiment.h:479
std::unique_ptr< RectangularLattice< FourVector > > new_jmu_auxiliary_
Auxiliary lattice for values of jmu at a time step t0 + dt.
Definition: experiment.h:491
double compute_min_cell_length(double dt) const
Calculate the minimal size for the grid cells such that the ScatterActionsFinder will find all collis...
Definition: experiment.h:338
void initialize_new_event()
This is called in the beginning of each event.
Definition: experiment.h:1911
bool is_finished()
Checks wether the desired number events have been calculated.
Definition: experiment.h:3042
int n_fractional_photons_
Number of fractional photons produced per single reaction.
Definition: experiment.h:429
const double delta_time_startup_
The clock's timestep size at start up.
Definition: experiment.h:589
std::unique_ptr< RectangularLattice< EnergyMomentumTensor > > Tmn_
Lattices of energy-momentum tensors for printout.
Definition: experiment.h:486
std::vector< Particles > ensembles_
Complete particle list, all ensembles in one vector.
Definition: experiment.h:380
std::unique_ptr< RectangularLattice< FourVector > > new_fields_auxiliary_
Auxiliary lattice for values of Amu at a time step t0 + dt.
Definition: experiment.h:499
SystemTimePoint time_start_
system starting time of the simulation
Definition: experiment.h:640
uint64_t previous_wall_actions_total_
Total number of wall-crossings for previous timestep.
Definition: experiment.h:667
std::unique_ptr< RectangularLattice< std::pair< ThreeVector, ThreeVector > > > FB_lat_
Lattices for the electric and magnetic components of the Skyrme or VDF force.
Definition: experiment.h:475
OutputsList outputs_
A list of output formaters.
Definition: experiment.h:398
bool printout_rho_eckart_
Whether to print the Eckart rest frame density.
Definition: experiment.h:505
int event_
Current event.
Definition: experiment.h:570
std::unique_ptr< RectangularLattice< std::array< FourVector, 4 > > > fields_four_gradient_auxiliary_
Auxiliary lattice for calculating the four-gradient of Amu.
Definition: experiment.h:502
void do_final_decays()
Performs the final decays of an event.
Definition: experiment.h:2830
std::unique_ptr< PauliBlocker > pauli_blocker_
An instance of PauliBlocker class that stores parameters needed for Pauli blocking calculations and c...
Definition: experiment.h:392
bool printout_v_landau_
Whether to print the 4-velocity in Landau frame.
Definition: experiment.h:514
bool perform_action(Action &action, int i_ensemble, bool include_pauli_blocking=true)
Perform the given action.
std::unique_ptr< RectangularLattice< FourVector > > UI3_lat_
Lattices for symmetry potentials (evaluated in the local rest frame) times the isospin flow 4-velocit...
Definition: experiment.h:468
bool printout_lattice_td_
Whether to print the thermodynamics quantities evaluated on the lattices.
Definition: experiment.h:520
std::unique_ptr< RectangularLattice< std::pair< ThreeVector, ThreeVector > > > EM_lat_
Lattices for electric and magnetic field in fm^-2.
Definition: experiment.h:483
std::unique_ptr< FieldsLattice > fields_lat_
Mean-field A^mu on the lattice.
Definition: experiment.h:444
int max_events_
Maximum number of events to be calculated in order obtain the desired number of non-empty events usin...
Definition: experiment.h:579
int nonempty_ensembles_
Number of ensembles containing an interaction.
Definition: experiment.h:573
OutputPtr photon_output_
The Photon output.
Definition: experiment.h:404
EventCounting event_counting_
The way in which the number of calculated events is specified.
Definition: experiment.h:567
const bool dileptons_switch_
This indicates whether dileptons are switched on.
Definition: experiment.h:604
Modus modus_
Instance of the Modus template parameter.
Definition: experiment.h:377
std::unique_ptr< RectangularLattice< FourVector > > old_jmu_auxiliary_
Auxiliary lattice for values of jmu at a time step t0.
Definition: experiment.h:489
std::unique_ptr< DecayActionsFinderDilepton > dilepton_finder_
The Dilepton Action Finder.
Definition: experiment.h:423
std::unique_ptr< DensityLattice > j_QBS_lat_
4-current for j_QBS lattice output
Definition: experiment.h:432
bool printout_tmn_landau_
Whether to print the energy-momentum tensor in Landau frame.
Definition: experiment.h:511
std::unique_ptr< RectangularLattice< std::array< FourVector, 4 > > > four_gradient_auxiliary_
Auxiliary lattice for calculating the four-gradient of jmu.
Definition: experiment.h:494
const bool photons_switch_
This indicates whether photons are switched on.
Definition: experiment.h:607
StringProcess * process_string_ptr_
Pointer to the string process class object, which is used to set the random seed for PYTHIA objects i...
Definition: experiment.h:533
std::unique_ptr< RectangularLattice< FourVector > > UB_lat_
Lattices for Skyrme or VDF potentials (evaluated in the local rest frame) times the baryon flow 4-vel...
Definition: experiment.h:462
ExperimentParameters parameters_
Struct of several member variables.
Definition: experiment.h:368
std::unique_ptr< RectangularLattice< FourVector > > old_fields_auxiliary_
Auxiliary lattice for values of Amu at a time step t0.
Definition: experiment.h:497
std::unique_ptr< DensityLattice > jmu_el_lat_
Electric charge density on the lattice.
Definition: experiment.h:441
std::unique_ptr< Potentials > potentials_
An instance of potentials class, that stores parameters of potentials, calculates them and their grad...
Definition: experiment.h:386
uint64_t wall_actions_total_
Total number of wall-crossings for current timestep.
Definition: experiment.h:661
uint64_t total_hypersurface_crossing_actions_
Total number of particles removed from the evolution in hypersurface crossing actions.
Definition: experiment.h:679
uint64_t interactions_total_
Total number of interactions for current timestep.
Definition: experiment.h:649
const bool use_grid_
This indicates whether to use the grid.
Definition: experiment.h:598
std::unique_ptr< DensityLattice > jmu_I3_lat_
Isospin projection density on the lattice.
Definition: experiment.h:438
uint64_t total_pauli_blocked_
Total number of Pauli-blockings for current timestep.
Definition: experiment.h:673
void final_output()
Output at the end of an event.
Definition: experiment.h:2879
std::vector< bool > projectile_target_interact_
Whether the projectile and the target collided.
Definition: experiment.h:410
Modus * modus()
Provides external access to SMASH calculation modus.
Definition: experiment.h:265
void update_potentials()
Recompute potentials on lattices if necessary.
Definition: experiment.h:2730
const bool IC_output_switch_
This indicates whether the IC output is enabled.
Definition: experiment.h:613
OutputPtr dilepton_output_
The Dilepton output.
Definition: experiment.h:401
int64_t seed_
random seed for the next event.
Definition: experiment.h:701
std::vector< Particles > * all_ensembles()
Getter for all ensembles.
Definition: experiment.h:259
uint64_t previous_interactions_total_
Total number of interactions for previous timestep.
Definition: experiment.h:655
uint64_t discarded_interactions_total_
Total number of discarded interactions, because they were invalidated before they could be performed.
Definition: experiment.h:685
void run() override
Runs the experiment.
Definition: experiment.h:3073
bool kinematic_cuts_for_IC_output_
This indicates whether kinematic cuts are enabled for the IC output.
Definition: experiment.h:698
const double end_time_
simulation time at which the evolution is stopped.
Definition: experiment.h:582
double total_energy_violated_by_Pythia_
Total energy violation introduced by Pythia.
Definition: experiment.h:695
The FourVector class holds relevant values in Minkowski spacetime with (+, −, −, −) metric signature.
Definition: fourvector.h:33
default_type default_value() const
Get the default value of the key.
Definition: input_keys.h:132
ParticleData contains the dynamic information of a certain particle.
Definition: particledata.h:58
static double formation_power_
Power with which the cross section scaling factor grows in time.
Definition: particledata.h:389
The Particles class abstracts the storage and manipulation of particles.
Definition: particles.h:33
size_t size() const
Definition: particles.h:87
A class that stores parameters of potentials, calculates potentials and their gradients.
Definition: potentials.h:36
A container for storing conserved values.
A container class to hold all the arrays on the lattice and access them.
Definition: lattice.h:47
String excitation processes used in SMASH.
Definition: stringprocess.h:45
ThermalizationAction implements forced thermalization as an Action class.
bool any_particles_thermalized() const
This method checks, if there are particles in the region to be thermalized.
The ThreeVector class represents a physical three-vector with the components .
Definition: threevector.h:31
@ Frozen
Use fermi motion without potentials.
TimeStepMode
The time step mode.
@ Fixed
Use fixed time step.
@ None
Don't use time steps; propagate from action to action.
@ EckartDensity
Density in the Eckart frame.
@ Tmn
Energy-momentum tensor in lab frame.
@ LandauVelocity
Velocity of the Landau rest frame.
@ j_QBS
Electric (Q), baryonic (B) and strange (S) currents.
@ TmnLandau
Energy-momentum tensor in Landau rest frame.
@ BottomUp
Sum the existing partial contributions.
@ Stochastic
Stochastic Criteiron.
@ None
No pseudo-resonance is created.
EventCounting
Defines how the number of events is determined.
@ Invalid
Unused, only in the code for internal logic.
@ FixedNumber
The desired number of events is simulated disregarding of whether an interaction took place.
@ MinimumNonEmpty
Events are simulated until there are at least a given number of ensembles in which an interaction too...
#define SMASH_SOURCE_LOCATION
Hackery that is required to output the location in the source code where the log statement occurs.
Definition: logging.h:153
std::ostream & operator<<(std::ostream &out, const ActionPtr &action)
Convenience: dereferences the ActionPtr to Action.
Definition: action.h:547
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:39
FormattingHelper< T > format(const T &value, const char *unit, int width=-1, int precision=-1)
Acts as a stream modifier for std::ostream to output an object with an optional suffix string and wit...
Definition: logging.h:217
std::unique_ptr< OutputInterface > create_oscar_output(const std::string &format, const std::string &content, const std::filesystem::path &path, const OutputParameters &out_par)
Definition: oscaroutput.cc:811
#define likely(x)
Tell the branch predictor that this expression is likely true.
Definition: macros.h:14
constexpr int p
Proton.
constexpr int n
Neutron.
Engine::result_type advance()
Advance the engine's state and return the generated value.
Definition: random.h:78
void set_seed(T &&seed)
Sets the seed of the random number engine.
Definition: random.h:71
Definition: action.h:24
static constexpr int LInitialConditions
Definition: experiment.h:90
void update_momenta(std::vector< Particles > &particles, double dt, const Potentials &pot, RectangularLattice< std::pair< ThreeVector, ThreeVector >> *FB_lat, RectangularLattice< std::pair< ThreeVector, ThreeVector >> *FI3_lat, RectangularLattice< std::pair< ThreeVector, ThreeVector >> *EM_lat, DensityLattice *jB_lat)
Updates the momenta of all particles at the current time step according to the equations of motion:
Definition: propagation.cc:111
UnaryFunction for_each(Container &&c, UnaryFunction &&f)
Convenience wrapper for std::for_each that operates on a complete container.
Definition: algorithms.h:96
EventInfo fill_event_info(const std::vector< Particles > &ensembles, double E_mean_field, double modus_impact_parameter, const ExperimentParameters &parameters, bool projectile_target_interact, bool kinematic_cut_for_SMASH_IC)
Generate the EventInfo object which is passed to outputs_.
Definition: experiment.cc:617
std::string format_measurements(const std::vector< Particles > &ensembles, uint64_t scatterings_this_interval, const QuantumNumbers &conserved_initial, SystemTimePoint time_start, double time, double E_mean_field, double E_mean_field_initial)
Generate a string which will be printed to the screen when SMASH is running.
Definition: experiment.cc:349
void expand_space_time(Particles *particles, const ExperimentParameters &parameters, const ExpansionProperties &metric)
Modifies positions and momentum of all particles to account for space-time deformation.
Definition: propagation.cc:86
void update_lattice(RectangularLattice< T > *lat, const LatticeUpdate update, const DensityType dens_type, const DensityParameters &par, const std::vector< Particles > &ensembles, const bool compute_gradient)
Updates the contents on the lattice.
Definition: density.h:542
void check_interactions_total(uint64_t interactions_total)
Make sure interactions_total can be represented as a 32-bit integer.
Definition: experiment.h:2510
std::tuple< double, FourVector, ThreeVector, ThreeVector, FourVector, FourVector, FourVector, FourVector > current_eckart(const ThreeVector &r, const ParticleList &plist, const DensityParameters &par, DensityType dens_type, bool compute_gradient, bool smearing)
Calculates Eckart rest frame density and 4-current of a given density type and optionally the gradien...
Definition: density.cc:171
double propagate_straight_line(Particles *particles, double to_time, const std::vector< FourVector > &beam_momentum)
Propagates the positions of all particles on a straight line to a given moment.
Definition: propagation.cc:44
ExperimentParameters create_experiment_parameters(Configuration &config)
Gathers all general Experiment parameters.
Definition: experiment.cc:132
constexpr double very_small_double
A very small double, used to avoid division by zero.
Definition: constants.h:40
double calculate_mean_field_energy(const Potentials &potentials, RectangularLattice< smash::DensityOnLattice > &jmu_B_lat, RectangularLattice< std::pair< ThreeVector, ThreeVector >> *em_lattice, const ExperimentParameters &parameters)
Calculate the total mean field energy of the system; this will be printed to the screen when SMASH is...
Definition: experiment.cc:397
static constexpr int LExperiment
void update_fields_lattice(RectangularLattice< FieldsOnLattice > *fields_lat, RectangularLattice< FourVector > *old_fields, RectangularLattice< FourVector > *new_fields, RectangularLattice< std::array< FourVector, 4 >> *fields_four_grad_lattice, DensityLattice *jmu_B_lat, const LatticeUpdate fields_lat_update, const Potentials &potentials, const double time_step)
Updates the contents on the lattice of FieldsOnLattice type.
Definition: fields.cc:14
bool are_particles_identical_at_given_time(const ParticleData &p1, const ParticleData &p2, double time)
Utility function to compare two ParticleData instances with respect to their PDG code,...
void validate_and_adjust_particle_list(ParticleList &particle_list)
Validate a particle list adjusting each particle to be a valid SMASH particle.
Definition: experiment.cc:639
constexpr double nucleon_mass
Nucleon mass in GeV.
Definition: constants.h:58
LatticeUpdate
Enumerator option for lattice updates.
Definition: lattice.h:36
std::ostream & operator<<(std::ostream &out, const Experiment< Modus > &e)
Creates a verbose textual description of the setup of the Experiment.
Definition: experiment.h:713
Potentials * pot_pointer
Pointer to a Potential class.
static constexpr int LMain
Definition: experiment.h:89
constexpr double really_small
Numerical error tolerance.
Definition: constants.h:37
DensityType
Allows to choose which kind of density to calculate.
Definition: density.h:36
RectangularLattice< FourVector > * UB_lat_pointer
Pointer to the skyrme potential on the lattice.
std::chrono::time_point< std::chrono::system_clock > SystemTimePoint
Type (alias) that is used to store the current time.
Definition: chrono.h:22
const std::string hline(113, '-')
String representing a horizontal line.
RectangularLattice< FourVector > * UI3_lat_pointer
Pointer to the symmmetry potential on the lattice.
constexpr double fm2_mb
mb <-> fm^2 conversion factor.
Definition: constants.h:28
Structure to contain custom data for output.
Struct containing the type of the metric and the expansion parameter of the metric.
Definition: propagation.h:26
Exception class that is thrown if an invalid modus is requested from the Experiment factory.
Definition: experiment.h:144
Exception class that is thrown if the requested output path in the Experiment factory is not existing...
Definition: experiment.h:153
Helper structure for Experiment.
double fixed_min_cell_length
Fixed minimal grid cell length (in fm).
const CollisionCriterion coll_crit
Employed collision criterion.
std::unique_ptr< Clock > outputclock
Output clock to keep track of the next output time.
static const Key< PseudoResonance > collTerm_pseudoresonance
See user guide description for more information.
Definition: input_keys.h:2093
static const Key< TotalCrossSectionStrategy > collTerm_totXsStrategy
See user guide description for more information.
Definition: input_keys.h:2059
Helper structure for Experiment to hold output options and parameters.
RivetOutputParameters rivet_parameters
Rivet specfic parameters.