Skip to content

Commit 83f2aa3

Browse files
feat: implemented a wrapper around the UserSpecialCuts for EkinMin but with additional filtering for particle type
1 parent 8788cf4 commit 83f2aa3

File tree

9 files changed

+426
-2
lines changed

9 files changed

+426
-2
lines changed

include/RMGHardware.hh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <memory>
2121
#include <set>
2222
#include <string>
23+
#include <unordered_map>
2324
#include <vector>
2425

2526
#include "G4GenericMessenger.hh"
@@ -33,6 +34,7 @@
3334
#include "RMGVOutputScheme.hh"
3435

3536
/** @brief Class to handle the detector geometry hardware, extends @c G4VUserDetectorConstruction. */
37+
class G4LogicalVolume;
3638
class G4VPhysicalVolume;
3739
class RMGHardware : public G4VUserDetectorConstruction {
3840

@@ -158,6 +160,23 @@ class RMGHardware : public G4VUserDetectorConstruction {
158160
*/
159161
void SetMaxStepLimit(double max_step, std::string name);
160162

163+
/** @brief Set a minimum kinetic energy cut for one selected particle in a volume.
164+
*
165+
* @details This sets @c G4UserLimits::SetUserMinEkine on matching logical volumes, but the
166+
* corresponding cut process is applied only to registered particles.
167+
*
168+
* @param ekin_min Minimum kinetic energy.
169+
* @param name Physical volume name or regex.
170+
* @param particle_name Geant4 particle name.
171+
*/
172+
void SetEminLimitForParticle(double ekin_min, std::string name, std::string particle_name);
173+
174+
/** @brief Check if selective EkinMin should be applied for the current track context. */
175+
static bool IsEminLimitParticleSelected(
176+
const G4LogicalVolume* logical,
177+
const std::string& particle_name
178+
);
179+
161180
void PrintListOfLogicalVolumes() { RMGNavigationTools::PrintListOfLogicalVolumes(); }
162181
void PrintListOfPhysicalVolumes() { RMGNavigationTools::PrintListOfPhysicalVolumes(); }
163182

@@ -176,6 +195,13 @@ class RMGHardware : public G4VUserDetectorConstruction {
176195
/// Mapping between physical volume names and maximum (user) step size to apply
177196
std::map<std::string, double> fPhysVolStepLimits;
178197

198+
struct RMGSelectiveEminLimit {
199+
double ekin_min;
200+
std::set<std::string> particles;
201+
};
202+
std::map<std::string, RMGSelectiveEminLimit> fPhysVolEminLimits;
203+
static std::unordered_map<const G4LogicalVolume*, std::set<std::string>> fLogicalVolEminParticles;
204+
179205
// one element for each sensitive detector physical volume
180206
std::map<std::pair<std::string, int>, RMGDetectorMetadata> fDetectorMetadata;
181207

include/RMGHardwareMessenger.hh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,19 @@ class RMGHardwareMessenger : public G4UImessenger {
4545
/** @brief Define the commands to set the step limits. */
4646
void DefineStepLimits();
4747

48+
/** @brief Define the commands to set particle-selective minimum kinetic energy limits. */
49+
void DefineSelectiveEminLimits();
50+
4851
private:
4952

5053
RMGHardware* fHardware;
5154
G4UIcommand* fRegisterCmd = nullptr;
5255
G4UIcmdWithADoubleAndUnit* fStepLimitsCmd = nullptr;
56+
G4UIcommand* fSelectiveEminLimitCmd = nullptr;
5357

5458
void RegisterDetectorCmd(const std::string& parameters);
5559
void StepLimitsCmd(const std::string& parameters);
60+
void SelectiveEminLimitCmd(const std::string& parameters);
5661
};
5762

5863
#endif
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (C) 2026
2+
//
3+
// This program is free software: you can redistribute it and/or modify it under
4+
// the terms of the GNU Lesser General Public License as published by the Free
5+
// Software Foundation, either version 3 of the License, or (at your option) any
6+
// later version.
7+
8+
#ifndef _RMG_SELECTIVE_EKIN_MIN_CUT_PROCESS_HH_
9+
#define _RMG_SELECTIVE_EKIN_MIN_CUT_PROCESS_HH_
10+
11+
#include "G4UserSpecialCuts.hh"
12+
13+
class RMGSelectiveEkinMinCutProcess : public G4UserSpecialCuts {
14+
15+
public:
16+
17+
explicit RMGSelectiveEkinMinCutProcess(const G4String& process_name = "UserSpecialCut");
18+
~RMGSelectiveEkinMinCutProcess() override = default;
19+
20+
G4double PostStepGetPhysicalInteractionLength(
21+
const G4Track& aTrack,
22+
G4double previousStepSize,
23+
G4ForceCondition* condition
24+
) override;
25+
};
26+
27+
#endif

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ set(PROJECT_PUBLIC_HEADERS
4141
${_root}/include/RMGRun.hh
4242
${_root}/include/RMGScintillatorDetector.hh
4343
${_root}/include/RMGScintillatorOutputScheme.hh
44+
${_root}/include/RMGSelectiveEkinMinCutProcess.hh
4445
${_root}/include/RMGStackingAction.hh
4546
${_root}/include/RMGSteppingAction.hh
4647
${_root}/include/RMGTools.hh
@@ -93,6 +94,7 @@ set(PROJECT_SOURCES
9394
${_root}/src/RMGRunAction.cc
9495
${_root}/src/RMGScintillatorDetector.cc
9596
${_root}/src/RMGScintillatorOutputScheme.cc
97+
${_root}/src/RMGSelectiveEkinMinCutProcess.cc
9698
${_root}/src/RMGStackingAction.cc
9799
${_root}/src/RMGSteppingAction.cc
98100
${_root}/src/RMGTrackingAction.cc

src/RMGHardware.cc

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "RMGHardware.hh"
1717

18+
#include <algorithm>
1819
#include <filesystem>
1920
#include <format>
2021
namespace 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

5356
G4ThreadLocal bool RMGHardware::fActiveDetectorsInitialized = false;
5457

58+
std::unordered_map<const G4LogicalVolume*, std::set<std::string>>
59+
RMGHardware::fLogicalVolEminParticles = {};
60+
5561
RMGHardware::RMGHardware() { this->DefineCommands(); }
5662

5763
G4VPhysicalVolume* 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+
492594
void RMGHardware::RegisterDetectorsFromGDML(std::string s) {
493595
if (s == "All") {
494596
for (const auto dt : magic_enum::enum_values<RMGDetectorType>()) {

src/RMGHardwareMessenger.cc

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,20 @@ RMGHardwareMessenger::RMGHardwareMessenger(RMGHardware* hw) : fHardware(hw) {
2525

2626
this->DefineRegisterDetector();
2727
this->DefineStepLimits();
28+
this->DefineSelectiveEminLimits();
2829
}
2930

30-
RMGHardwareMessenger::~RMGHardwareMessenger() { delete fRegisterCmd; }
31+
RMGHardwareMessenger::~RMGHardwareMessenger() {
32+
delete fRegisterCmd;
33+
delete fStepLimitsCmd;
34+
delete fSelectiveEminLimitCmd;
35+
}
3136

3237
void RMGHardwareMessenger::SetNewValue(G4UIcommand* command, G4String newValues) {
3338

3439
if (command == fRegisterCmd) RegisterDetectorCmd(newValues);
3540
else if (command == fStepLimitsCmd) StepLimitsCmd(newValues);
41+
else if (command == fSelectiveEminLimitCmd) SelectiveEminLimitCmd(newValues);
3642
}
3743

3844
void RMGHardwareMessenger::DefineRegisterDetector() {
@@ -89,6 +95,33 @@ void RMGHardwareMessenger::DefineStepLimits() {
8995
fStepLimitsCmd->AvailableForStates(G4State_PreInit);
9096
}
9197

98+
void RMGHardwareMessenger::DefineSelectiveEminLimits() {
99+
100+
fSelectiveEminLimitCmd = new G4UIcommand("/RMG/Geometry/SetEkinMinForParticle", this);
101+
fSelectiveEminLimitCmd->SetGuidance(
102+
"Sets minimum kinetic energy for one selected particle in a detector volume"
103+
);
104+
105+
auto p_ekin = new G4UIparameter("ekin_min", 'd', false);
106+
p_ekin->SetGuidance("minimum kinetic energy");
107+
fSelectiveEminLimitCmd->SetParameter(p_ekin);
108+
109+
auto p_unit = new G4UIparameter("unit", 's', false);
110+
p_unit->SetGuidance("energy unit");
111+
p_unit->SetParameterCandidates("eV keV MeV GeV TeV");
112+
fSelectiveEminLimitCmd->SetParameter(p_unit);
113+
114+
auto p_pv = new G4UIparameter("pv_name", 's', false);
115+
p_pv->SetGuidance("Detector physical volume, accepts regex patterns");
116+
fSelectiveEminLimitCmd->SetParameter(p_pv);
117+
118+
auto p_particle = new G4UIparameter("particle_name", 's', false);
119+
p_particle->SetGuidance("Geant4 particle name, e.g. e-, e+, gamma");
120+
fSelectiveEminLimitCmd->SetParameter(p_particle);
121+
122+
fSelectiveEminLimitCmd->AvailableForStates(G4State_PreInit);
123+
}
124+
92125
void RMGHardwareMessenger::RegisterDetectorCmd(const std::string& parameters) {
93126
G4Tokenizer next(parameters);
94127

@@ -117,4 +150,16 @@ void RMGHardwareMessenger::StepLimitsCmd(const std::string& parameters) {
117150
fHardware->SetMaxStepLimit(num, pv_name);
118151
}
119152

153+
void RMGHardwareMessenger::SelectiveEminLimitCmd(const std::string& parameters) {
154+
G4Tokenizer next(parameters);
155+
156+
auto x = next();
157+
auto y = next();
158+
auto num = G4UIcmdWithADoubleAndUnit::GetNewDoubleValue((x + " " + y).c_str());
159+
auto pv_name = next();
160+
auto particle_name = next();
161+
162+
fHardware->SetEminLimitForParticle(num, pv_name, particle_name);
163+
}
164+
120165
// vim: tabstop=2 shiftwidth=2 expandtab

src/RMGPhysics.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@
7070
#include "G4StepLimiterPhysics.hh"
7171
#include "G4StoppingPhysics.hh"
7272
#include "G4ThermalNeutrons.hh"
73+
#include "G4UserSpecialCuts.hh"
7374
#include "G4Version.hh"
7475

7576
#include "RMGConfig.hh"
7677
#include "RMGInnerBremsstrahlungProcess.hh"
7778
#include "RMGLog.hh"
7879
#include "RMGNeutronCaptureProcess.hh"
7980
#include "RMGOpWLSProcess.hh"
81+
#include "RMGSelectiveEkinMinCutProcess.hh"
8082
#include "RMGTools.hh"
8183

8284
namespace u = CLHEP;
@@ -360,6 +362,21 @@ void RMGPhysics::ConstructProcess() {
360362
// add step limits
361363
auto step_limits = new G4StepLimiterPhysics();
362364
step_limits->ConstructProcess();
365+
366+
// replace generic UserSpecialCuts with particle-selective variant used by
367+
// /RMG/Geometry/SetEkinMinForParticle.
368+
GetParticleIterator()->reset();
369+
while ((*GetParticleIterator())()) {
370+
auto particle = GetParticleIterator()->value();
371+
auto proc_manager = particle->GetProcessManager();
372+
if (!proc_manager) continue;
373+
374+
auto user_special_cut = proc_manager->GetProcess("UserSpecialCut");
375+
if (!user_special_cut) continue;
376+
377+
proc_manager->RemoveProcess(user_special_cut);
378+
proc_manager->AddDiscreteProcess(new RMGSelectiveEkinMinCutProcess());
379+
}
363380
}
364381

365382
void RMGPhysics::ConstructOptical() {

0 commit comments

Comments
 (0)