File indexing completed on 2026-10-20 08:45:50
0001 #include "simphony.h"
0002
0003 #include <atomic>
0004 #include <mutex>
0005 #include <optional>
0006 #include <stdexcept>
0007 #include <string>
0008
0009 #include "CSGFoundry.h"
0010 #include "CSGOptiX.h"
0011 #include "SEventConfig.hh"
0012 #include "SLOG.hh"
0013 #include "SSim.hh"
0014 #include "U4GDML.h"
0015 #include "U4Tree.h"
0016
0017 namespace
0018 {
0019 struct Runtime
0020 {
0021 std::mutex mutex;
0022 std::atomic<bool> initialized{false};
0023 std::atomic<bool> gpu_available{false};
0024 const G4VPhysicalVolume* world = nullptr;
0025 simphony::GeometryOptions options;
0026 U4Tree* tree = nullptr;
0027 CSGFoundry* foundry = nullptr;
0028 CSGOptiX* backend = nullptr;
0029 std::optional<int> pending_event;
0030 bool initialization_failed = false;
0031 };
0032
0033 Runtime& runtime()
0034 {
0035 static Runtime instance;
0036 return instance;
0037 }
0038
0039 bool same_options(
0040 const simphony::GeometryOptions& lhs,
0041 const simphony::GeometryOptions& rhs) noexcept
0042 {
0043 return lhs.sensor_identifier == rhs.sensor_identifier && lhs.gpu == rhs.gpu;
0044 }
0045
0046 void require_initialized(const Runtime& state)
0047 {
0048 if (!state.initialized.load(std::memory_order_acquire))
0049 throw std::logic_error("simphony is not initialized");
0050 }
0051
0052 void require_gpu(const Runtime& state)
0053 {
0054 require_initialized(state);
0055 if (state.backend == nullptr)
0056 throw std::logic_error("simphony has no GPU backend");
0057 }
0058 }
0059
0060 namespace simphony
0061 {
0062 void initialize(const G4VPhysicalVolume* world, const GeometryOptions& options)
0063 {
0064 if (world == nullptr)
0065 throw std::invalid_argument("simphony::initialize requires a non-null Geant4 world");
0066
0067 Runtime& state = runtime();
0068 std::lock_guard lock(state.mutex);
0069
0070 if (state.initialization_failed)
0071 throw std::logic_error("simphony initialization previously failed; restart the process before retrying");
0072
0073 if (state.initialized.load(std::memory_order_acquire))
0074 {
0075 if (state.world == world && same_options(state.options, options))
0076 return;
0077 throw std::logic_error("simphony is already initialized with different geometry or options");
0078 }
0079
0080 bool create_gpu = false;
0081 if (options.gpu != GpuRequirement::Disabled)
0082 {
0083 if (SEventConfig::CONTEXT == nullptr)
0084 SEventConfig::Initialize();
0085 create_gpu = SEventConfig::HasDevice();
0086 if (options.gpu == GpuRequirement::Required && !create_gpu)
0087 throw std::runtime_error("simphony requires a CUDA-capable GPU, but none is available");
0088 }
0089
0090 try
0091 {
0092 SSim* simulation = SSim::CreateOrReuse();
0093 if (simulation == nullptr)
0094 throw std::runtime_error("simphony failed to create its simulation state");
0095
0096 U4Tree* tree = U4Tree::Create(
0097 simulation->get_tree(), world, options.sensor_identifier);
0098 if (tree == nullptr)
0099 throw std::runtime_error("simphony failed to translate the Geant4 geometry");
0100
0101 simulation->initSceneFromTree();
0102 CSGFoundry* foundry = CSGFoundry::CreateFromSim();
0103 if (foundry == nullptr)
0104 throw std::runtime_error("simphony failed to create its CSG geometry");
0105
0106 CSGOptiX* backend = create_gpu ? CSGOptiX::Create(foundry) : nullptr;
0107 if (create_gpu && backend == nullptr)
0108 throw std::runtime_error("simphony failed to create its GPU backend");
0109
0110 state.world = world;
0111 state.options = options;
0112 state.tree = tree;
0113 state.foundry = foundry;
0114 state.backend = backend;
0115 state.gpu_available.store(backend != nullptr, std::memory_order_release);
0116 state.initialized.store(true, std::memory_order_release);
0117 }
0118 catch (...)
0119 {
0120 state.initialization_failed = true;
0121 throw;
0122 }
0123 }
0124
0125 bool is_initialized() noexcept
0126 {
0127 return runtime().initialized.load(std::memory_order_acquire);
0128 }
0129
0130 bool has_gpu() noexcept
0131 {
0132 return runtime().gpu_available.load(std::memory_order_acquire);
0133 }
0134
0135 void simulate(int event_id, bool reset_after)
0136 {
0137 if (event_id < 0)
0138 throw std::invalid_argument("simphony::simulate requires a non-negative event id");
0139
0140 Runtime& state = runtime();
0141 std::lock_guard lock(state.mutex);
0142 require_gpu(state);
0143
0144 if (state.pending_event.has_value())
0145 throw std::logic_error("simphony has an event awaiting reset");
0146
0147 state.backend->simulate(event_id, reset_after);
0148 if (!reset_after)
0149 state.pending_event = event_id;
0150 }
0151
0152 void reset(int event_id)
0153 {
0154 if (event_id < 0)
0155 throw std::invalid_argument("simphony::reset requires a non-negative event id");
0156
0157 Runtime& state = runtime();
0158 std::lock_guard lock(state.mutex);
0159 require_gpu(state);
0160
0161 if (!state.pending_event.has_value())
0162 throw std::logic_error("simphony has no event awaiting reset");
0163 if (*state.pending_event != event_id)
0164 throw std::logic_error("simphony reset event id does not match the pending event");
0165
0166 state.backend->reset(event_id);
0167 state.pending_event.reset();
0168 }
0169
0170 void save_geometry(const std::filesystem::path& directory)
0171 {
0172 if (directory.empty())
0173 throw std::invalid_argument("simphony::save_geometry requires a destination directory");
0174
0175 Runtime& state = runtime();
0176 std::lock_guard lock(state.mutex);
0177 require_initialized(state);
0178
0179 const std::string base = directory.string();
0180 U4GDML::Write(state.world, base.c_str(), "origin.gdml");
0181 state.foundry->save(base.c_str());
0182 }
0183 }