File indexing completed on 2026-06-26 07:50:31
0001 #include <cassert>
0002 #include <csignal>
0003 #include <cstdlib>
0004 #include <iostream>
0005 #include <set>
0006 #include <sys/types.h>
0007 #include <sys/wait.h>
0008 #include <unistd.h>
0009
0010 #include <plog/Appenders/ConsoleAppender.h>
0011 #include <plog/Formatters/TxtFormatter.h>
0012 #include <plog/Init.h>
0013 #include <plog/Log.h>
0014
0015 using plog::error;
0016 using plog::fatal;
0017 using plog::info;
0018
0019 #include "G4BooleanSolid.hh"
0020 #include "G4IntersectionSolid.hh"
0021 #include "G4LogicalVolume.hh"
0022 #include "G4PhysicalConstants.hh"
0023 #include "G4Sphere.hh"
0024 #include "G4SubtractionSolid.hh"
0025 #include "G4SystemOfUnits.hh"
0026 #include "G4VPhysicalVolume.hh"
0027 #include "G4VSolid.hh"
0028
0029 #include "config_path.h"
0030
0031 #include "U4GDML.h"
0032 #include "U4Solid.h"
0033 #include "U4Volume.h"
0034
0035 #include "s_csg.h"
0036
0037 namespace
0038 {
0039 inline constexpr char testGeomFile[] = GPHOX_TEST_GEOM_DIR "/sphere_phicut_quarter_shell.gdml";
0040
0041 enum ConvertOutcome
0042 {
0043 CONVERT_REJECTED,
0044 CONVERT_ACCEPTED,
0045 CONVERT_ERROR
0046 };
0047
0048 bool IsExpectedRejectSignal(int signal)
0049 {
0050 return signal == SIGABRT || signal == SIGINT;
0051 }
0052
0053 bool HasNonSpherePrimitive(const sn* nd)
0054 {
0055 if (nd == nullptr)
0056 return false;
0057
0058 std::set<int> typecodes;
0059 nd->typecodes(typecodes);
0060
0061 for (int typecode : typecodes)
0062 {
0063 if (CSG::IsPrimitive(typecode) == false)
0064 continue;
0065
0066 if (typecode == CSG_SPHERE || typecode == CSG_ZSPHERE)
0067 continue;
0068
0069 if (typecode == CSG_NOTSUPPORTED || typecode == CSG_UNDEFINED)
0070 continue;
0071
0072 if (typecode == CSG_PHICUT || typecode == CSG_HALFSPACE)
0073 return true;
0074 }
0075
0076 return false;
0077 }
0078
0079 int CountPartialPhiSpheres(const G4VSolid* solid)
0080 {
0081 const G4Sphere* sphere = dynamic_cast<const G4Sphere*>(solid);
0082 if (sphere)
0083 {
0084 double start_phi = sphere->GetStartPhiAngle() / CLHEP::radian;
0085 double delta_phi = sphere->GetDeltaPhiAngle() / CLHEP::radian;
0086 bool partial_phi = start_phi != 0. || delta_phi != 2. * CLHEP::pi;
0087 return partial_phi ? 1 : 0;
0088 }
0089
0090 const G4BooleanSolid* boolean = dynamic_cast<const G4BooleanSolid*>(solid);
0091 if (boolean == nullptr)
0092 return 0;
0093
0094 const G4VSolid* left = boolean->GetConstituentSolid(0);
0095 const G4VSolid* right = boolean->GetConstituentSolid(1);
0096 return CountPartialPhiSpheres(left) + CountPartialPhiSpheres(right);
0097 }
0098
0099 ConvertOutcome ConvertInChildProcess(const G4VSolid* solid)
0100 {
0101 pid_t pid = fork();
0102 if (pid < 0)
0103 {
0104 perror("fork");
0105 return CONVERT_ERROR;
0106 }
0107
0108 if (pid == 0)
0109 {
0110 s_csg* csg = new s_csg;
0111 assert(csg);
0112
0113 int lvid = 0;
0114 int depth = 0;
0115 int level = 1;
0116 sn* nd = U4Solid::Convert(solid, lvid, depth, level);
0117 int exit_code = 4;
0118 if (nd != nullptr)
0119 {
0120 bool has_phi_cut_representation = HasNonSpherePrimitive(nd);
0121 exit_code = has_phi_cut_representation ? 0 : 5;
0122 if (has_phi_cut_representation == false)
0123 {
0124 std::cerr
0125 << "child conversion produced non-null tree without any non-sphere primitive; "
0126 << "phi-cut geometry is not represented"
0127 << std::endl;
0128 }
0129 }
0130 delete nd;
0131 _exit(exit_code);
0132 }
0133
0134 int status = 0;
0135 int rc = waitpid(pid, &status, 0);
0136 if (rc != pid)
0137 {
0138 perror("waitpid");
0139 return CONVERT_ERROR;
0140 }
0141
0142 if (WIFSIGNALED(status))
0143 {
0144 int signal = WTERMSIG(status);
0145 if (IsExpectedRejectSignal(signal))
0146 {
0147 std::cout << "child rejected conversion with expected signal " << signal << std::endl;
0148 return CONVERT_REJECTED;
0149 }
0150
0151 std::cerr << "child crashed with unexpected signal " << signal << std::endl;
0152 return CONVERT_ERROR;
0153 }
0154
0155 if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
0156 {
0157 std::cout
0158 << "child converted partial-phi sphere with non-sphere primitive(s) in the CSG tree"
0159 << std::endl;
0160 return CONVERT_ACCEPTED;
0161 }
0162
0163 if (WIFEXITED(status))
0164 {
0165 std::cerr << "child exited unexpectedly with status " << WEXITSTATUS(status) << std::endl;
0166 return CONVERT_ERROR;
0167 }
0168
0169 std::cerr << "child ended in unexpected state " << status << std::endl;
0170 return CONVERT_ERROR;
0171 }
0172 }
0173
0174 int main(int argc, char** argv)
0175 {
0176 static plog::ConsoleAppender<plog::TxtFormatter> consoleAppender;
0177 plog::init(plog::info, &consoleAppender);
0178
0179 const G4VPhysicalVolume* world = U4GDML::Read(testGeomFile);
0180 LOG_IF(plog::fatal, world == nullptr)
0181 << "failed to load GDML path " << (testGeomFile ? testGeomFile : "-");
0182 if (world == nullptr)
0183 return EXIT_FAILURE;
0184
0185 const G4VPhysicalVolume* quarter_shell_pv = U4Volume::FindPV(world, "QuarterShell_pv");
0186 LOG_IF(plog::fatal, quarter_shell_pv == nullptr)
0187 << "failed to find QuarterShell_pv in GDML path " << testGeomFile;
0188 if (quarter_shell_pv == nullptr)
0189 return EXIT_FAILURE;
0190
0191 const G4LogicalVolume* quarter_shell_lv = quarter_shell_pv->GetLogicalVolume();
0192 LOG_IF(plog::fatal, quarter_shell_lv == nullptr)
0193 << "QuarterShell_pv lacks a logical volume";
0194 if (quarter_shell_lv == nullptr)
0195 return EXIT_FAILURE;
0196
0197 const G4VSolid* quarter_shell_solid = quarter_shell_lv->GetSolid();
0198 LOG_IF(plog::fatal, quarter_shell_solid == nullptr)
0199 << "QuarterShell_pv lacks a solid";
0200 if (quarter_shell_solid == nullptr)
0201 return EXIT_FAILURE;
0202
0203 const G4IntersectionSolid* intersection = dynamic_cast<const G4IntersectionSolid*>(quarter_shell_solid);
0204 LOG_IF(plog::fatal, intersection != nullptr)
0205 << "test geometry unexpectedly uses a parent IntersectionSolid";
0206 if (intersection != nullptr)
0207 return EXIT_FAILURE;
0208
0209 const G4SubtractionSolid* subtraction = dynamic_cast<const G4SubtractionSolid*>(quarter_shell_solid);
0210 LOG_IF(plog::fatal, subtraction == nullptr)
0211 << "test geometry expected a subtraction shell solid " << quarter_shell_solid->GetName();
0212 if (subtraction == nullptr)
0213 return EXIT_FAILURE;
0214
0215 int partial_phi_spheres = CountPartialPhiSpheres(quarter_shell_solid);
0216 LOG_IF(plog::fatal, partial_phi_spheres == 0)
0217 << "test geometry expected partial-phi sphere primitives";
0218 if (partial_phi_spheres == 0)
0219 return EXIT_FAILURE;
0220
0221 ConvertOutcome outcome = ConvertInChildProcess(quarter_shell_solid);
0222 switch (outcome)
0223 {
0224 case CONVERT_REJECTED:
0225 std::cout
0226 << "partial-phi sphere conversion is rejected, matching current fail-fast behavior"
0227 << std::endl;
0228 return EXIT_SUCCESS;
0229
0230 case CONVERT_ACCEPTED:
0231 std::cout
0232 << "partial-phi sphere conversion succeeded with a non-null CSG tree"
0233 << std::endl;
0234 return EXIT_SUCCESS;
0235
0236 case CONVERT_ERROR:
0237 break;
0238 }
0239
0240 std::cerr
0241 << "SpherePhiCutQuarterShellTest could neither confirm fail-fast rejection nor "
0242 << "successful conversion of the partial-phi spherical shell."
0243 << std::endl;
0244
0245 return EXIT_FAILURE;
0246 }