Warning, file /eic-opticks/tests/SimphonyApiTest.cc was not indexed
or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 #include <filesystem>
0002 #include <iostream>
0003 #include <stdexcept>
0004 #include <string>
0005
0006 #include "G4GDMLParser.hh"
0007
0008 #include "simphony.h"
0009
0010 namespace
0011 {
0012 struct TranslationFailure : std::runtime_error
0013 {
0014 TranslationFailure() :
0015 std::runtime_error("injected sensor translation failure")
0016 {
0017 }
0018 };
0019
0020 struct SensorIdentifier : simphony::SensorIdentifier
0021 {
0022 int calls = 0;
0023 bool fail_translation = false;
0024
0025 void setLevel(int) override
0026 {
0027 }
0028
0029 int getGlobalIdentity(const G4VPhysicalVolume*, const G4VPhysicalVolume*) override
0030 {
0031 ++calls;
0032 if (fail_translation)
0033 throw TranslationFailure();
0034 return -1;
0035 }
0036
0037 int getInstanceIdentity(const G4VPhysicalVolume*) const override
0038 {
0039 return -1;
0040 }
0041 };
0042
0043 void require(bool condition, const std::string& message)
0044 {
0045 if (!condition)
0046 throw std::runtime_error(message);
0047 }
0048
0049 template <typename Exception, typename Function>
0050 void require_throws(Function&& function, const std::string& message)
0051 {
0052 try
0053 {
0054 function();
0055 }
0056 catch (const Exception&)
0057 {
0058 return;
0059 }
0060 throw std::runtime_error(message);
0061 }
0062
0063 void require_negative_event_ids_rejected()
0064 {
0065 require_throws<std::invalid_argument>(
0066 [] { simphony::simulate(-1); },
0067 "simulate(-1) did not throw std::invalid_argument");
0068 require_throws<std::invalid_argument>(
0069 [] { simphony::reset(-1); },
0070 "reset(-1) did not throw std::invalid_argument");
0071 }
0072 }
0073
0074 int main(int argc, char** argv)
0075 {
0076 try
0077 {
0078 require(argc == 3 || (argc == 4 && std::string(argv[3]) == "--fail-translation"),
0079 "usage: SimphonyApiTest GEOMETRY.gdml OUTPUT_DIRECTORY [--fail-translation]");
0080
0081 require(!simphony::is_initialized(), "API unexpectedly initialized at process start");
0082 require(!simphony::has_gpu(), "API unexpectedly has a GPU backend at process start");
0083 require_throws<std::logic_error>(
0084 [] { simphony::simulate(0); },
0085 "simulate before initialize did not throw std::logic_error");
0086 require_throws<std::invalid_argument>(
0087 [] { simphony::initialize(nullptr); },
0088 "initialize(nullptr) did not throw std::invalid_argument");
0089 require(!simphony::is_initialized(), "failed initialization changed API state");
0090 require_negative_event_ids_rejected();
0091
0092 G4GDMLParser parser;
0093 parser.Read(argv[1], false);
0094 const G4VPhysicalVolume* world = parser.GetWorldVolume();
0095 require(world != nullptr, "GDML parser returned a null world");
0096
0097 SensorIdentifier identifier;
0098 const simphony::GeometryOptions options{
0099 .sensor_identifier = &identifier,
0100 .gpu = simphony::GpuRequirement::Disabled,
0101 };
0102
0103 if (argc == 4)
0104 {
0105 identifier.fail_translation = true;
0106 require_throws<TranslationFailure>(
0107 [&] { simphony::initialize(world, options); },
0108 "initialize did not propagate the translation failure");
0109 require(!simphony::is_initialized() && !simphony::has_gpu(),
0110 "failed translation published an initialized runtime");
0111
0112 identifier.fail_translation = false;
0113 require_throws<std::logic_error>(
0114 [&] { simphony::initialize(world, options); },
0115 "retry after partial geometry translation did not throw std::logic_error");
0116 auto changed_options = options;
0117 changed_options.sensor_identifier = nullptr;
0118 changed_options.gpu = simphony::GpuRequirement::Required;
0119 require_throws<std::logic_error>(
0120 [&] { simphony::initialize(world, changed_options); },
0121 "retry with different options after partial translation was not rejected");
0122 require(!simphony::is_initialized() && !simphony::has_gpu(),
0123 "rejected retry changed the runtime state");
0124 return 0;
0125 }
0126
0127 simphony::initialize(world, options);
0128
0129 require(identifier.calls > 0, "custom sensor identifier was not used during translation");
0130 require(simphony::is_initialized(), "initialize did not update API state");
0131 require(!simphony::has_gpu(), "Disabled policy unexpectedly created a GPU backend");
0132 require_negative_event_ids_rejected();
0133
0134 simphony::initialize(world, options);
0135 require_throws<std::logic_error>(
0136 [] { simphony::simulate(7, false); },
0137 "simulate without a GPU backend did not throw std::logic_error");
0138 require_throws<std::logic_error>(
0139 [] { simphony::reset(7); },
0140 "reset without a pending event did not throw std::logic_error");
0141
0142 auto changed_identifier = options;
0143 changed_identifier.sensor_identifier = nullptr;
0144 require_throws<std::logic_error>(
0145 [world, changed_identifier] { simphony::initialize(world, changed_identifier); },
0146 "reinitialization with a different sensor identifier did not throw std::logic_error");
0147
0148 auto changed_options = options;
0149 changed_options.gpu = simphony::GpuRequirement::Optional;
0150 require_throws<std::logic_error>(
0151 [world, changed_options] { simphony::initialize(world, changed_options); },
0152 "reinitialization with changed options did not throw std::logic_error");
0153
0154 const std::filesystem::path output = argv[2];
0155 std::filesystem::remove_all(output);
0156 simphony::save_geometry(output);
0157
0158 require(std::filesystem::is_regular_file(output / "origin.gdml"),
0159 "save_geometry did not write origin.gdml");
0160 require(std::filesystem::is_regular_file(output / "CSGFoundry" / "solid.npy"),
0161 "save_geometry did not write CSGFoundry/solid.npy");
0162 }
0163 catch (const std::exception& error)
0164 {
0165 std::cerr << "SimphonyApiTest: " << error.what() << '\n';
0166 return 1;
0167 }
0168
0169 return 0;
0170 }