- 
                Notifications
    
You must be signed in to change notification settings  - Fork 44
 
Add builtin surface roughness models #1928
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
base: develop
Are you sure you want to change the base?
Add builtin surface roughness models #1928
Conversation
          Test summary 4 564 files   7 293 suites   13m 37s ⏱️ Results for commit c6aa9e1. ♻️ This comment has been updated with latest results.  | 
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still working through this but it's looking fantastic! I've got a few initial comments to suggest while I wrap up the vecgeom surface normal work.
2e4c714    to
    966f24f      
    Compare
  
    | 
           @sethrj I've been seeing some errors with finding   | 
    
| 
           @hhollenb Can you post the errors? The only ones I'm seeing in the CI (about missing operators) is due to not including   | 
    
| 
           Sorry I was looking at the build-docker tests: I'll fix the   | 
    
| 
           Aha, I see what's happening... the error is when building with NVCC, and there's a   | 
    
          
 Not sure we made an issue for this, but a related reminder from the CUDA math API: 
  | 
    
| 
           Yep I do remember that @amandalund . Created #1950 (which also explains why @hhollenb 's latest build is failing the CPU build...)  | 
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still working my way through this, and it's looking great! I'm really impressed.
| inline CELER_FUNCTION SurfaceModelId surface_model() const; | ||
| 
               | 
          ||
