Implement physics constructor for OpticalPhysics
Celeritas intends to implement optical photon offload from Geant4 via custom G4VProcesses for photon-generating processed (Cerenkov, Scintillation). Within Geant4, this means they will need to be added to the relevant particles in application physics lists both to enable the offload and for optical data construction/availablity (as with the EM processes).
Following the pattern for used for EM physics in CelerEMStandardPhysics, the changes here add a preliminary concrete G4VPhysicsConstructor to handle particle and process construction for Optical processes. Though Celeritas will not generate and track G4OpticalPhotons, this initial implement largely follows the core G4OpticalPhysics constructor just to get something in place with G4OpticalPhoton.
Comments are used, though it's fairly obvious, to note where the custom Celeritas processes will go. Adding this constructor in CelerEmPhysicsList and/or other lists and celer-g4 can be done, but wanted to submit a WIP just to check this is on the right base.
Though adding this to CelerEmPhysicsList and CelerFTFPBert could proceed at this point, it might be worth thinking about how best to handle the optical physics parameters. One gotcha the last few commits show is that G4OpticalParameters (analogous to G4EmParameters) is only present from Geant4 10.7. Before this, the parameters are handled locally in G4OpticalPhysics, which CelerOpticalPhysics attempts to emulate.
I think all of the optical parameters can be handled/transfered in the same way as the EM ones are in the CelerEmStandardPhysics constructor:
https://github.com/celeritas-project/celeritas/blob/cc2d1df1b3d609c00c12561e2dec60d179e7c012/src/celeritas/ext/detail/CelerEmStandardPhysics.cc#L98-L107
i.e. obtain them from the input struct and:
- For Geant4 < 10.7, hold this locally and configure processes locally (see how Geant4 did it)
- Otherwise, call
G4OpticalParameterssetters as appropriate.
Some of this may of course not be needed later if we don't need to set these processes up to get data etc for Celeritas from them.
However, I don't think we can/should add the optical parameters as direct members of GeantPhysicsOptions as that's really focussed on EM options. It feels like we should maybe have a separate GeantOpticalPhysicsOptions struct that's a member of GeantPhysicsOptions? Longer term, could also consider a larger refactor so we'd have something like:
struct GeantPhysicsOptions
{
GeantEmPhysicsOptions electromagnetic;
GeantOpticalPhysicsOptions optical;
// GeantHadronicPhysicsOptions hadronic;
};
but could just start with the optical parameter struct as a member?
@drbenmorgan I think your plan for a separate struct (and later creating another one for EM parameters) is perfect. Thanks for this!
Struct in place, but as you'll see, the implementation of CelerOpticalPhysics is now a mess due to the changes in how optical physics was configured between Geant4 10.5-10.7. I'll have a think how to improve that.
Next is to add GeantOpticalPhysicsOptions as a member of GeantPhysicsOptions, but ran into some test failures as naturally the generated JSON changes. I did think about having it as a std::optional as we probably don't always want optical physics on. However, need to play about with that as I don't think nlohmann::json supports this directly (and not sure on Celeritas policy on use of std::optional). Advice/ideas welcome!
@drbenmorgan To make life easier for now, let's just stick to the newer versions, and we can throw a "not implemented" for the older versions if the experiments still need it by the time we've got sufficient optical physics implemented 😅
Modulo fixing any errors in the CI, I think this is close to ready. I guess the last step is unit tests for when the optical physics are enabled (they are off by default), but am not 100% sure where to add these. GeantImporterTest.cc and the LarSphere test would appear to be appropriate, but I then get failures on G4Cerenkov and G4Scintillation not being exportable by GeantImporter.cc. I'm not sure about the higher level celeb-sim/celer-g4 yet.
Having played a bit with the tests as above, one slight interface smell (depending on point of view) with the use of std::optional is that setting options through the top level GeantPhysicsOptions feels a bit messy:
GeantPhysicsOptions opts; // default, no optical options
opts.optical_options.cerenkov_radiation = on; // whoops, not the correct interface, and is nullopt anyway
// so have to either do
GeantOpticalOptions optical; // gets you the defaults
optical.cerenkov_radiation = false;
opts.optical_options = optical;
// or
opts.optical_options = GeantOpticalOptions();
(*opts.optical_options).cerenkov_radiation = false; // or .value()...
So I'm leaning towards @sethrj's suggestion to define an explicit operator bool() const for GeantOpticalOptions which would return false if none of the processes are activated. JSON to/from can work just the same (I think), but need to decide what default construction behaviour is, and from that the need for a "build_default" or "deactivate" member function to provide a simple switch.
@stognini I'll implement your suggestions in a fresh PR!
Sorry I pulled the trigger too soon!
No worries. Everything was looking great, so was just having a final look in case I spotted any minor detail after yesterday's commits. Thanks a lot @drbenmorgan !