Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement framework for optical physics models #1302

Draft
wants to merge 24 commits into
base: develop
Choose a base branch
from

Conversation

hhollenb
Copy link
Contributor

@hhollenb hhollenb commented Jul 2, 2024

This is an initial implementation of optical models and their associated data structures.

General overview:

  • OpticalModel: acts like both a process (building cross sections / mean free paths) and as a model (action to run the interactor).
  • ImportedOpticalMaterials: physics data for optical models is really just optical material properties. This serves as a list of imported optical material data that models will access directly to get their physics data.
  • OpticalModelBuilder: constructs the ImportedOpticalMaterials and is responsible for building the appropriate models based on the user's selection.
  • OpticalPhysicsParams: responsible for building each optical model, adding them to the action registry, and building their MFP grids in OpticalPhysicsParamsData.
  • OpticalMfpBuilder: A helper class to have optical models build MFP grids on a per optical material basis.
  • AbsorptionModel: An almost trivial optical model. Has no data and just kills the track when called.
  • RayleighModel: A more complex optical model. Included to make sure API for building model-specific data is sensible.

This PR isn't merge ready yet, and is meant to be a discussion point for how we want to organize optical models. Some functions and classes are empty, and I'm in the middle of adding tests.

Thoughts / discussion questions:

  • May be better to have something like this in ImportData:
std::unordered_map<GeoMatIdx, ImportOpticalMaterialId> geo_optical_map;
std::vector<ImportOpticalMaterial> optical_materials;
  • Will we want an OpticalMaterialParams? Is there ever a point where we need to process ImportOpticalMaterials and store the data outside of optical model specific data collections?
  • How much flexibility do we want to give to user defined optical models? Should we have an ImportOpticalModel structure? A lambda table in ImportOpticalModel would duplicate the mean free paths in ImportOpticalMaterial...
  • OpticalMfpBuilder is a bit awkward. There should be a better way to do this that is also portable to Cerenkov and scintillation parameter building.

@hhollenb hhollenb added enhancement New feature or request core Software engineering infrastructure labels Jul 2, 2024
@sethrj
Copy link
Member

sethrj commented Jul 3, 2024

Does this supersede #1162 ?

@hhollenb
Copy link
Contributor Author

hhollenb commented Jul 5, 2024

Does this supersede #1162 ?

Yes, I'll close that one. I reused any relevant code but had to make enough major changes I thought it was worthwhile to start from a fresh branch.

@hhollenb hhollenb mentioned this pull request Jul 5, 2024
11 tasks
@hhollenb hhollenb self-assigned this Jul 7, 2024
Copy link
Member

@sethrj sethrj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it's taken some time to go through this: this looks really solid; I think you've got basically all the skeleton there! I've got a thought or two about simplifying some aspects to accelerate the time it takes for us to flesh this out... perhaps the first PR could be just the interactors for rayleigh and absorption; so that way we can unit test them and make sure the initial OpticalInteraction class is good enough. (You know, we might even be able to leave out code like the secondary stack until later, just to keep things simple.) But I think virtually all of this code will eventually end up in the final version almost unchanged 😄

