Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/celeritas/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ list(APPEND SOURCES
optical/CoreParams.cc
optical/CoreState.cc
optical/CoreTrackData.cc
optical/detector/ScoringParams.cc
optical/gen/CherenkovParams.cc
optical/gen/ScintillationParams.cc
optical/gen/GeneratorBase.cc
Expand Down Expand Up @@ -390,6 +391,8 @@ celeritas_polysource(optical/action/DiscreteSelectAction)
celeritas_polysource(optical/action/PreStepAction)
celeritas_polysource(optical/action/TrackingCutAction)
celeritas_polysource(optical/action/detail/TrackInitAlgorithms)
celeritas_polysource(optical/detector/DetectorData)
celeritas_polysource(optical/detector/DetectorAction)
celeritas_polysource(optical/gen/GeneratorAction)
celeritas_polysource(optical/gen/OffloadAction)
celeritas_polysource(optical/gen/OffloadGatherAction)
Expand Down
2 changes: 2 additions & 0 deletions src/celeritas/inp/Problem.hh
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ struct OpticalProblem
OpticalTrackingLimits limits;
//! Per-process state sizes for optical tracking loop
OpticalStateCapacity capacity;
//! User scoring configuration for optical detectors
OpticalScoring scoring;
//! Number of streams
size_type num_streams{};
//! Random number generator seed
Expand Down
22 changes: 22 additions & 0 deletions src/celeritas/inp/Scoring.hh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//---------------------------------------------------------------------------//
#pragma once

#include <functional>
#include <optional>
#include <unordered_set>
#include <variant>
Expand All @@ -19,6 +20,11 @@ class G4LogicalVolume;

namespace celeritas
{
namespace optical
{
struct DetectorHit;
}

namespace inp
{
//---------------------------------------------------------------------------//
Expand Down Expand Up @@ -148,6 +154,22 @@ struct Scoring
std::optional<SimpleCalo> simple_calo;
};

//---------------------------------------------------------------------------//
/*!
* Enable detector callback for hits in optical physics simulations.
*/
struct OpticalScoring
{
//!@{
//! \name Type aliases
using HitCallbackFunc
= std::function<void(Span<optical::DetectorHit> const&)>;
//!@}

//! Hit callback function for optical detectors
std::optional<HitCallbackFunc> detector_callback;
};

//---------------------------------------------------------------------------//
} // namespace inp
} // namespace celeritas
4 changes: 4 additions & 0 deletions src/celeritas/optical/CoreParams.hh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace optical
//---------------------------------------------------------------------------//
class MaterialParams;
class PhysicsParams;
class ScoringParams;
class SimParams;
class SurfacePhysicsParams;
//---------------------------------------------------------------------------//
Expand All @@ -57,6 +58,7 @@ class CoreParams final : public ParamsDataInterface<CoreParamsData>
using SPConstSurface = std::shared_ptr<SurfaceParams const>;
using SPConstSurfacePhysics = std::shared_ptr<SurfacePhysicsParams const>;
using SPConstDetectors = std::shared_ptr<DetectorParams const>;
using SPConstScoring = std::shared_ptr<ScoringParams const>;

using SPConstCherenkov = std::shared_ptr<CherenkovParams const>;
using SPConstScintillation = std::shared_ptr<ScintillationParams const>;
Expand Down Expand Up @@ -85,6 +87,7 @@ class CoreParams final : public ParamsDataInterface<CoreParamsData>
SPConstSurfacePhysics surface_physics;
SPConstDetectors detectors;

SPConstScoring scoring; //!< Optional
SPConstCherenkov cherenkov; //!< Optional
SPConstScintillation scintillation; //!< Optional

