1515
1616#include " RMGHardware.hh"
1717
18+ #include < algorithm>
1819#include < filesystem>
1920#include < format>
2021namespace fs = std::filesystem;
@@ -42,6 +43,8 @@ namespace fs = std::filesystem;
4243#include " RMGTools.hh"
4344#include " RMGVertexOutputScheme.hh"
4445
46+ #include " fmt/ranges.h"
47+
4548#if RMG_HAS_GDML
4649#include " G4GDMLParser.hh"
4750#endif
@@ -52,6 +55,9 @@ G4ThreadLocal std::vector<std::shared_ptr<RMGVOutputScheme>> RMGHardware::fActiv
5255
5356G4ThreadLocal bool RMGHardware::fActiveDetectorsInitialized = false ;
5457
58+ std::unordered_map<const G4LogicalVolume*, std::set<std::string>>
59+ RMGHardware::fLogicalVolEminParticles = {};
60+
5561RMGHardware::RMGHardware () { this ->DefineCommands (); }
5662
5763G4VPhysicalVolume* RMGHardware::Construct () {
@@ -166,11 +172,65 @@ G4VPhysicalVolume* RMGHardware::Construct() {
166172 );
167173 } else {
168174 for (const auto & vol : volumes) {
169- vol->GetLogicalVolume ()->SetUserLimits (new G4UserLimits (el.second ));
175+ G4LogicalVolume* logical = vol->GetLogicalVolume ();
176+ auto userLimits = logical->GetUserLimits ();
177+ if (!userLimits) { userLimits = new G4UserLimits (); }
178+ userLimits->SetMaxAllowedStep (el.second );
179+ logical->SetUserLimits (userLimits);
170180 }
171181 }
172182 }
173183
184+ // attach particle-selective user min kinetic energy cuts to logical volumes
185+ fLogicalVolEminParticles .clear ();
186+ std::unordered_map<const G4LogicalVolume*, double > logical_ekin_limits;
187+ for (const auto & [vol_name, cfg] : fPhysVolEminLimits ) {
188+ RMGLog::OutFormat (
189+ RMGLog::debug,
190+ " Setting selective min user kinetic energy for volume '{}' to {} ({})" ,
191+ vol_name,
192+ cfg.ekin_min ,
193+ fmt::join (cfg.particles , " ," )
194+ );
195+ auto volumes = RMGNavigationTools::FindPhysicalVolume (vol_name);
196+ if (volumes.empty ()) {
197+ RMGLog::Out (
198+ RMGLog::error,
199+ " No matching volumes for '{}' found, skipping selective user ekin min limit setting" ,
200+ vol_name
201+ );
202+ continue ;
203+ }
204+
205+ for (const auto & vol : volumes) {
206+ G4LogicalVolume* logical = vol->GetLogicalVolume ();
207+
208+ const auto ekin_it = logical_ekin_limits.find (logical);
209+ if (ekin_it != logical_ekin_limits.end () && ekin_it->second != cfg.ekin_min ) {
210+ RMGLog::OutFormat (
211+ RMGLog::error,
212+ " Conflicting selective ekin min limits for logical volume {} ({} vs {} MeV), "
213+ " skipping registration from pattern '{}'" ,
214+ logical->GetName (),
215+ ekin_it->second ,
216+ cfg.ekin_min ,
217+ vol_name
218+ );
219+ continue ;
220+ }
221+
222+ auto userLimits = logical->GetUserLimits ();
223+ if (!userLimits) userLimits = new G4UserLimits ();
224+ userLimits->SetUserMinEkine (cfg.ekin_min );
225+ logical->SetUserLimits (userLimits);
226+
227+ logical_ekin_limits.emplace (logical, cfg.ekin_min );
228+
229+ auto [it, inserted] = fLogicalVolEminParticles .try_emplace (logical, cfg.particles );
230+ if (!inserted) { it->second .insert (cfg.particles .begin (), cfg.particles .end ()); }
231+ }
232+ }
233+
174234 // register staged detectors now
175235 if (!fStagedDetectors .empty ()) {
176236 RMGLog::Out (RMGLog::debug, " Registering staged detectors" );
@@ -489,6 +549,48 @@ void RMGHardware::SetMaxStepLimit(double max_step, std::string name) {
489549 RMGLog::OutFormat (RMGLog::detail, " Set step limits for {:s} to {:.2f} mm" , name, max_step);
490550}
491551
552+ void RMGHardware::SetEminLimitForParticle (double ekin_min, std::string name, std::string particle_name) {
553+ if (particle_name.empty ()) {
554+ RMGLog::Out (
555+ RMGLog::error,
556+ " No particle name was provided for selective ekin min limit, ignoring command"
557+ );
558+ return ;
559+ }
560+
561+ auto [it, inserted] = fPhysVolEminLimits .try_emplace (name, RMGSelectiveEminLimit{ekin_min, {}});
562+ if (!inserted && it->second .ekin_min != ekin_min) {
563+ RMGLog::OutFormat (
564+ RMGLog::error,
565+ " Conflicting selective ekin min limit for volume {:s}: existing {:.2f} MeV, requested "
566+ " {:.2f} MeV" ,
567+ name,
568+ it->second .ekin_min ,
569+ ekin_min
570+ );
571+ return ;
572+ }
573+
574+ it->second .particles .insert (particle_name);
575+ RMGLog::OutFormat (
576+ RMGLog::detail,
577+ " Set selective ekin min limit for {:s} to {:.2f} MeV (particle: {})" ,
578+ name,
579+ ekin_min,
580+ particle_name
581+ );
582+ }
583+
584+ bool RMGHardware::IsEminLimitParticleSelected (
585+ const G4LogicalVolume* logical,
586+ const std::string& particle_name
587+ ) {
588+ if (!logical) return false ;
589+ const auto it = fLogicalVolEminParticles .find (logical);
590+ if (it == fLogicalVolEminParticles .end ()) return false ;
591+ return it->second .find (particle_name) != it->second .end ();
592+ }
593+
492594void RMGHardware::RegisterDetectorsFromGDML (std::string s) {
493595 if (s == " All" ) {
494596 for (const auto dt : magic_enum::enum_values<RMGDetectorType>()) {
0 commit comments