File indexing completed on 2026-06-26 07:50:05
0001 #include <algorithm>
0002 #include <array>
0003 #include <cstdlib>
0004 #include <filesystem>
0005 #include <fstream>
0006 #include <iostream>
0007 #include <ranges>
0008 #include <sstream>
0009 #include <stdexcept>
0010 #include <string>
0011 #include <string_view>
0012 #include <sys/stat.h>
0013 #include <vector>
0014
0015 #include <cuda_runtime.h>
0016 #include <nlohmann/json.hpp>
0017
0018 #include "SEventConfig.hh"
0019
0020 #include "config.h"
0021 #include "config_path.h"
0022
0023 namespace gphox
0024 {
0025
0026 using namespace std;
0027
0028 constexpr const char* GPHOX_PTX_PATH_ENV = "CSGOptiX__optixpath";
0029
0030 namespace
0031 {
0032
0033 template <typename Enum>
0034 struct NamedEnumInfo
0035 {
0036 Enum value;
0037 std::string_view name;
0038 };
0039
0040 inline constexpr std::array EventModeInfos{
0041 NamedEnumInfo<EventMode>{EventMode::DebugHeavy, "DebugHeavy"},
0042 NamedEnumInfo<EventMode>{EventMode::DebugLite, "DebugLite"},
0043 NamedEnumInfo<EventMode>{EventMode::Nothing, "Nothing"},
0044 NamedEnumInfo<EventMode>{EventMode::Minimal, "Minimal"},
0045 NamedEnumInfo<EventMode>{EventMode::Hit, "Hit"},
0046 NamedEnumInfo<EventMode>{EventMode::HitPhoton, "HitPhoton"},
0047 NamedEnumInfo<EventMode>{EventMode::HitPhotonSeq, "HitPhotonSeq"},
0048 NamedEnumInfo<EventMode>{EventMode::HitSeq, "HitSeq"},
0049 };
0050
0051 inline constexpr std::array TorchGentypeInfos{
0052 NamedEnumInfo<unsigned>{OpticksGenstep_TORCH, "TORCH"},
0053 };
0054
0055 inline constexpr std::array TorchTypeInfos{
0056 NamedEnumInfo<unsigned>{T_DISC, "disc"},
0057 NamedEnumInfo<unsigned>{T_LINE, "line"},
0058 NamedEnumInfo<unsigned>{T_POINT, "point"},
0059 NamedEnumInfo<unsigned>{T_CIRCLE, "circle"},
0060 NamedEnumInfo<unsigned>{T_RECTANGLE, "rectangle"},
0061 NamedEnumInfo<unsigned>{T_SPHERE_MARSAGLIA, "sphere_marsaglia"},
0062 NamedEnumInfo<unsigned>{T_SPHERE, "sphere"},
0063 };
0064
0065 inline constexpr std::array ModeLiteInfos{
0066 NamedEnumInfo<ModeLite>{ModeLite::Unspecified, "Unspecified"},
0067 NamedEnumInfo<ModeLite>{ModeLite::Standard, "Standard"},
0068 NamedEnumInfo<ModeLite>{ModeLite::Lite, "Lite"},
0069 NamedEnumInfo<ModeLite>{ModeLite::DebugCompare, "DebugCompare"},
0070 };
0071
0072 inline constexpr std::array ModeMergeInfos{
0073 NamedEnumInfo<ModeMerge>{ModeMerge::Unspecified, "Unspecified"},
0074 NamedEnumInfo<ModeMerge>{ModeMerge::Separate, "Separate"},
0075 NamedEnumInfo<ModeMerge>{ModeMerge::Merged, "Merged"},
0076 };
0077
0078 bool FileExists(const std::string& path)
0079 {
0080 if (path.empty())
0081 return false;
0082 std::error_code ec;
0083 return std::filesystem::exists(path, ec) && !ec;
0084 }
0085
0086 std::vector<std::string> SplitSearchPaths(std::string_view paths)
0087 {
0088 std::vector<std::string> search_paths;
0089
0090 size_t last = 0;
0091 size_t next = 0;
0092 while ((next = paths.find(':', last)) != std::string_view::npos)
0093 {
0094 search_paths.push_back(std::string{paths.substr(last, next - last)});
0095 last = next + 1;
0096 }
0097
0098 search_paths.push_back(std::string{paths.substr(last)});
0099 return search_paths;
0100 }
0101
0102 template <typename Enum, size_t N>
0103 std::string ValidNamedEnumNames(const std::array<NamedEnumInfo<Enum>, N>& infos)
0104 {
0105 std::string names;
0106 for (const auto& info : infos)
0107 {
0108 if (!names.empty())
0109 names += ", ";
0110 names += info.name;
0111 }
0112 return names;
0113 }
0114
0115 template <typename Enum, size_t N>
0116 std::string_view NamedEnumName(
0117 Enum value,
0118 const std::array<NamedEnumInfo<Enum>, N>& infos,
0119 std::string_view fallback)
0120 {
0121 const auto it = std::ranges::find(infos, value, &NamedEnumInfo<Enum>::value);
0122 if (it != infos.end())
0123 return it->name;
0124
0125 return fallback;
0126 }
0127
0128 template <typename Enum, size_t N>
0129 Enum ReadNamedEnum(
0130 const nlohmann::json& object,
0131 const char* section,
0132 const char* key,
0133 const std::array<NamedEnumInfo<Enum>, N>& infos)
0134 {
0135 const std::string full_key = std::string{section} + "." + key;
0136 const auto& value = object.at(key);
0137 const std::string valid_names = ValidNamedEnumNames(infos);
0138
0139 if (!value.is_string())
0140 throw std::invalid_argument{"Invalid " + full_key + " value. Expected string name, one of: " + valid_names};
0141
0142 const std::string name = value.get<std::string>();
0143 const auto it = std::ranges::find(infos, std::string_view{name}, &NamedEnumInfo<Enum>::name);
0144 if (it != infos.end())
0145 return it->value;
0146
0147 throw std::invalid_argument{"Invalid " + full_key + " \"" + name + "\". Expected one of: " + valid_names};
0148 }
0149
0150 std::string_view EventModeName(EventMode mode)
0151 {
0152 return NamedEnumName(mode, EventModeInfos, "Minimal");
0153 }
0154
0155 template <typename T>
0156 void Assign(const nlohmann::json& object, const char* key, T& value)
0157 {
0158 if (const auto it = object.find(key); it != object.end())
0159 value = it->get<T>();
0160 }
0161
0162 void AssignFloat2(const nlohmann::json& object, const char* key, float2& value)
0163 {
0164 if (const auto it = object.find(key); it != object.end())
0165 value = make_float2((*it)[0].get<float>(), (*it)[1].get<float>());
0166 }
0167
0168 void AssignFloat3(const nlohmann::json& object, const char* key, float3& value)
0169 {
0170 if (const auto it = object.find(key); it != object.end())
0171 value = make_float3((*it)[0].get<float>(), (*it)[1].get<float>(), (*it)[2].get<float>());
0172 }
0173
0174 void AssignNormalizedFloat3(const nlohmann::json& object, const char* key, float3& value)
0175 {
0176 if (const auto it = object.find(key); it != object.end())
0177 {
0178 const float3 candidate = make_float3(
0179 (*it)[0].get<float>(),
0180 (*it)[1].get<float>(),
0181 (*it)[2].get<float>());
0182 constexpr float epsilon = 1e-12f;
0183 const float length_squared = candidate.x * candidate.x + candidate.y * candidate.y + candidate.z * candidate.z;
0184 if (length_squared <= epsilon)
0185 throw std::invalid_argument(std::string("Cannot normalize vector for key '") + key + "'");
0186
0187 value = normalize(candidate);
0188 }
0189 }
0190
0191 template <typename Enum, size_t N>
0192 void AssignNamedEnum(
0193 const nlohmann::json& object,
0194 const char* section,
0195 const char* key,
0196 Enum& value,
0197 const std::array<NamedEnumInfo<Enum>, N>& infos)
0198 {
0199 if (object.contains(key))
0200 value = ReadNamedEnum(object, section, key, infos);
0201 }
0202
0203 void AssignOutputDir(const nlohmann::json& event, std::filesystem::path& output_dir)
0204 {
0205 if (const auto it = event.find("output_dir"); it != event.end())
0206 output_dir = it->get<std::string>();
0207 }
0208
0209 }
0210
0211 Config::Config(std::string config_name) :
0212 name{config_name}
0213 {
0214 ReadConfig(Locate(name + ".json"));
0215 Apply();
0216 }
0217
0218 std::string Config::PtxPath(const std::string& ptx_name)
0219 {
0220 const char* env_path = std::getenv(GPHOX_PTX_PATH_ENV);
0221 if (env_path && FileExists(env_path))
0222 return env_path;
0223
0224 std::vector<std::string> candidates;
0225 for (const auto& dir : SplitSearchPaths(GPHOX_PTX_SEARCH_PATHS))
0226 {
0227 if (dir.empty())
0228 continue;
0229
0230 std::string candidate = (std::filesystem::path{dir} / ptx_name).string();
0231 candidates.push_back(candidate);
0232 if (FileExists(candidate))
0233 return candidate;
0234 }
0235
0236 std::stringstream errmsg;
0237 errmsg << "Could not resolve PTX file \"" << ptx_name << "\".\n"
0238 << "Expected one of:\n"
0239 << " - " << GPHOX_PTX_PATH_ENV << "=<path-to-ptx>\n";
0240 for (const auto& candidate : candidates)
0241 errmsg << " - " << candidate << "\n";
0242 throw std::runtime_error(errmsg.str());
0243 }
0244
0245 std::string Config::Locate(std::string filename) const
0246 {
0247 std::vector<std::string> search_paths;
0248
0249 const std::string user_dir{std::getenv("GPHOX_CONFIG_DIR") ? std::getenv("GPHOX_CONFIG_DIR") : ""};
0250
0251 if (user_dir.empty())
0252 {
0253 search_paths = SplitSearchPaths(GPHOX_CONFIG_SEARCH_PATHS);
0254 }
0255 else
0256 {
0257 search_paths.push_back(user_dir);
0258 }
0259
0260 struct stat buffer;
0261 std::string filepath{""};
0262 for (std::string path : search_paths)
0263 {
0264 std::string fpath{path + "/" + filename};
0265 if (stat(fpath.c_str(), &buffer) == 0)
0266 {
0267 filepath = fpath;
0268 break;
0269 }
0270 }
0271
0272 if (filepath.empty())
0273 {
0274 std::string errmsg{"Could not find config file \"" + filename + "\" in "};
0275 for (std::string path : search_paths) errmsg += (path + ":");
0276 throw std::runtime_error(errmsg);
0277 }
0278
0279 return filepath;
0280 }
0281
0282
0283
0284
0285 void Config::ReadConfig(std::string filepath)
0286 {
0287 nlohmann::json json;
0288
0289 try
0290 {
0291 std::ifstream ifs(filepath);
0292 ifs >> json;
0293
0294 if (const auto it = json.find("torch"); it != json.end())
0295 {
0296 const nlohmann::json& torch_ = *it;
0297
0298 AssignNamedEnum(torch_, "torch", "gentype", torch.gentype, TorchGentypeInfos);
0299 Assign(torch_, "trackid", torch.trackid);
0300 Assign(torch_, "matline", torch.matline);
0301 Assign(torch_, "numphoton", torch.numphoton);
0302 AssignFloat3(torch_, "pos", torch.pos);
0303 Assign(torch_, "time", torch.time);
0304 AssignNormalizedFloat3(torch_, "mom", torch.mom);
0305 Assign(torch_, "weight", torch.weight);
0306 AssignFloat3(torch_, "pol", torch.pol);
0307 Assign(torch_, "wavelength", torch.wavelength);
0308 AssignFloat2(torch_, "zenith", torch.zenith);
0309 AssignFloat2(torch_, "azimuth", torch.azimuth);
0310 Assign(torch_, "radius", torch.radius);
0311 Assign(torch_, "distance", torch.distance);
0312 Assign(torch_, "mode", torch.mode);
0313 AssignNamedEnum(torch_, "torch", "type", torch.type, TorchTypeInfos);
0314 }
0315
0316 if (const auto it = json.find("event"); it != json.end())
0317 {
0318 const nlohmann::json& event_ = *it;
0319
0320 Assign(event_, "max_bounce", max_bounce);
0321 Assign(event_, "max_genstep", max_genstep);
0322 Assign(event_, "maxslot", maxslot);
0323 AssignNamedEnum(event_, "event", "mode", event_mode, EventModeInfos);
0324 AssignNamedEnum(event_, "event", "mode_lite", mode_lite, ModeLiteInfos);
0325 AssignNamedEnum(event_, "event", "mode_merge", mode_merge, ModeMergeInfos);
0326 AssignOutputDir(event_, output_dir);
0327 Assign(event_, "propagate_epsilon", propagate_epsilon);
0328 Assign(event_, "propagate_epsilon0", propagate_epsilon0);
0329 Assign(event_, "propagate_epsilon0_mask", propagate_epsilon0_mask);
0330 }
0331 }
0332 catch (nlohmann::json::exception& e)
0333 {
0334 throw std::runtime_error{"Failed reading config parameters from " + filepath + "\n" + e.what()};
0335 }
0336 catch (std::invalid_argument& e)
0337 {
0338 throw std::runtime_error{"Invalid config value in " + filepath + "\n" + e.what()};
0339 }
0340 }
0341
0342 void Config::Apply() const
0343 {
0344 const std::string event_mode_name{EventModeName(event_mode)};
0345 const std::string output_dir_str = output_dir.string();
0346
0347 SEventConfig::SetMaxBounce(max_bounce);
0348 SEventConfig::SetMaxGenstep(max_genstep);
0349 SEventConfig::SetMaxSlot(maxslot);
0350 SEventConfig::SetEventMode(event_mode_name.c_str());
0351 if (mode_lite != ModeLite::Unspecified)
0352 SEventConfig::SetModeLite(static_cast<int>(mode_lite));
0353 if (mode_merge != ModeMerge::Unspecified)
0354 SEventConfig::SetModeMerge(static_cast<int>(mode_merge));
0355 SEventConfig::SetOutFold(output_dir_str.c_str());
0356 SEventConfig::SetPropagateEpsilon(propagate_epsilon);
0357 SEventConfig::SetPropagateEpsilon0(propagate_epsilon0);
0358 SEventConfig::SetPropagateEpsilon0Mask(propagate_epsilon0_mask.c_str());
0359 }
0360
0361 }