| // Get internal surface ID for the model | ||
| inline CELER_FUNCTION InternalSurfaceId internal_surface_id() const; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be good to clarify whether this is "internal" as in "implementation" or "sub-surface" (I can't remember what we ended up deciding or postponing...)
EDIT: I remember, originally this was a type alias inside SurfaceModel , and it's just a local index for the data. I wonder if it'd be better to call it ModelSurfaceId or something after all? :\
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe PerModelSurfaceId?
| { | ||
| 
               | 
          ||
| namespace | ||
| { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's generally not a good idea to put anonymous namespace/static functions in a header file because the code will be duplicated across multiple translation units (rather than eliminated by the linker). Since only operator+ is defined (no other math operators, and only with a mixed type) I recommend putting the function in SurfaceUtils and giving it a descriptive name like advance_subsurface_along(pos, dir)
| 
               | 
          ||
| // Get surface model for the given step | ||
| inline CELER_FUNCTION SurfaceModelView | ||
| surface_model(Real3 const&, SurfacePhysicsOrder) const; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To improve the readability of the class synopsis,
| surface_model(Real3 const&, SurfacePhysicsOrder) const; | |
| surface_model(Real3 const& normal, SurfacePhysicsOrder) const; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's direction not normal? Regardless I think the traversal direction should be an enum (boolean) value on the state, not a property of the geometry direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the traversal direction changes whenever a surface interaction occurs, we always call the function with
auto surface_physics = track.surface_physics();
auto surface_model = surface_physics.surface_model(surface_physics.traversal_direction(track.geometry().dir()), step);
so I collapsed it down to
auto surface_model = track.surface_physics().surface_model(track.geometry().dir(), step);
but I can go back and expand it again for readability.
        
          
                src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh
              
                Outdated
          
            Show resolved
            Hide resolved
        
              
          
                src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh
              
                Outdated
          
            Show resolved
            Hide resolved
        
      | 
               | 
          ||
| // Get surface model for the given step | ||
| inline CELER_FUNCTION SurfaceModelView | ||
| surface_model(Real3 const&, SurfacePhysicsOrder) const; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's direction not normal? Regardless I think the traversal direction should be an enum (boolean) value on the state, not a property of the geometry direction.
| 
           Added some changes to simplify the applier code, use maps as inputs to models, and cache the track traversal direction. This should remove most of the ambiguities about whether vectors refer to the surface normal or track direction in function arguments, and also remove the need to query geometry in the track slot executors. The traversal direction will need to be updated after a reflection / refraction in the interaction step which will be implemented later.  | 
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hhollenb This really is great work, but to make it easier to review we need to try to split these into smaller requests. I think there's a quadratic scaling with PR review time...
Still working on finishing this up but we need to decide about this Builtin thing first. (Or maybe we should just change that in a follow-on...)
| * surface physics steps. | ||
| */ | ||
| template<SurfacePhysicsOrder S, template<class> class Applier> | ||
| class BuiltinSurfaceModel : public SurfaceModel | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is too much complexity for incomplete functionality (i.e., we have to inherit from it in the Roughness model, so we end up with an extra model class that doesn't do anything on its own: it's not an interface, it's an implementation).
We should choose to:
- Continue like we've done with the other actions in the code and have a bunch of similar-looking code (~50% looks the same but it's spread among many different lines)
 - Commit fully to a template+traits approach: each traits instance will have surface physics order, input type, "internal surface" record type, input-to-record builder class, (likely another params-type type for "backend storage"), and executor.
 
For this PR, is it at all possible to do the first one, including only a single model, and using the current workaround for the "fake" processes? Then we can generalize in the next PR.
| 
           @hhollenb @amandalund Notes from today, which should be incorporated into documentation once stable: Physics surface crossing stateLogical directions: 
 Physical directions: 
 Changing during traversal: 
 Cached: 
 Executor/applier/etc diagramThis is the best I could come up with since there's a mix of functional programming (  
The red bold arrows are return values, dashed arrows are "creates during call operator", black arrows are data passing. Construction arguments are to the left, call arguments to the right. I'll create an equivalent for the EM interactor for comparison... maybe that'll help inform our design choices.  | 
    
| // Ensure the local normal follows the entering surface convention | ||
| auto normal = s_physics.global_normal(); | ||
| if (s_physics.traversal_direction() == SubsurfaceDirection::reverse) | ||
| { | ||
| normal = -normal; | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be done when entering a surface, reflecting, or here? It doesn't seem related to the roughness per se.
| 
           For comparison, here's a typical EM interactor (bethe-heitler): In the EM case, there's a clean break between the procedural components and functional: the executor doesn't return an interactor, it executes the interaction. I actually think philosophically the choice @hhollenb is nicer. Instead of building the executor and, embarrassingly, calling it only once:     BetheHeitlerInteractor interact(
        params, particle, dir, allocate_secondaries, material, element);
    auto rng = track.rng();
    return interact(rng);you would construct the interactor and let someone else handle it:     return BetheHeitlerInteractor{...};However, this isn't an executor since it's not really executing (though in the EM case it could also be sampling the element!). The "Executor" misnomer is one issue causing confusion in this PR. I think the other complication is that forwarding and combining arguments can be confusing, especially if nested. That's done a few mote times here than in EM: EnteringSurfaceNormalSampler, BSM::make_executor in particular. And the templates on BSM make that even more confusing. Finally, the roughness "executor" takes a few select track states rather than the whole track view. Even though this is the "correct" thing to do (pass around only the data you need, not all the data you could possibly want, in order to make the data flow and operations clearer) I think it adds another layer of cognitive burden. So I think this design is good at heart, but to improve clarity we should maybe: 
  | 
    


Adds support for built-in roughness models: polished, uniform smear, and Gaussian roughness.
The
BuiltinSurfaceModelBuilderbuilds surface models from supported input data in the form ofstd::map<PhysSurfaceId, InputT>. For reflectivity and interaction models (not yet supported),FakeSurfaceModels are built instead.BuiltinSurfaceModelprovides a common interface for accessing surfaces from the input data and wrapping executors with an appropriate applier (cf.InteractionApplierin volumetric physics models).The
SurfaceModelViewis a wrapper view to directly access physics relevant for a surface model. This encapsulates a lot of the bookkeeping of the surface traversal and represents a single physical surface crossing. This also hides some of the previous complications ofSurfacePhysicsView, which now primarily handles subsurface traversal.Should be direct to add reflectivity and interaction models once we get calculators for them implemented.