Expand Down Expand Up @@ -134,6 +137,7 @@ class CoreParams final : public ParamsDataInterface<CoreParamsData>
SPAuxRegistry const& aux_reg() const { return input_.aux_reg; }
SPGeneratorRegistry const& gen_reg() const { return input_.gen_reg; }
SPConstDetectors const& detectors() const { return input_.detectors; }
SPConstScoring const& scoring() const { return input_.scoring; }
SPConstCherenkov const& cherenkov() const { return input_.cherenkov; }
SPConstScintillation const& scintillation() const
{
Expand Down
1 change: 1 addition & 0 deletions src/celeritas/optical/CoreTrackData.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ void resize(CoreStateData<Ownership::value, M>* state,
resize(&state->particle, size);
resize(&state->physics, size);
resize(&state->rng, params.rng, stream_id, size);
resize(&state->scoring, size);
resize(&state->sim, size);
resize(&state->surface_physics, size);
resize(&state->init, stream_id, size);
Expand Down
7 changes: 5 additions & 2 deletions src/celeritas/optical/CoreTrackData.hh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "PhysicsData.hh"
#include "SimData.hh"
#include "TrackInitData.hh"
#include "detector/DetectorData.hh"
#include "gen/CherenkovData.hh"
#include "gen/ScintillationData.hh"
#include "surface/SurfacePhysicsData.hh"
Expand Down Expand Up @@ -103,6 +104,7 @@ struct CoreStateData
ParticleStateData<W, M> particle;
PhysicsStateData<W, M> physics;
RngStateData<W, M> rng;
DetectorStateData<W, M> scoring;
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 confusing...

SimStateData<W, M> sim;
SurfacePhysicsStateData<W, M> surface_physics;
TrackInitStateData<W, M> init;
Expand All @@ -116,8 +118,8 @@ struct CoreStateData
//! Whether the data are assigned
explicit CELER_FUNCTION operator bool() const
{
return geometry && particle && physics && rng && sim && surface_physics
&& init && stream_id;
return geometry && particle && physics && rng && scoring && sim
&& surface_physics && init && stream_id;
}

//! Assign from another set of data
Expand All @@ -129,6 +131,7 @@ struct CoreStateData
particle = other.particle;
physics = other.physics;
rng = other.rng;
scoring = other.scoring;
sim = other.sim;
surface_physics = other.surface_physics;
init = other.init;
Expand Down
15 changes: 15 additions & 0 deletions src/celeritas/optical/CoreTrackView.hh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "PhysicsTrackView.hh"
#include "SimTrackView.hh"
#include "TrackInitializer.hh"
#include "detector/ScoringTrackView.hh"
#include "surface/SurfacePhysicsTrackView.hh"

#if !CELER_DEVICE_COMPILE
Expand Down Expand Up @@ -82,6 +83,9 @@ class CoreTrackView
// Return a sensitive detector view
inline CELER_FUNCTION DetectorView detectors() const;

// Return a scoring view
inline CELER_FUNCTION ScoringTrackView scoring() const;

// Return an RNG engine
inline CELER_FUNCTION RngEngine rng() const;

Expand Down Expand Up @@ -151,6 +155,9 @@ CoreTrackView::operator=(TrackInitializer const& init)
// Initialize the surface state
this->surface_physics().reset();

// Initialize scoring
this->scoring().clear_hit();

return *this;
}

Expand Down Expand Up @@ -259,6 +266,14 @@ CELER_FUNCTION auto CoreTrackView::detectors() const -> DetectorView
return DetectorView{params_.detectors};
}

//---------------------------------------------------------------------------//
/*!
* Return a scoring view.
*/
CELER_FUNCTION auto CoreTrackView::scoring() const -> ScoringTrackView
{
return ScoringTrackView{states_.scoring, this->track_slot_id()};
}
//---------------------------------------------------------------------------//
/*!
* Return the RNG engine.
Expand Down
104 changes: 104 additions & 0 deletions src/celeritas/optical/detector/DetectorAction.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//------------------------------- -*- C++ -*- -------------------------------//
// Copyright Celeritas contributors: see top-level COPYRIGHT file for details
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
//---------------------------------------------------------------------------//
//! \file celeritas/optical/detector/DetectorAction.cc
//---------------------------------------------------------------------------//
#include "DetectorAction.hh"

#include "celeritas/optical/action/ActionLauncher.hh"
#include "celeritas/optical/action/TrackSlotExecutor.hh"

#include "DetectorExecutor.hh"

namespace celeritas
{
namespace optical
{
//---------------------------------------------------------------------------//
/*!
* Construct with action ID.
*/
DetectorAction::DetectorAction(ActionId aid)
: StaticConcreteAction(aid, "scoring-detector", "Score detector hits")
{
}

//---------------------------------------------------------------------------//
/*!
* Launch the detector action on host.
*/
void DetectorAction::step(CoreParams const& params, CoreStateHost& state) const
{
TrackSlotExecutor execute{
params.ptr<MemSpace::native>(), state.ptr(), DetectorExecutor{}};
launch_action(state, execute);

this->process_hits(params, state);
}

#if !CELER_USE_DEVICE
void DetectorAction::step(CoreParams const&, CoreStateDevice&) const
{
CELER_NOT_CONFIGURED("CUDA OR HIP");
}
#endif

//---------------------------------------------------------------------------//
/*!
* Process hits copied from the kernels and send them to the callback.
*/
void DetectorAction::process_hits(CoreParams const& params,
CoreStateHost& state) const
{
this->process_hits_impl<MemSpace::host>(params, state);
}

//---------------------------------------------------------------------------//
/*!
* Process hits copied from the kernels and send them to the callback.
*/
void DetectorAction::process_hits(CoreParams const& params,
CoreStateDevice& state) const
{
this->process_hits_impl<MemSpace::device>(params, state);
}