struct ImportData;
//---------------------------------------------------------------------------//
/*!
* A registry for imported optical material data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ImportedProcesses stuff in phys is a little janky; it also is some of the oldest code that we use. When I wrote it, I was hoping to reduce data copying and lifetime and so forth; but in reality there's just not that much data for us to worry about, especially with optical physics. I think it'd just be simpler and more effective to build each optical model directly from optical import data, so we don't even need this (admittedly lightweight) class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case I think I'd like to change the optical map in ImportData to be a vector of ImportOpticalMaterials and change the map to be a map between GeoMatIds and OpticalMaterialIds. We need to just iterate over a list of optical materials in several places, and having a simple vector to index would be convenient.

Copy link
Member

@sethrj sethrj Jul 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hhollenb I fully support that! It's definitely more consistent with how we treat the other properties. Perhaps instead of a map between geo and optical matIDs, what about having an unsigned int optical_material_id field in ImportGeoMaterial? (You could define a static inline constexpr unsigned int unspecified = -1 like we have with ImportVolume.)

*/
struct AbsorptionExecutor
{
inline CELER_FUNCTION Interaction
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're going to need an optical interaction type—since it includes changes to polarisation, and I assume we're not ionizing anything so we never make any non-photon secondaries.

// See the top-level COPYRIGHT file for details.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
//---------------------------------------------------------------------------//
//! \file celeritas/optical/OpticalMfpBuilder.hh
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can put these guys in the detail namespace.


//---------------------------------------------------------------------------//
/*!
* Build the mean free paths for the model.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Build the mean free paths for the model.
* Build the mean free paths for one material for the model.

*
* Performs the construction of optical physics data and optical models
* from imported data, and provides an interface for accessing this data
* on the host and on the device.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to add a few notes about how this class differs from the main EM physics:

  • We interpolate linearly on MFP vs energy, rather than macroscopic XS vs log energy (or scaled log energy)
  • There's a one-to-one model/process correlation
  • Only a single incident particle type is supported
  • Not all materials in the problem have optical data (?)
  • ....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* We interpolate linearly on MFP vs energy, rather than macroscopic XS vs log energy (or scaled log energy)

A thought on linear interpolation: this is effectively just due to our choice of grid, right? AFAIK, the code in OpticalPhysicsParams and determining the discrete action is agnostic of the grid, other than the fact it will calculate some XS / MFP for a given energy value. It's definitely important whether we're using MFP vs macroscopic XS, but otherwise the code behaves mostly the same as in the EM case when dealing with these grids.

@hhollenb
Copy link
Contributor Author

hhollenb commented Jul 8, 2024

Sorry it's taken some time to go through this: this looks really solid; I think you've got basically all the skeleton there! I've got a thought or two about simplifying some aspects to accelerate the time it takes for us to flesh this out... perhaps the first PR could be just the interactors for rayleigh and absorption; so that way we can unit test them and make sure the initial OpticalInteraction class is good enough. (You know, we might even be able to leave out code like the secondary stack until later, just to keep things simple.) But I think virtually all of this code will eventually end up in the final version almost unchanged 😄

I believe @whokion has some preliminary interactors implemented. The absorption interactor is trivial (just returns an absorbed state -- might be fine with just an executor) and doesn't need to be super rigorously tested. Rayleigh is non-trivial, but hopefully I got enough code in here to draft what setting up its data and model would look like. I'll discuss getting another PR up for just the interactions and tests over slack.

@whokion
Copy link
Contributor

whokion commented Jul 8, 2024

I can add OpticalRayInteractor (which I already have) once we decide how to build required optical data for the interactor - I was using OpticalRaylieghParams class, which can be absorbed to OpticalPhysicsParams or can be added too if we decide to have a separated data Params class for each model. Let's have a consensus first how we support optical material properties.

@sethrj
Copy link
Member

sethrj commented Jul 8, 2024

@whokion I think the optical rayleigh model should have its own data independent of the physics. I assume it's not needed by the other models?

@whokion
Copy link
Contributor

whokion commented Jul 8, 2024

@sethrj Yes, the Rayleigh data is independent from other models - actually, there is no required optical data for the OpticalRayleigh interactor if all mfp are taken care by the OpticalPhysicsParams class. It was not clear to me whether each model is responsible to import its own mfp along with other properties or OpticalPhysicsParams manages all mfp.

@sethrj
Copy link
Member

sethrj commented Jul 8, 2024

The plan is for the models to provide the MFP at setup time (usually by just processing the imported optical data) for the "optical physics" to manage all together at runtime.

@hhollenb
Copy link
Contributor Author

Made a PR for absorption and Rayleigh interactors here: #1317

@sethrj sethrj mentioned this pull request Aug 6, 2024
30 tasks
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These model/Optical... files should've been deleted, ignore them and I'll remove them.

Copy link
Member

@sethrj sethrj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of great work here! I think the scope of this is just too large for one person and one PR; I apologize for not partitioning the work among us. We'll definitely use all the work you've done here, but we need to break it into more bite-size chunks.

To start, let's drill down on just constructing and evaluating interaction lengths:

  • Model
  • AbsorptionModel with "not implemented" for the step action
  • MfpBuilder
  • The minimal subset of physics data (not params yet)

Let's talk about this monday?

struct ImportOpticalModel
{
optical::ImportModelClass model_class;
std::vector<ImportPhysicsVector> mfps; //!< per optical material MFPs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant with the ImportOpticalMaterial.{rayleigh,absorption,wls} data which already tells us the available models and stores absorption_length (mfp): should we embed model information into the material data, or have separate model classes?

/*!
 * Store optical material properties.
 */
struct ImportOpticalMaterial
{
    ImportPhysicsVector refractive_index;
    ImportScintData scintillation;
    bool cerenkov{false};

    //! Whether minimal useful data is stored
    explicit operator bool() const
    {
        return static_cast<bool>(refractive_index);
    }
};

struct ImportOpticalWLSModel
{
    ImportPhysicsVector absorption_length;  //!< Absorption length [MeV, len]

    double mean_num_photons{};  //!< Mean number of re-emitted photons
    double time_constant{};  //!< Time delay between absorption and re-emission
    ImportPhysicsVector component;  //!< Re-emission population [MeV, unitless]

    explicit operator bool() const
    {
        return mfp && mean_num_photons > 0 && time_constant > 0
               && static_cast<bool>(absorption_length)
               && static_cast<bool>(component)
               && absorption_length.vector_type == ImportPhysicsVectorType::free
               && component.vector_type == absorption_length.vector_type;
    }
};

struct ImportOpticalAbsorptionModel
{
    ImportPhysicsVector absorption_length;  //!< Absorption length [MeV, len]

    explicit operator bool() const
    {
        return static_cast<bool>(absorption_length);
    }
};

//! Number of optical materials
OpticalMaterialId::size_type num_materials() const
{
return num_materials_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be obtained from the size of properties already in the table:

Suggested change
return num_materials_;
return this->host_ref().refractive_index.size();

Comment on lines +39 to +43
//! Construct with defaults
Model(ActionId id, std::string const& label, std::string const& description)
: ConcreteAction(id, label, description)
{
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delegate the constructor:

Suggested change
//! Construct with defaults
Model(ActionId id, std::string const& label, std::string const& description)
: ConcreteAction(id, label, description)
{
}
// Construct with ID, label, description
using ConcreteAction::ConcreteAction;

}

//! Build mean free path grids for all optical materials
virtual void build_mfps(detail::MfpBuilder build) const = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
virtual void build_mfps(detail::MfpBuilder build) const = 0;
virtual void build_mfps(detail::MfpBuilder& build) const = 0;

Comment on lines +23 to +28
* Brief class description.
*
* Optional detailed class description, and possibly example usage:
* \code
MfpBuilder ...;
\endcode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add documentation

Comment on lines +134 to +137
GenericGridInserter inserter(&data.reals, &data.grids);
std::vector<ValueGridId> grid_ids;

model.build_mfps(detail::MfpBuilder{&inserter, &grid_ids});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grid inserter should be outside this loop so that it can deduplicate data across models. I think we can also move most of this function into the implementation of MfpBuilder, which could include all the builders: see celeritas/optical/detail/MatScintSpecInserter.hh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Software engineering infrastructure enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants