gphysics
Loading...
Searching...
No Matches
gphysicsRuntime.cc
Go to the documentation of this file.
1#include "gphysicsRuntime.h"
2#include <gemc/gbase/gbase.h>
3
4#include "G4AutoLock.hh"
5#include "G4BuilderType.hh"
6#include "G4MTRunManager.hh"
7#include "G4ParticleTable.hh"
8#include "G4PhysListFactoryAlt.hh"
9#include "G4PhysicsConstructorRegistry.hh"
10#include "G4ProcessManager.hh"
11#include "G4ProcessVector.hh"
12#include "G4RunManager.hh"
13#include "G4StateManager.hh"
14#include "G4StepLimiterPhysics.hh"
15#include "G4SystemOfUnits.hh"
16#include "G4UIcmdWithoutParameter.hh"
17#include "G4UIdirectory.hh"
18#include "G4UImanager.hh"
19#include "G4UImessenger.hh"
20#include "G4VPhysicsConstructor.hh"
21#include "G4VProcess.hh"
22
23#include <algorithm>
24#include <cctype>
25#include <cmath>
26#include <set>
27#include <stdexcept>
28#include <typeinfo>
29
30namespace {
31
32// Match G4VModularPhysicsList: hadronic constructors require serialized process construction.
33G4Mutex constructionMutex = G4MUTEX_INITIALIZER;
34
35bool editableState(std::string& error) {
36 if (G4Threading::IsWorkerThread()) {
37 error = "Physics changes must be requested on the master thread.";
38 return false;
39 }
40 const auto state = G4StateManager::GetStateManager()->GetCurrentState();
41 if (state == G4State_PreInit || state == G4State_Idle) { return true; }
42 error = "Physics can only be changed before initialization or between runs.";
43 return false;
44}
45
46void flushWorkerCommands() {
47 if (auto* master = dynamic_cast<G4MTRunManager*>(G4RunManager::GetRunManager())) {
48 master->RequestWorkersProcessCommandsStack();
49 }
50}
51
52template<typename Function>
53void forManagers(Function function) {
54 std::set<G4ProcessManager*> seen;
55 auto* iterator = G4ParticleTable::GetParticleTable()->GetIterator();
56 iterator->reset(false);
57 while ((*iterator)()) {
58 auto* manager = iterator->value()->GetProcessManager();
59 if (manager && seen.insert(manager).second) { function(manager); }
60 }
61}
62
63std::string category(const G4VProcess* process) {
64 // Geant4 labels these optical-photon production processes as electromagnetic.
65 if (process->GetProcessName() == "Cerenkov" || process->GetProcessName() == "Scintillation") {
66 return "Optical";
67 }
68 switch (process->GetProcessType()) {
69 case fTransportation: return "Transportation";
70 case fElectromagnetic: return "Electromagnetic";
71 case fOptical: return "Optical";
72 case fHadronic: return "Hadronic";
73 case fDecay: return "Decay";
74 case fGeneral: return "General";
75 case fParameterisation: return "Parameterisation";
76 case fParallel: return "Parallel worlds";
77 default: return "Other";
78 }
79}
80
81} // namespace
82
83class GPhysicsMessenger : public G4UImessenger {
84public:
85 explicit GPhysicsMessenger(gphysics::RuntimePhysicsList& input) : physics(input) {
86 directory = std::make_unique<G4UIdirectory>("/gemc/physics/");
87 clear = command("clearProcesses");
88 rebuild = command("rebuildProcesses");
89 activate = command("applyActivation");
90 }
91 void SetNewValue(G4UIcommand* input, G4String) override {
92 if (input == activate.get()) { physics.applyActivation(); }
93 else if (G4Threading::IsWorkerThread()) {
94 if (input == clear.get()) { physics.clearProcesses(); }
95 if (input == rebuild.get()) { physics.rebuildWorker(); }
96 }
97 }
98private:
99 std::unique_ptr<G4UIcmdWithoutParameter> command(const std::string& name) {
100 const auto path = "/gemc/physics/" + name;
101 auto result = std::make_unique<G4UIcmdWithoutParameter>(path.c_str(), this);
102 result->AvailableForStates(G4State_PreInit, G4State_Idle);
103 result->SetToBeBroadcasted(true);
104 return result;
105 }
106 gphysics::RuntimePhysicsList& physics;
107 std::unique_ptr<G4UIdirectory> directory;
108 std::unique_ptr<G4UIcmdWithoutParameter> clear, rebuild, activate;
109};
110
111namespace {
112G4ThreadLocal GPhysicsMessenger* workerMessenger = nullptr;
113}
114
115namespace gphysics {
116
118 std::string error;
119 if (!select(name, error)) { throw std::runtime_error(error); }
120 messenger = std::make_unique<GPhysicsMessenger>(*this);
121}
122
124 // The factory lists own their constructors; this vector is only the public modular-list view.
125 G4VMPLsubInstanceManager.offset[g4vmplInstanceID].physicsVector->clear();
126}
127
129 auto* manager = G4RunManager::GetRunManager();
130 return manager ? dynamic_cast<RuntimePhysicsList*>(
131 const_cast<G4VUserPhysicsList*>(manager->GetUserPhysicsList())) : nullptr;
132}
133
134std::vector<std::string> RuntimePhysicsList::referenceLists() {
135 g4alt::G4PhysListFactory factory;
136 const auto& names = factory.AvailablePhysLists();
137 return {names.begin(), names.end()};
138}
139
140std::vector<std::string> RuntimePhysicsList::constructors() {
141 const auto names = G4PhysicsConstructorRegistry::Instance()->AvailablePhysicsConstructors();
142 return {names.begin(), names.end()};
143}
144
145bool RuntimePhysicsList::select(const std::string& name, std::string& error) {
146 if (!editableState(error)) { return false; }
147 std::string normalized;
148 std::copy_if(name.begin(), name.end(), std::back_inserter(normalized),
149 [](unsigned char ch) { return !std::isspace(ch); });
150 if (normalized == selected) { return true; }
151 std::set<std::string> additions;
152 for (auto start = normalized.find('+'); start != std::string::npos;) {
153 const auto end = normalized.find('+', start + 1);
154 const auto constructor = normalized.substr(start + 1, end - start - 1);
155 if (constructor.empty() || !additions.insert(constructor).second) {
156 error = "Physics constructors must be nonempty and cannot be added twice.";
157 return false;
158 }
159 start = end;
160 }
161 g4alt::G4PhysListFactory factory;
162 if (!factory.IsReferencePhysList(normalized)) {
163 error = "Unknown physics list or constructor: " + name;
164 return false;
165 }
166 auto* stateManager = G4StateManager::GetStateManager();
167 const auto previousState = stateManager->GetCurrentState();
168 if (previousState == G4State_Idle) { flushWorkerCommands(); }
169 // Factory constructors register physics in PreInit. Workers are idle throughout this transaction.
170 stateManager->SetNewState(G4State_PreInit);
171 std::unique_ptr<G4VModularPhysicsList> next(factory.GetReferencePhysList(normalized));
172 next->RegisterPhysics(new G4StepLimiterPhysics());
173 auto reject = [&](const std::string& message) {
174 // List destruction would delete all particles. Retain the unused list until shutdown.
175 lists.push_back(std::move(next));
176 if (!selected.empty()) {
177 // Factory construction changes shared defaults; restore the active configuration's defaults.
178 lists.emplace_back(factory.GetReferencePhysList(selected));
179 }
180 stateManager->SetNewState(previousState);
181 error = message;
182 return false;
183 };
184 for (const auto& addition : additions) {
185 if (!addition.starts_with("G4")) { continue; } // Preserve the extensible factory's named aliases.
186 bool installed = false;
187 for (int i = 0; const auto* constructor = next->GetPhysics(i); ++i) {
188 installed |= demangle(typeid(*constructor).name()) == addition;
189 }
190 if (!installed) {
191 return reject("Constructor conflicts with the reference list: " + addition +
192 ". Select a reference-list variant or use replacement syntax instead.");
193 }
194 }
195 const auto* em = next->GetPhysicsWithType(bElectromagnetic);
196 const std::string identity = em ? std::string(typeid(*em).name()) + em->GetPhysicsName() : "";
197 if (previousState == G4State_Idle && identity != electromagneticIdentity) {
198 return reject("Changing the electromagnetic constructor after initialization requires restarting GEMC.");
199 }
200 stateManager->SetNewState(previousState);
201 if (previousState == G4State_Idle) {
202 broadcast("clearProcesses");
203 clearProcesses();
204 }
205 activeConstructors.clear();
206 for (int i = 0; const auto* constructor = next->GetPhysics(i); ++i) {
207 activeConstructors.push_back(const_cast<G4VPhysicsConstructor*>(constructor));
208 }
209 *G4VMPLsubInstanceManager.offset[g4vmplInstanceID].physicsVector = activeConstructors;
210 // Before a run manager exists there is no default region; defer region cuts until SetCuts().
211 defaultCutValue = productionCut.value_or(next->GetDefaultCutValue());
212 activeList = next.get();
213 lists.push_back(std::move(next));
214 selected = normalized;
215 electromagneticIdentity = identity;
216 if (previousState == G4State_Idle) {
217 SetDefaultCutValue(defaultCutValue);
219 auto* manager = G4RunManager::GetRunManager();
220 manager->InitializePhysics();
221 manager->PhysicsHasBeenModified();
222 broadcast("rebuildProcesses");
223 broadcast("applyActivation");
224 }
225 return true;
226}
227
229 for (auto* constructor : activeConstructors) { constructor->ConstructParticle(); }
230}
231
233 G4AutoLock lock(&constructionMutex);
234 // Constructors created after worker startup need fresh thread-local split-class slots.
235 const_cast<G4VPCManager&>(G4VPhysicsConstructor::GetSubInstanceManager()).NewSubInstances();
236 auto& state = threadState.Get();
237 if (!state.initialized) {
238 AddTransportation();
239 forManagers([&](G4ProcessManager* manager) {
240 for (int i = 0; i < manager->GetProcessListLength(); ++i) {
241 state.retained.insert((*manager->GetProcessList())[i]);
242 }
243 });
244 } else {
245 // New neutral particles, such as opticalphoton, also need the existing transportation process.
246 const auto transport = std::find_if(state.retained.begin(), state.retained.end(),
247 [](const auto* process) { return process->GetProcessType() == fTransportation; });
248 forManagers([&](G4ProcessManager* manager) {
249 if (manager->GetProcessListLength() == 0 && transport != state.retained.end()) {
250 manager->AddProcess(*transport);
251 manager->SetProcessOrderingToFirst(*transport, idxAlongStep);
252 manager->SetProcessOrderingToFirst(*transport, idxPostStep);
253 }
254 });
255 }
256 for (auto* constructor : activeConstructors) {
257 const bool electromagnetic = constructor->GetPhysicsType() == bElectromagnetic;
258 if (electromagnetic && state.initialized) { continue; }
259 std::set<G4VProcess*> before;
260 if (electromagnetic) {
261 forManagers([&](G4ProcessManager* manager) {
262 for (int i = 0; i < manager->GetProcessListLength(); ++i) {
263 before.insert((*manager->GetProcessList())[i]);
264 }
265 });
266 }
267 constructor->ConstructProcess();
268 if (electromagnetic) {
269 state.retainedConstructors.push_back(constructor);
270 forManagers([&](G4ProcessManager* manager) {
271 for (int i = 0; i < manager->GetProcessListLength(); ++i) {
272 auto* process = (*manager->GetProcessList())[i];
273 if (!before.contains(process)) { state.retained.insert(process); }
274 }
275 });
276 }
277 }
278 state.initialized = true;
279 applyActivation();
280}
281
283 activeList->SetCuts();
284 if (productionCut) { SetCutsWithDefault(); }
285}
286
288 G4VUserPhysicsList::InitializeWorker();
289 workerMessenger = new GPhysicsMessenger(*this);
290}
291
293 for (auto* constructor : activeConstructors) {
294 if (constructor->GetPhysicsType() != bElectromagnetic) { constructor->TerminateWorker(); }
295 }
296 for (auto* constructor : threadState.Get().retainedConstructors) { constructor->TerminateWorker(); }
297 G4VUserPhysicsList::TerminateWorker();
298 delete workerMessenger;
299 workerMessenger = nullptr;
300}
301
302void RuntimePhysicsList::clearProcesses() {
303 G4AutoLock lock(&constructionMutex);
304 std::set<G4VProcess*> processes;
305 const auto& retained = threadState.Get().retained;
306 std::map<G4ProcessManager*, G4ProcessManager*> replacements;
307 forManagers([&](G4ProcessManager* manager) {
308 // RemoveProcess leaves "ordered first/last" flags set in Geant4. Fresh managers reset that
309 // bookkeeping without replacing the EM process objects cached by G4LossTableManager.
310 auto* replacement = new G4ProcessManager(manager->GetParticleType());
311 replacement->SetVerboseLevel(manager->GetVerboseLevel());
312 replacements.emplace(manager, replacement);
313 for (int i = 0; i < manager->GetProcessListLength(); ++i) {
314 auto* process = (*manager->GetProcessList())[i];
315 if (!retained.contains(process)) { continue; }
316 const auto rest = manager->GetProcessOrdering(process, idxAtRest);
317 const auto along = manager->GetProcessOrdering(process, idxAlongStep);
318 const auto post = manager->GetProcessOrdering(process, idxPostStep);
319 replacement->AddProcess(process, rest, along, post);
320 for (const auto index : {idxAtRest, idxAlongStep, idxPostStep}) {
321 if (manager->GetProcessOrdering(process, index) == 0) {
322 replacement->SetProcessOrderingToFirst(process, index);
323 }
324 }
325 replacement->SetProcessActivation(process, manager->GetProcessActivation(i));
326 }
327 for (int i = manager->GetProcessListLength() - 1; i >= 0; --i) {
328 auto* process = (*manager->GetProcessList())[i];
329 if (!retained.contains(process)) {
330 processes.insert(process);
331 }
332 manager->RemoveProcess(process);
333 }
334 });
335 // General ions can share a manager. Update every particle alias before deleting each old manager once.
336 std::vector<std::pair<G4ParticleDefinition*, G4ProcessManager*>> particles;
337 auto* iterator = G4ParticleTable::GetParticleTable()->GetIterator();
338 iterator->reset(false);
339 while ((*iterator)()) {
340 auto* particle = iterator->value();
341 if (const auto found = replacements.find(particle->GetProcessManager()); found != replacements.end()) {
342 particles.emplace_back(particle, found->second);
343 }
344 }
345 for (const auto& [particle, replacement] : particles) {
346 particle->SetProcessManager(replacement);
347 if (!G4Threading::IsWorkerThread()) { particle->SetMasterProcessManager(replacement); }
348 }
349 for (const auto& [oldManager, replacement] : replacements) { delete oldManager; }
350 for (auto* process : processes) { delete process; }
351 if (G4Threading::IsWorkerThread()) {
352 for (auto* constructor : activeConstructors) {
353 if (constructor->GetPhysicsType() != bElectromagnetic) { constructor->TerminateWorker(); }
354 }
355 }
356}
357
358void RuntimePhysicsList::rebuildWorker() {
359 G4RunManager::GetRunManager()->InitializePhysics();
360 G4RunManager::GetRunManager()->PhysicsHasBeenModified();
361}
362
363void RuntimePhysicsList::broadcast(const std::string& command) {
364 if (!messenger) { return; }
365 const int status = G4UImanager::GetUIpointer()->ApplyCommand("/gemc/physics/" + command);
366 if (status) { throw std::runtime_error("Could not apply physics command: " + command); }
367 flushWorkerCommands();
368}
369
370bool RuntimePhysicsList::setProductionCut(double millimeters, std::string& error) {
371 if (!editableState(error)) { return false; }
372 if (!std::isfinite(millimeters) || millimeters <= 0) {
373 error = "Production cut must be a positive length in millimeters.";
374 return false;
375 }
376 if (G4StateManager::GetStateManager()->GetCurrentState() == G4State_Idle) {
377 // Preserve ordering with native /run/setCut commands waiting in the worker UI queues.
378 flushWorkerCommands();
379 }
380 productionCut = millimeters * mm;
381 defaultCutValue = *productionCut;
382 if (G4StateManager::GetStateManager()->GetCurrentState() == G4State_Idle) {
383 SetCutsWithDefault();
384 G4RunManager::GetRunManager()->PhysicsHasBeenModified();
385 }
386 return true;
387}
388
389std::vector<ProcessState> RuntimePhysicsList::processes() const {
390 std::map<std::string, ProcessState> result;
391 forManagers([&](G4ProcessManager* manager) {
392 for (int i = 0; i < manager->GetProcessListLength(); ++i) {
393 auto* process = (*manager->GetProcessList())[i];
394 const auto name = process->GetProcessName();
395 const bool active = manager->GetProcessActivation(i);
396 auto [it, inserted] = result.try_emplace(name, ProcessState{
397 name, category(process), active, false,
398 process->GetProcessType() != fTransportation, {}});
399 if (!inserted && active != it->second.active) { it->second.mixed = true; }
400 it->second.particles.push_back(manager->GetParticleType()->GetParticleName());
401 }
402 });
403 std::vector<ProcessState> states;
404 for (auto& [name, state] : result) {
405 std::sort(state.particles.begin(), state.particles.end());
406 state.particles.erase(std::unique(state.particles.begin(), state.particles.end()), state.particles.end());
407 states.push_back(std::move(state));
408 }
409 return states;
410}
411
412bool RuntimePhysicsList::setProcessEnabled(const std::string& name, bool enabled, std::string& error) {
413 if (!editableState(error)) { return false; }
414 const auto states = processes();
415 const auto found = std::find_if(states.begin(), states.end(),
416 [&](const auto& state) { return state.name == name; });
417 if (found == states.end() || !found->editable) {
418 error = "Process is unavailable or required for transportation: " + name;
419 return false;
420 }
421 activation[name] = enabled;
422 broadcast("applyActivation");
423 G4RunManager::GetRunManager()->PhysicsHasBeenModified();
424 return true;
425}
426
427void RuntimePhysicsList::applyActivation() {
428 forManagers([&](G4ProcessManager* manager) {
429 for (int i = 0; i < manager->GetProcessListLength(); ++i) {
430 const auto* process = (*manager->GetProcessList())[i];
431 const auto choice = activation.find(process->GetProcessName());
432 if (choice != activation.end() && process->GetProcessType() != fTransportation) {
433 manager->SetProcessActivation(i, choice->second);
434 }
435 }
436 });
437}
438
439} // namespace gphysics
void SetNewValue(G4UIcommand *input, G4String) override
GPhysicsMessenger(gphysics::RuntimePhysicsList &input)
RuntimePhysicsList(const std::string &selection)
static std::vector< std::string > referenceLists()
static std::vector< std::string > constructors()
bool setProductionCut(double millimeters, std::string &error)
static RuntimePhysicsList * current()
bool select(const std::string &selection, std::string &error)
std::vector< ProcessState > processes() const
bool setProcessEnabled(const std::string &name, bool enabled, std::string &error)
std::string demangle(const char *name)