//---------------------------------------------------------------------------//
/*!
* Process hits copied from the kernels and send them to the callback.
*
* Copied hits might be invalid, and are removed before sending into the
* callback function. The callback is only execute when a non-zero amount of
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

Spelling error: "execute" should be "executed". The comment should read "The callback is only executed when a non-zero amount of valid hits occurs."

Suggested change
* callback function. The callback is only execute when a non-zero amount of
* callback function. The callback is only executed when a non-zero amount of

Copilot uses AI. Check for mistakes.
* valid hits occurs.
*/
template<MemSpace M>
void DetectorAction::process_hits_impl(CoreParams const& params,
CoreState<M>& state) const
{
DetectorHitOutput hit_results;

// Copy hits (possibly from device) into pinned vector
copy_hits<M>(&hit_results, state.ref().scoring, state.stream_id());

// Erase all hits with invalid detector ID
hit_results.hits.erase(
std::remove_if(hit_results.hits.begin(),
hit_results.hits.end(),
[](DetectorHit const& hit) {
return !static_cast<bool>(hit.detector);
}),
hit_results.hits.end());

// Send hits to the callback function, if there are any
if (!hit_results.hits.empty())
{
auto const& scoring = params.scoring();
CELER_ASSERT(scoring);
scoring->process_hits(make_span(hit_results.hits));
}
}

//---------------------------------------------------------------------------//
} // namespace optical
} // namespace celeritas
35 changes: 35 additions & 0 deletions src/celeritas/optical/detector/DetectorAction.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//------------------------------ -*- cuda -*- -------------------------------//
// Copyright Celeritas contributors: see top-level COPYRIGHT file for details
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
//---------------------------------------------------------------------------//
//! \file celeritas/optical/detector/DetectorAction.cu
//---------------------------------------------------------------------------//
#include "DetectorAction.hh"

#include "celeritas/optical/action/ActionLauncher.device.hh"
#include "celeritas/optical/action/TrackSlotExecutor.hh"

#include "DetectorExecutor.hh"

namespace celeritas
{
namespace optical
{
//---------------------------------------------------------------------------//
/*!
* Launch the detector action on device.
*/
void DetectorAction::step(CoreParams const& params, CoreStateDevice& state) const
{
TrackSlotExecutor execute{
params.ptr<MemSpace::native>(), state.ptr(), DetectorExecutor{}};

static ActionLauncher<decltype(execute)> const launch_kernel(*this);
launch_kernel(state, execute);

this->process_hits(params, state);
}

//---------------------------------------------------------------------------//
} // namespace optical
} // namespace celeritas
62 changes: 62 additions & 0 deletions src/celeritas/optical/detector/DetectorAction.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//------------------------------- -*- C++ -*- -------------------------------//
// Copyright Celeritas contributors: see top-level COPYRIGHT file for details
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
//---------------------------------------------------------------------------//
//! \file celeritas/optical/detector/DetectorAction.hh
//---------------------------------------------------------------------------//
#pragma once

#include <algorithm>

#include "celeritas/optical/CoreParams.hh"
#include "celeritas/optical/CoreState.hh"
#include "celeritas/optical/action/ActionInterface.hh"

#include "DetectorData.hh"
#include "ScoringParams.hh"

namespace celeritas
{
namespace optical
{
//---------------------------------------------------------------------------//
/*!
* Record sensitive detector data for optical photons at the end of every step.
*
* The \c DetectorExecutor is responsible for copying hit data for every photon
* into the state buffer at the end of every step on a kernel level. Even if a
* track was not in a detector, it is still copied into the state buffer with
* an invalid detector ID. All hits are copied into pinned memory on the host,
* where invalid hits are erased. A span of only valid hits is then passed into
* the user provided callback function.
*/
class DetectorAction final : public OpticalStepActionInterface,
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 to be consistent these should all be ScoringAction/Data/Executor... we might want to defer renaming to a different PR though, or at least lay out what the

I was also confused initially because DetectorAction is just an implementation detail of ScoringParams, and the creation of those is what does all the work (kind of like the optical offload). The action/executor definitely need to be in a detail namespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking back on it, I think scoring is a bad name cause that is usually used for the simpler tallying of things like energy deposits. Maybe a DetectorHitParams or UserDetectorParams is better for indicating that it's meant to deal with the actual hits and callbacks?

Copy link
Member

@sethrj sethrj Feb 4, 2026

Choose a reason for hiding this comment

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

Don't think too hard about it; we could easily end up in a hell of combining detector, sensitive, hit, callback, user, score, tally, ...

I think "sensitive detector" is a special case of a "scoring region": the scoring region is specifically a set of logical volumes, and the scoring action is a general "callback" capability that gets the entire step.

An arguably scoring regions are just a specialization of a "user stepping action"...

public StaticConcreteAction
{
public:
// Construct with ID
explicit DetectorAction(ActionId);

// Launch kernel with host data
void step(CoreParams const&, CoreStateHost&) const final;

// Launch kernel with device data
void step(CoreParams const&, CoreStateDevice&) const final;

//! Dependency ordering of the action
StepActionOrder order() const final { return StepActionOrder::post; }

private:
//!@{
//! Process hits copied from the kernels and send them to the callback
void process_hits(CoreParams const&, CoreStateHost&) const;
void process_hits(CoreParams const&, CoreStateDevice&) const;

template<MemSpace M>
void process_hits_impl(CoreParams const&, CoreState<M>&) const;
//!@}
};

//---------------------------------------------------------------------------//
} // namespace optical
} // namespace celeritas
Loading
Loading