diff --git a/app/celer-g4/GlobalSetup.cc b/app/celer-g4/GlobalSetup.cc index 374ecdc32b..36b124f264 100644 --- a/app/celer-g4/GlobalSetup.cc +++ b/app/celer-g4/GlobalSetup.cc @@ -114,13 +114,14 @@ void GlobalSetup::ReadInput(std::string const& filename) { if (ends_with(filename, ".json")) { -#if CELERITAS_USE_JSON - using std::to_string; - CELER_LOG(status) << "Reading JSON input from '" << filename << "'"; std::ifstream infile(filename); CELER_VALIDATE(infile, << "failed to open '" << filename << "'"); +#if CELERITAS_USE_JSON nlohmann::json::parse(infile).get_to(input_); +#else + CELER_NOT_CONFIGURED("nlohmann_json"); +#endif // Input options if (CELERITAS_CORE_GEO == CELERITAS_CORE_GEO_ORANGE) @@ -146,9 +147,6 @@ void GlobalSetup::ReadInput(std::string const& filename) options_->cuda_heap_size = input_.cuda_heap_size; options_->sync = input_.sync; options_->default_stream = input_.default_stream; -#else - CELER_NOT_CONFIGURED("nlohmann_json"); -#endif } else if (ends_with(filename, ".mac")) { diff --git a/app/celer-g4/RunInputIO.json.cc b/app/celer-g4/RunInputIO.json.cc index d836cee1e8..9d2b6bb2a8 100644 --- a/app/celer-g4/RunInputIO.json.cc +++ b/app/celer-g4/RunInputIO.json.cc @@ -8,6 +8,7 @@ #include "RunInputIO.json.hh" #include "corecel/cont/ArrayIO.json.hh" +#include "corecel/io/JsonUtils.json.hh" #include "corecel/io/Logger.hh" #include "corecel/io/StringEnumMapper.hh" #include "corecel/sys/Environment.hh" @@ -54,13 +55,11 @@ void to_json(nlohmann::json& j, SensitiveDetectorType const& value) */ void from_json(nlohmann::json const& j, RunInput& v) { -#define RI_LOAD_OPTION(NAME) \ - do \ - { \ - if (j.contains(#NAME)) \ - j.at(#NAME).get_to(v.NAME); \ - } while (0) -#define RI_LOAD_REQUIRED(NAME) j.at(#NAME).get_to(v.NAME) +#define RI_LOAD_OPTION(NAME) CELER_JSON_LOAD_OPTION(j, v, NAME) +#define RI_LOAD_REQUIRED(NAME) CELER_JSON_LOAD_REQUIRED(j, v, NAME) + + // Check version (if available) + check_format(j, "celer-g4"); RI_LOAD_REQUIRED(geometry_file); RI_LOAD_OPTION(event_file); @@ -119,7 +118,7 @@ void from_json(nlohmann::json const& j, RunInput& v) #undef RI_LOAD_OPTION #undef RI_LOAD_REQUIRED - CELER_VALIDATE(v.event_file.empty() != !v.primary_options, + CELER_VALIDATE(v.event_file.empty() == static_cast(v.primary_options), << "either a HepMC3 filename or options to generate " "primaries must be provided (but not both)"); CELER_VALIDATE(v.physics_list == PhysicsListSelection::geant_physics_list @@ -138,15 +137,15 @@ void from_json(nlohmann::json const& j, RunInput& v) */ void to_json(nlohmann::json& j, RunInput const& v) { +#define RI_SAVE_OPTION(NAME) \ + CELER_JSON_SAVE_WHEN(j, v, NAME, v.NAME != default_args.NAME) +#define RI_SAVE(NAME) CELER_JSON_SAVE(j, v, NAME) + j = nlohmann::json::object(); RunInput const default_args; -#define RI_SAVE_OPTION(NAME) \ - do \ - { \ - if (!(v.NAME == default_args.NAME)) \ - j[#NAME] = v.NAME; \ - } while (0) -#define RI_SAVE(NAME) j[#NAME] = v.NAME + + // Save version and format type + save_format(j, "celer-g4"); RI_SAVE(geometry_file); RI_SAVE_OPTION(event_file); diff --git a/app/celer-g4/celer-g4.cc b/app/celer-g4/celer-g4.cc index 29d0d6f468..ac611498b4 100644 --- a/app/celer-g4/celer-g4.cc +++ b/app/celer-g4/celer-g4.cc @@ -275,6 +275,12 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; #endif } + if (celeritas::starts_with(filename, "--")) + { + CELER_LOG(critical) << "Unknown option \"" << filename << "\""; + celeritas::app::print_usage(argv[0]); + return EXIT_FAILURE; + } // Create params, which need to be shared with detectors as well as // initialization, and can be written for output diff --git a/app/celer-sim/RunnerInputIO.json.cc b/app/celer-sim/RunnerInputIO.json.cc index 7d202b26b6..88bb3970fb 100644 --- a/app/celer-sim/RunnerInputIO.json.cc +++ b/app/celer-sim/RunnerInputIO.json.cc @@ -10,8 +10,8 @@ #include #include "corecel/cont/ArrayIO.json.hh" +#include "corecel/io/JsonUtils.json.hh" #include "corecel/io/LabelIO.json.hh" -#include "corecel/io/Logger.hh" #include "corecel/io/StringEnumMapper.hh" #include "corecel/io/StringUtils.hh" #include "corecel/sys/EnvironmentIO.json.hh" @@ -52,23 +52,13 @@ namespace app */ void from_json(nlohmann::json const& j, RunnerInput& v) { -#define LDIO_LOAD_OPTION(NAME) \ - do \ - { \ - if (j.contains(#NAME)) \ - j.at(#NAME).get_to(v.NAME); \ - } while (0) -#define LDIO_LOAD_DEPRECATED(OLD, NEW) \ - do \ - { \ - if (j.contains(#OLD)) \ - { \ - CELER_LOG(warning) << "Deprecated option '" << #OLD << "': use '" \ - << #NEW << "' instead"; \ - j.at(#OLD).get_to(v.NEW); \ - } \ - } while (0) -#define LDIO_LOAD_REQUIRED(NAME) j.at(#NAME).get_to(v.NAME) +#define LDIO_LOAD_REQUIRED(NAME) CELER_JSON_LOAD_REQUIRED(j, v, NAME) +#define LDIO_LOAD_OPTION(NAME) CELER_JSON_LOAD_OPTION(j, v, NAME) +#define LDIO_LOAD_DEPRECATED(OLD, NEW) \ + CELER_JSON_LOAD_DEPRECATED(j, v, OLD, NEW) + + // Check version (if available) + check_format(j, "celer-sim"); LDIO_LOAD_OPTION(cuda_heap_size); LDIO_LOAD_OPTION(cuda_stack_size); @@ -127,6 +117,7 @@ void from_json(nlohmann::json const& j, RunnerInput& v) LDIO_LOAD_OPTION(track_order); LDIO_LOAD_OPTION(physics_options); +#undef LDIO_LOAD_DEPRECATED #undef LDIO_LOAD_OPTION #undef LDIO_LOAD_REQUIRED @@ -148,70 +139,60 @@ void from_json(nlohmann::json const& j, RunnerInput& v) */ void to_json(nlohmann::json& j, RunnerInput const& v) { +#define LDIO_SAVE(NAME) CELER_JSON_SAVE(j, v, NAME) +#define LDIO_SAVE_WHEN(NAME, COND) CELER_JSON_SAVE_WHEN(j, v, NAME, COND) +#define LDIO_SAVE_OPTION(NAME) \ + LDIO_SAVE_WHEN(NAME, v.NAME != default_args.NAME) + j = nlohmann::json::object(); RunnerInput const default_args; - //! Save if not unspecified or if applicable -#define LDIO_SAVE_OPTION(NAME) \ - do \ - { \ - if (v.NAME != default_args.NAME) \ - j[#NAME] = v.NAME; \ - } while (0) - //! Always save -#define LDIO_SAVE_REQUIRED(NAME) j[#NAME] = v.NAME + + // Save version and celer-sim format + save_format(j, "celer-sim"); LDIO_SAVE_OPTION(cuda_heap_size); LDIO_SAVE_OPTION(cuda_stack_size); - LDIO_SAVE_REQUIRED(environ); + LDIO_SAVE(environ); - LDIO_SAVE_REQUIRED(geometry_file); - LDIO_SAVE_REQUIRED(physics_file); + LDIO_SAVE(geometry_file); + LDIO_SAVE(physics_file); LDIO_SAVE_OPTION(event_file); - if (v.event_file.empty()) - { - LDIO_SAVE_REQUIRED(primary_options); - } + LDIO_SAVE_WHEN(primary_options, v.event_file.empty()); LDIO_SAVE_OPTION(mctruth_file); - if (!v.mctruth_file.empty()) - { - LDIO_SAVE_REQUIRED(mctruth_filter); - } - LDIO_SAVE_OPTION(simple_calo); - LDIO_SAVE_OPTION(action_diagnostic); - LDIO_SAVE_OPTION(step_diagnostic); + LDIO_SAVE_WHEN(mctruth_filter, !v.mctruth_file.empty()); + LDIO_SAVE(simple_calo); + LDIO_SAVE(action_diagnostic); + LDIO_SAVE(step_diagnostic); LDIO_SAVE_OPTION(step_diagnostic_bins); - LDIO_SAVE_OPTION(write_track_counts); + LDIO_SAVE(write_track_counts); - LDIO_SAVE_REQUIRED(seed); - LDIO_SAVE_REQUIRED(num_track_slots); + LDIO_SAVE(seed); + LDIO_SAVE(num_track_slots); LDIO_SAVE_OPTION(max_steps); - LDIO_SAVE_REQUIRED(initializer_capacity); - LDIO_SAVE_REQUIRED(max_events); - LDIO_SAVE_REQUIRED(secondary_stack_factor); - LDIO_SAVE_REQUIRED(use_device); - LDIO_SAVE_REQUIRED(sync); - LDIO_SAVE_REQUIRED(merge_events); - LDIO_SAVE_REQUIRED(default_stream); - LDIO_SAVE_REQUIRED(warm_up); + LDIO_SAVE(initializer_capacity); + LDIO_SAVE(max_events); + LDIO_SAVE(secondary_stack_factor); + LDIO_SAVE(use_device); + LDIO_SAVE(sync); + LDIO_SAVE(merge_events); + LDIO_SAVE(default_stream); + LDIO_SAVE(warm_up); LDIO_SAVE_OPTION(field); - if (v.field != RunnerInput::no_field()) - { - LDIO_SAVE_REQUIRED(field_options); - } + LDIO_SAVE_WHEN(field_options, v.field != RunnerInput::no_field()); LDIO_SAVE_OPTION(step_limiter); - LDIO_SAVE_REQUIRED(brem_combined); + LDIO_SAVE(brem_combined); - LDIO_SAVE_REQUIRED(track_order); - if (v.physics_file.empty() || !ends_with(v.physics_file, ".root")) - { - LDIO_SAVE_REQUIRED(physics_options); - } + LDIO_SAVE(track_order); + LDIO_SAVE_WHEN(physics_options, + v.physics_file.empty() + || !ends_with(v.physics_file, ".root")); #undef LDIO_SAVE_OPTION -#undef LDIO_SAVE_REQUIRED +#undef LDIO_SAVE_WHEN +#undef LDIO_SAVE } //---------------------------------------------------------------------------// diff --git a/app/celer-sim/simple-driver.py b/app/celer-sim/simple-driver.py index f5136e3ee2..14f1c7fbbb 100755 --- a/app/celer-sim/simple-driver.py +++ b/app/celer-sim/simple-driver.py @@ -102,6 +102,7 @@ def strtobool(text): 'default_stream': False, 'brem_combined': True, 'physics_options': physics_options, + 'field': None, } with open(f'{run_name}.inp.json', 'w') as f: diff --git a/src/accel/SharedParams.cc b/src/accel/SharedParams.cc index 017df5212c..a05aada418 100644 --- a/src/accel/SharedParams.cc +++ b/src/accel/SharedParams.cc @@ -606,7 +606,7 @@ void SharedParams::try_output() const if (CELERITAS_USE_JSON && !params_ && filename.empty()) { // Setup was not called but JSON is available: make a default filename - filename = "celeritas.json"; + filename = "celeritas.out.json"; CELER_LOG(debug) << "Set default Celeritas output filename"; } @@ -619,13 +619,25 @@ void SharedParams::try_output() const if (CELERITAS_USE_JSON) { - CELER_LOG(info) << "Writing Geant4 diagnostic output to \"" << filename - << '"'; - - std::ofstream outf(filename); - CELER_VALIDATE( - outf, << "failed to open output file at \"" << filename << '"'); - output_reg_->output(&outf); + auto msg = CELER_LOG(info); + msg << "Wrote Geant4 diagnostic output to "; + std::ofstream outf; + std::ostream* os{nullptr}; + if (filename == "-") + { + os = &std::cout; + msg << ""; + } + else + { + os = &outf; + outf.open(filename); + CELER_VALIDATE( + outf, << "failed to open output file at \"" << filename << '"'); + msg << '"' << filename << '"'; + } + CELER_ASSERT(os); + output_reg_->output(os); } else { diff --git a/src/celeritas/ext/GeantPhysicsOptionsIO.json.cc b/src/celeritas/ext/GeantPhysicsOptionsIO.json.cc index 60eb58f392..2e16f80b32 100644 --- a/src/celeritas/ext/GeantPhysicsOptionsIO.json.cc +++ b/src/celeritas/ext/GeantPhysicsOptionsIO.json.cc @@ -9,6 +9,7 @@ #include +#include "corecel/io/JsonUtils.json.hh" #include "corecel/io/StringEnumMapper.hh" #include "corecel/math/QuantityIO.json.hh" @@ -62,13 +63,10 @@ void to_json(nlohmann::json& j, RelaxationSelection const& value) */ void from_json(nlohmann::json const& j, GeantPhysicsOptions& options) { +#define GPO_LOAD_OPTION(NAME) CELER_JSON_LOAD_OPTION(j, options, NAME) + options = {}; -#define GPO_LOAD_OPTION(NAME) \ - do \ - { \ - if (j.contains(#NAME)) \ - j.at(#NAME).get_to(options.NAME); \ - } while (0) + GPO_LOAD_OPTION(coulomb_scattering); GPO_LOAD_OPTION(compton_scattering); GPO_LOAD_OPTION(photoelectric); @@ -108,8 +106,10 @@ void from_json(nlohmann::json const& j, GeantPhysicsOptions& options) */ void to_json(nlohmann::json& j, GeantPhysicsOptions const& options) { +#define GPO_SAVE_OPTION(NAME) CELER_JSON_SAVE(j, options, NAME) + j = nlohmann::json::object(); -#define GPO_SAVE_OPTION(NAME) j[#NAME] = options.NAME + GPO_SAVE_OPTION(coulomb_scattering); GPO_SAVE_OPTION(compton_scattering); GPO_SAVE_OPTION(photoelectric); diff --git a/src/celeritas/user/RootStepWriterIO.json.cc b/src/celeritas/user/RootStepWriterIO.json.cc index 718f4cf2e3..eb8152e415 100644 --- a/src/celeritas/user/RootStepWriterIO.json.cc +++ b/src/celeritas/user/RootStepWriterIO.json.cc @@ -9,6 +9,8 @@ #include +#include "corecel/io/JsonUtils.json.hh" + #include "RootStepWriter.hh" namespace celeritas @@ -19,12 +21,7 @@ namespace celeritas */ void from_json(nlohmann::json const& j, SimpleRootFilterInput& options) { -#define SRFI_LOAD_OPTION(NAME) \ - do \ - { \ - if (j.contains(#NAME)) \ - j.at(#NAME).get_to(options.NAME); \ - } while (0) +#define SRFI_LOAD_OPTION(NAME) CELER_JSON_LOAD_OPTION(j, options, NAME) SRFI_LOAD_OPTION(track_id); SRFI_LOAD_OPTION(event_id); SRFI_LOAD_OPTION(parent_id); @@ -38,13 +35,10 @@ void from_json(nlohmann::json const& j, SimpleRootFilterInput& options) */ void to_json(nlohmann::json& j, SimpleRootFilterInput const& options) { - j["track_id"] = options.track_id; -#define SRFI_SAVE_OPTION(NAME) \ - do \ - { \ - if (options.NAME != options.unspecified) \ - j[#NAME] = options.NAME; \ - } while (0) +#define SRFI_SAVE_OPTION(NAME) \ + CELER_JSON_SAVE_WHEN(j, options, NAME, options.NAME != options.unspecified) + + CELER_JSON_SAVE(j, options, track_id); SRFI_SAVE_OPTION(event_id); SRFI_SAVE_OPTION(parent_id); SRFI_SAVE_OPTION(action_id); diff --git a/src/corecel/CMakeLists.txt b/src/corecel/CMakeLists.txt index aad43f8873..2a3267c139 100644 --- a/src/corecel/CMakeLists.txt +++ b/src/corecel/CMakeLists.txt @@ -75,6 +75,7 @@ endif() if(CELERITAS_USE_JSON) list(APPEND SOURCES AssertIO.json.cc + io/JsonUtils.json.cc sys/DeviceIO.json.cc sys/KernelRegistryIO.json.cc sys/MemRegistryIO.json.cc diff --git a/src/corecel/io/JsonUtils.json.cc b/src/corecel/io/JsonUtils.json.cc new file mode 100644 index 0000000000..582302eea3 --- /dev/null +++ b/src/corecel/io/JsonUtils.json.cc @@ -0,0 +1,65 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2023 UT-Battelle, LLC, and other Celeritas developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file corecel/io/detail/JsonUtils.json.cc +//---------------------------------------------------------------------------// + +#include "JsonUtils.json.hh" + +#include "corecel/Assert.hh" +#include "corecel/io/Logger.hh" +#include "corecel/sys/Version.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Print a warning about a deprecated input option. + */ +void warn_deprecated_json_option(char const* old_name, char const* new_name) +{ + CELER_LOG(warning) << "Deprecated option '" << old_name << "': use '" + << new_name << "' instead"; +} + +//---------------------------------------------------------------------------// +/*! + * Save a format and version marker. + */ +void save_format(nlohmann::json& j, std::string const& format) +{ + j["_format"] = format; + j["_version"] = to_string(celer_version()); +} + +//---------------------------------------------------------------------------// +/*! + * Load and check for a format and compatible version marker. + */ +void check_format(nlohmann::json const& j, std::string const& format) +{ + if (auto iter = j.find("_version"); iter != j.end()) + { + auto version = Version::from_string(iter->get()); + if (version > celer_version()) + { + CELER_LOG(warning) + << "Input version " << version + << " is newer than the current Celeritas version " + << celer_version() + << ": options may be missing or incompatible"; + } + } + if (auto iter = j.find("_format"); iter != j.end()) + { + auto format_str = iter->get(); + CELER_VALIDATE(format_str == format, + << "invalid format for \"" << format << "\" input: \"" + << format_str << "\""); + } +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/corecel/io/JsonUtils.json.hh b/src/corecel/io/JsonUtils.json.hh new file mode 100644 index 0000000000..e6fdbcdb71 --- /dev/null +++ b/src/corecel/io/JsonUtils.json.hh @@ -0,0 +1,88 @@ +//----------------------------------*-C++-*----------------------------------// +// Copyright 2023 UT-Battelle, LLC, and other Celeritas developers. +// See the top-level COPYRIGHT file for details. +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file corecel/io/detail/JsonUtils.json.hh +//---------------------------------------------------------------------------// +#pragma once + +#include + +//---------------------------------------------------------------------------// +// MACROS +//---------------------------------------------------------------------------// +/*! + * Load a required field into a struct. + */ +#define CELER_JSON_LOAD_REQUIRED(OBJ, STRUCT, NAME) \ + OBJ.at(#NAME).get_to(STRUCT.NAME) + +/*! + * Load an optional field. + * + * If the field is missing or null, it is omitted. + */ +#define CELER_JSON_LOAD_OPTION(OBJ, STRUCT, NAME) \ + do \ + { \ + if (auto iter = OBJ.find(#NAME); \ + iter != OBJ.end() && !iter->is_null()) \ + { \ + iter->get_to(STRUCT.NAME); \ + } \ + } while (0) + +/*! + * Load an optional field. + * + * If the field is missing or null, it is omitted. + */ +#define CELER_JSON_LOAD_DEPRECATED(OBJ, STRUCT, OLD, NEW) \ + do \ + { \ + if (auto iter = OBJ.find(#OLD); iter != OBJ.end()) \ + { \ + ::celeritas::warn_deprecated_json_option(#OLD, #NEW); \ + iter->get_to(STRUCT.NEW); \ + } \ + } while (0) + +/*! + * Save a field to a JSON object. + */ +#define CELER_JSON_SAVE(OBJ, STRUCT, NAME) OBJ[#NAME] = STRUCT.NAME + +/*! + * Save a field if the condition is met. + * + * If not met, a "null" placeholder is saved. + */ +#define CELER_JSON_SAVE_WHEN(OBJ, STRUCT, NAME, COND) \ + do \ + { \ + if ((COND)) \ + { \ + CELER_JSON_SAVE(OBJ, STRUCT, NAME); \ + } \ + else \ + { \ + OBJ[#NAME] = nullptr; \ + } \ + } while (0) +//---------------------------------------------------------------------------// + +namespace celeritas +{ +//---------------------------------------------------------------------------// +// Print a warning about a deprecated input option +void warn_deprecated_json_option(char const* old_name, char const* new_name); + +// Save a format and version marker +void save_format(nlohmann::json& j, std::string const& format); + +// Load and check for a format and compatible version marker +void check_format(nlohmann::json const& j, std::string const& format); + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/corecel/sys/Version.cc b/src/corecel/sys/Version.cc index 254fa2a947..8f9933085d 100644 --- a/src/corecel/sys/Version.cc +++ b/src/corecel/sys/Version.cc @@ -11,12 +11,10 @@ #include #include +#include "celeritas_version.h" #include "corecel/Assert.hh" #include "corecel/io/Join.hh" -using std::cout; -using std::endl; - namespace celeritas { //---------------------------------------------------------------------------// @@ -57,5 +55,25 @@ std::ostream& operator<<(std::ostream& os, Version const& v) return os; } +//---------------------------------------------------------------------------// +//! Save as a string +std::string to_string(Version const& v) +{ + std::ostringstream os; + os << v; + return os.str(); +} + +//---------------------------------------------------------------------------// +/*! + * Get the Celeritas version. + */ +Version celer_version() +{ + return {celeritas_version_major, + celeritas_version_minor, + celeritas_version_patch}; +} + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/sys/Version.hh b/src/corecel/sys/Version.hh index 936c8cf7e9..b123850a82 100644 --- a/src/corecel/sys/Version.hh +++ b/src/corecel/sys/Version.hh @@ -10,6 +10,7 @@ #include #include // IWYU pragma: keep #include +#include #include // Undefine macros from sys/sysmacros.h @@ -137,5 +138,11 @@ CELER_DEFINE_VERSION_CMP(>=) // Write to stream std::ostream& operator<<(std::ostream&, Version const&); +// Save as string +std::string to_string(Version const&); + +// Get the Celeritas version as an object +Version celer_version(); + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/orange/construct/OrangeInputIO.json.cc b/src/orange/construct/OrangeInputIO.json.cc index 37f34bec25..ce3c170ef3 100644 --- a/src/orange/construct/OrangeInputIO.json.cc +++ b/src/orange/construct/OrangeInputIO.json.cc @@ -492,7 +492,7 @@ void from_json(nlohmann::json const& j, OrangeInput& value) << "invalid ORANGE JSON input: unknown format '" << fmt << "'"); std::string version{""}; - if (auto iter = j.find("version"); iter != j.end()) + if (auto iter = j.find("_version"); iter != j.end()) { version = std::to_string(iter->get()); }