diff --git a/.github/codecov.yml b/.github/codecov.yml index efe84845db..2129699f2c 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -17,7 +17,7 @@ coverage: # 'auto' will use the coverage from the base commit (pull request base or # parent commit) coverage to compare against. This is the default. target: auto - # The default threshold is 1%, which means any drop in coverage will cause + # The default threshold is 1%, which means any drop in coverage will cause the CI to fail threshold: 5% base: auto if_not_found: success @@ -39,9 +39,6 @@ comment: ignore: - "test" - "app" - - "doc/**/*" - - "docs/**/*" - - "*.md" - - "scripts/**/*" - - ".github/**/*" - - "src/larceler/LarCelerStandalone*" + - "example" + - "src/larceler/**/*" + - "src/ddceler/**/*" diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 4224fd3eb6..9d55bfd393 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -43,7 +43,7 @@ jobs: - buildtype: "debug" image: "ubuntu-rocm" # Debug device code breaks HIP optimizer include: - - buildtype: "reldeb" + - buildtype: "ndebug" geometry: "vecgeom" special: "codecov" geant: "11.3" @@ -132,6 +132,12 @@ jobs: - name: Install Celeritas id: install + if: >- + ${{ + !cancelled() + && steps.build.outcome == 'success' + && matrix.special != 'codecov' + }} working-directory: build run: | echo "::group::Install" diff --git a/.github/workflows/build-fast.yml b/.github/workflows/build-fast.yml index ffe0287e54..451e57e137 100644 --- a/.github/workflows/build-fast.yml +++ b/.github/workflows/build-fast.yml @@ -81,11 +81,13 @@ jobs: run: | ln -fs scripts/cmake-presets/ci-ubuntu-github.json CMakeUserPresets.json cmake --version - cmake --preset=${CMAKE_PRESET} \ + cmake --preset=${CMAKE_PRESET} --log-level=VERBOSE \ ${{matrix.cxxstd && format('-DCMAKE_CXX_STANDARD={0} ', matrix.cxxstd) || ''}} \ - -DCeleritas_GIT_DESCRIBE="${{github.event.pull_request - && format(';-pr.{0};', github.event.pull_request.number) - || format(';-{0};', github.ref_name)}}" + -DCeleritas_VERSION_STRING="${{ + github.event.pull_request + && format('-pr.{0}', github.event.pull_request.number) + || format('-{0}', github.ref_name) + }}" ### BUILD ### @@ -174,10 +176,12 @@ jobs: - name: Configure Celeritas run: | Copy-Item scripts/cmake-presets/ci-windows-github.json -Destination CMakeUserPresets.json - cmake --preset=$Env:CMAKE_PRESET ` - -DCeleritas_GIT_DESCRIBE="${{github.event.pull_request - && format(';-pr.{0};', github.event.pull_request.number) - || format(';-{0};', github.ref_name)}}" + cmake --preset=$Env:CMAKE_PRESET --log-level=VERBOSE ` + -DCeleritas_VERSION_STRING="${{ + github.event.pull_request + && format('-pr.{0}', github.event.pull_request.number) + || format('-{0}', github.ref_name) + }}" ### BUILD ### diff --git a/.github/workflows/build-spack.yml b/.github/workflows/build-spack.yml index 08f6be82b1..583e0dd20b 100644 --- a/.github/workflows/build-spack.yml +++ b/.github/workflows/build-spack.yml @@ -55,7 +55,7 @@ jobs: geometry: "orange" special: "static" geant: "11.3" - - build_type: "reldebinfo" + - build_type: "reldeb" geometry: "orange" special: "asanlite" geant: null @@ -79,13 +79,13 @@ jobs: geometry: "vecgeom" special: "tidy" geant: "11.2" - - build_type: "reldeb" + - build_type: "ndebug" geometry: "vecgeom" special: "codecov" geant: "11.3" env: CCACHE_DIR: "${{github.workspace}}/.ccache" - CCACHE_MAXSIZE: "${{matrix.build_type == 'reldebinfo' && '500' || '300'}}Mi" + CCACHE_MAXSIZE: "${{matrix.build_type == 'reldeb' && '500' || '300'}}Mi" CMAKE_PRESET: >- ${{format('{0}-{1}{2}{3}', matrix.build_type, @@ -358,6 +358,7 @@ jobs: !cancelled() && steps.build.outcome == 'success' && matrix.special != 'tidy' + && matrix.special != 'codecov' }} working-directory: build run: | diff --git a/.github/workflows/build-ultralite.yml b/.github/workflows/build-ultralite.yml index 37c6e1fbcc..b5e61e1017 100644 --- a/.github/workflows/build-ultralite.yml +++ b/.github/workflows/build-ultralite.yml @@ -41,9 +41,9 @@ jobs: ln -fs scripts/cmake-presets/ci-ubuntu-github.json CMakeUserPresets.json cmake --version cmake --preset=${CMAKE_PRESET} --log-level=VERBOSE \ - -DCeleritas_GIT_DESCRIBE="${{github.event.pull_request - && format(';-pr.{0};;;', github.event.pull_request.number) - || format(';-{0};;;', github.ref_name)}}" + -DCeleritas_VERSION_STRING="${{github.event.pull_request + && format('pr.{0}', github.event.pull_request.number) + || github.ref_name}}" ### BUILD ### @@ -138,10 +138,12 @@ jobs: run: | Copy-Item scripts/cmake-presets/ci-windows-github.json -Destination CMakeUserPresets.json cmake --version - cmake --preset=$Env:CMAKE_PRESET ` - -DCeleritas_GIT_DESCRIBE="${{github.event.pull_request - && format(';-pr.{0};', github.event.pull_request.number) - || format(';-{0};', github.ref_name)}}" + cmake --preset=$Env:CMAKE_PRESET --log-level=VERBOSE ` + -DCeleritas_VERSION_STRING="${{ + github.event.pull_request + && format('-pr.{0}', github.event.pull_request.number) + || format('-{0}', github.ref_name) + }}" ### BUILD ### diff --git a/.github/workflows/check-dd4hep-integration.yml b/.github/workflows/check-dd4hep-integration.yml new file mode 100644 index 0000000000..c16b371cc6 --- /dev/null +++ b/.github/workflows/check-dd4hep-integration.yml @@ -0,0 +1,56 @@ +#-------------------------------- -*- yaml -*- ---------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#-----------------------------------------------------------------------------# +# Check that dd4hep plugin builds and runs on EIC containers +#-----------------------------------------------------------------------------# + +name: Check dd4hep integration + +on: + workflow_dispatch: + workflow_call: + +concurrency: + group: build-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}-${{github.workflow}} + cancel-in-progress: true + +jobs: + check-dd4hep-integration: + container: ghcr.io/eic/eic_ci:nightly + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l -o pipefail {0} + steps: + - name: Check out + uses: actions/checkout@v5 + - name: Configure + run: | + eic-info + cmake --log-level=verbose -S . -B build \ + -DCELERITAS_USE_DD4hep=ON -DCMAKE_INSTALL_PREFIX=install + - name: Build and install + id: build + working-directory: build + run: | + cmake --build . -j8 + cmake --install . + + - name: Run preshower simulation + working-directory: example/ddceler + run: | + ./run-preshower.sh celeritas \ + --numberOfEvents 3 \ + --random.seed=1 \ + --random.enableEventSeed + - name: Upload artifacts + uses: actions/upload-artifact@v4 + if: + ${{ + !cancelled() + && steps.build.outcome == 'success' + }} + with: + name: simulation-artifacts + path: example/ddceler/output/** diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..f688d1bf6b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,171 @@ +# Celeritas AI Agent Instructions +Celeritas is a GPU-accelerated HEP detector physics library for HL-LHC, integrating with Geant4. It's a C++17 codebase with CUDA/HIP device support. + +## Architecture Overview + +### Core Components (src/) +- **corecel/**: GPU abstractions, data structures, utilities (host-device compatible code) +- **geocel/**: Geometry cell interfaces (ORANGE, VecGeom, Geant4 adapters) +- **orange/**: ORANGE geometry engine (native Celeritas geometry) +- **celeritas/**: Core physics (EM processes, particles, materials, stepping loop) +- **accel/**: Geant4 integration layer (offload mechanisms, tracking managers) + +### Data Flow Pattern +Celeritas separates *shared* data from *state* data: +- **Params**: Immutable problem setup data (physics tables, geometry) - constructed once +- **States**: Mutable per-track data (particle states, RNG states) - one per concurrent event/track +- Data exists in **host** and **device** memory spaces with explicit ownership (`value`, `reference`, `const_reference`) + +To support GPU execution, it uses data-oriented design but with object-oriented interfaces (`View`s). + +## Build & Test Workflow + +### Quick Start +Use a standard CMake workflow: +```bash +cmake -B build -G Ninja +cd build +ninja +ctest +``` + +### Testing +- Unit tests in `test/` mirror `src/` structure +- Uses GoogleTest with base-class test harness and custom helper macros +- Unit test for class ``celeritas::A::Foo`` should be defined in namespace + ``celeritas::A::test`` + +## Code Conventions + +### File Extensions +- `.hh`: Host-device compatible C++ headers (use `CELER_FUNCTION` macros) +- `.cc`: Host-only C++ +- `.cu`: CUDA kernels and launch code (compiled by nvcc, but should be HIP-compatible via macros) +- `.device.hh/.device.cc`: Requires CUDA/HIP runtime but compilable by host compiler +- `.test.cc`: Unit tests + +Most development doesn't involve CUDA/HIP code: only kernel launches (device execution) should be in `.cu` files. + +### Host-Device Compatibility +Use these macros (from `corecel/Macros.hh`): +- `CELER_FUNCTION`: Mark functions callable from both host and device +- `CELER_FORCEINLINE_FUNCTION`: Force-inline version +- `CELER_CONSTEXPR_FUNCTION`: Compile-time + host/device + +Example: +```cpp +CELER_FUNCTION real_type calculate(Real3 const& pos) { /* ... */ } +``` + +### Naming Conventions +- Classes/structs: `CapWords` (e.g., `PhysicsTrackView`) +- Functions/variables: `snake_case` +- Private members: trailing underscore (`data_`) +- Functors: agent nouns (`ModelEvaluator`), instances are verbs (`evaluate_model`) +- OpaqueId types: `FooId` for `Foo` items, `Bar_` tag struct for abstract concepts + +### Data Structures +- Use `OpaqueId` for type-safe indices, not raw integers +- End structs with `operator bool()` for validity checks +- Prefer `Collection` over raw arrays for GPU-compatible storage +- Use `Span` for array views, `Array` for fixed-size arrays + +### Assertions & Error Handling +- `CELER_EXPECT`: Preconditions (function entry) +- `CELER_ASSERT`: Internal invariants +- `CELER_ENSURE`: Postconditions (function exit) +- `CELER_VALIDATE`: Always-on user input validation +- Debug assertions only active when `CELERITAS_DEBUG=ON` + +## Critical Patterns + +### Action/Executor/Interactor Paradigm +The stepping loop uses a three-layer pattern: +- **Action**: Implements `StepActionInterface`, defines when to run (`order()`), and launches kernels for host/device via `step()` methods +- **Executor**: Wraps the interactor and handles track-level logic (e.g., `make_action_track_executor` filters by `action_id`) +- **Interactor**: Pure physics functor that operates on a minimal physics information (e.g., `MaterialView`) and returns an `Interaction` + +Example flow: +```cpp +// In Model::step() [.cc or .cu] +auto execute = make_action_track_executor( + params.ptr(), state.ptr(), this->action_id(), + InteractionApplier{MyModelExecutor{this->host_ref()}}); +launch_action(*this, params, state, execute); // or ActionLauncher for device +``` + +See `src/celeritas/em/model/KleinNishinaModel.{cc,cu}` for a complete example. + +### Building Params Data with Inserters +Physics models build device-compatible data using "inserter" classes during construction. Inserters efficiently populate `Collection` objects with deduplication and proper memory layout: +```cpp +class XsGridInserter { +public: + GridId operator()(inp::XsGrid const& grid); // Returns ID for referencing +private: + DedupeCollectionBuilder reals_; // Deduplicates identical data + CollectionBuilder grids_; // Sequential insertion +}; +``` +See `src/celeritas/grid/XsGridInserter.hh` and `src/celeritas/em/model/detail/LivermoreXsInserter.hh`. + +### Collection Groups: Managing Host-Device Data +Collections manage deeply hierarchical GPU data via type-safe indices and ranges: +- **Collection**: Array-like container with ownership (`value`/`reference`/`const_reference`) and memory space (`host`/`device`) +- **ItemId**: Type-safe index into a `Collection` (actually `OpaqueId`) +- **ItemRange**: Slice of items [begin, end) for variable-length data +- **ItemMap**: Maps one ID type to another via offset arithmetic (not a hash map!) + +Example data structure: +```cpp +template +struct MyParamsData { + Collection materials; // Array of materials + Collection components; // Backend storage + Collection reals; // Backend reals + // Each Material has ItemRange referencing components + // Templated operator= enables copying across memory spaces + // Boolean operator validates initialization and copying +}; +``` + +Collections power the params/states architecture: build on host with `Ownership::value`, copy to device, then access via `const_reference` (params) or `reference` (states). See `src/corecel/data/Collection.hh` for details. + +### Test Requirements +Every class needs a unit test with cyclomatic complexity coverage. Detail classes (in `detail/` namespaces) are exempt but still recommended. + +## Integration Points + +### Geant4 Integration (accel/) +Users integrate via `SharedParams`, `TrackingManagerConstructor`, and run actions (`BeginOfRunAction`, `EndOfRunAction`). See `example/accel/` for templates. Use `celeritas_target_link_libraries()` instead of `target_link_libraries()` to handle VecGeom RDC linking. + +### Standalone Execution (app/) +EM-only execution is used for performance testing and verification via `celer-sim`. + +### Geometry +Supports ORANGE (native), VecGeom, and Geant4 geometries. GDML is the standard interchange format. Geometry loads through `inp::Model` (see `geocel/inp/Model.hh`). + +## Documentation + +- Doxygen comments go next to **definitions**, not declarations +- Document `operator()` behavior in class comment, not operator itself +- Use `\citep{author-keyword-year}` for references (maintained in Zotero at `doc/_static/zotero.bib`) +- Physics constants need units and paper citations + +## Development Tools + +- **pre-commit**: Auto-formats code (clang-format enforces 80-column limit, East const) +- **celeritas-gen.py** (`scripts/dev/`): Generate file skeletons with proper decorations +- **CMake presets**: System-specific configs in `scripts/cmake-presets/.json` + +## Common Pitfalls + +- Never copy-paste code: instead, refactor into reusable functors +- Failing to mark functions `CELER_FUNCTION` will cause `call to __host__ function from __device__` errors + +## External Dependencies + +Key dependencies (see `scripts/spack-packages.yaml` for versions): +- Geant4 +- GoogleTest (tests), CLI11 (apps), nlohmann_json (I/O) +- Optional: VecGeom, ROOT, HepMC3, DD4hep, MPI, OpenMP, Perfetto diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bcb62bf5a..101712bd4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,9 @@ cmake_minimum_required(VERSION 3.18...4.1) +# Fallback version in case of shallow clone: update when tagging "-dev" +set(Celeritas_VERSION 0.7) +# Load actual version from git metadata include("${CMAKE_CURRENT_LIST_DIR}/cmake/CgvFindVersion.cmake") cgv_find_version(Celeritas) @@ -30,6 +33,7 @@ endif() # Optional dependencies celeritas_optional_package(covfie "Use Covfie field integrator") +celeritas_optional_package(DD4hep "Enable DD4hep integration") celeritas_optional_package(Geant4 "Enable Geant4 adapter tools") celeritas_optional_package(HepMC3 "Enable HepMC3 event record reader") celeritas_optional_package(LArSoft "Build LArSoft data interfaces") @@ -82,9 +86,17 @@ if(CELERITAS_BUILD_TESTS) mark_as_advanced(CELERITAS_TEST_XML) endif() +if(CELERITAS_USE_VecGeom AND CELERITAS_USE_Geant4) + set(_needs_g4vg ON) +else() + set(_needs_g4vg OFF) +endif() + # Automatic options celeritas_force_package(CLI11 ${CELERITAS_BUILD_APPS}) celeritas_force_package(GTest ${CELERITAS_BUILD_TESTS}) +celeritas_force_package(G4VG ${_needs_g4vg}) +celeritas_force_package(nlohmann_json ON) #----------------------------------------------------------------------------# # PACKAGE-SPECIFIC OPTIONS @@ -332,6 +344,21 @@ set(CELERITAS_RUNTIME_OUTPUT_DIRECTORY # ... #----------------------------------------------------------------------------# +# Model explicit package dependencies +foreach(_pkg LArSoft DD4hep) + if(CELERITAS_USE_${_pkg}) + foreach(_dep ROOT Geant4) + if(NOT CELERITAS_USE_${_dep}) + celeritas_error_incompatible_option( + "${_pkg} requires ${_dep}" CELERITAS_USE_${_dep} ON + ) + endif() + endforeach() + endif() +endforeach() + +#----------------------------------------------------------------------------# + include(CeleritasUtils) if(CELERITAS_USE_CLI11) @@ -344,14 +371,15 @@ if(BUILD_TESTING) endif() if(CELERITAS_USE_LArSoft) - foreach(_dep ROOT Geant4) - if(NOT CELERITAS_USE_${_dep}) - celeritas_error_incompatible_option( - "LArSoft (LArSoft) requires ${_dep}" CELERITAS_USE_${_dep} ON - ) - endif() - endforeach() - find_package(LArSoft REQUIRED) + if(NOT LArSoft_FOUND) + find_package(LArSoft REQUIRED) + endif() +endif() + +if(CELERITAS_USE_DD4hep) + if(NOT DD4hep_FOUND) + find_package(DD4hep REQUIRED COMPONENTS DDCore DDG4) + endif() endif() if(CELERITAS_USE_CUDA) @@ -389,8 +417,10 @@ endif() # Unconditionally set Thrust availability for downstream configuration celeritas_force_package(Thrust "${Thrust_FOUND}") -if(CELERITAS_USE_Geant4 AND NOT Geant4_FOUND) - find_package(Geant4 REQUIRED) +if(CELERITAS_USE_Geant4) + if(NOT Geant4_FOUND) + find_package(Geant4 REQUIRED) + endif() if(CELERITAS_OPENMP STREQUAL "track" AND Geant4_multithreaded_FOUND) message(WARNING "Track-level OpenMP will conflict with MT runs of geant4:" "consider setting CELERITAS_OPENMP to \"event\" or disabling OpenMP" @@ -453,8 +483,11 @@ if(CELERITAS_USE_ROOT) ON ) endif() - # ROOT requirement is due to missing CMake commands in old versions - find_package(ROOT 6.24 REQUIRED) + if(NOT ROOT_FOUND) + # ROOT requirement is due to missing CMake commands in < 6.24 + # and Python3 compatibility in 6.28 + find_package(ROOT 6.28 REQUIRED) + endif() if(ROOT_CXX_STANDARD AND CMAKE_CXX_STANDARD AND NOT (ROOT_CXX_STANDARD STREQUAL CMAKE_CXX_STANDARD)) message(WARNING "ROOT C++ standard (${ROOT_CXX_STANDARD}) " @@ -463,6 +496,7 @@ if(CELERITAS_USE_ROOT) endif() endif() + if(CELERITAS_USE_covfie) if(CELERITAS_USE_CUDA OR CELERITAS_USE_HIP) set(_min_covfie_version 0.14) diff --git a/CMakePresets.json b/CMakePresets.json index 48b9737e34..ede2da7689 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -8,7 +8,7 @@ "binaryDir": "${sourceDir}/build-${presetName}", "generator": "Ninja", "cacheVariables": { - "Celeritas_GIT_DESCRIBE": "", + "Celeritas_CGV_CACHE": "", "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "ON"}, "CMAKE_CXX_COMPILER_LAUNCHER": {"type": "STRING", "value": "$env{CCACHE_PROGRAM}"}, @@ -47,6 +47,7 @@ "CELERITAS_USE_PNG": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"} } }, @@ -145,8 +146,9 @@ "hidden": true, "description": "Enable coverage using GCC/LLVM", "cacheVariables": { - "CELERITAS_C_FLAGS": {"type": "STRING", "value": "-fprofile-arcs;-ftest-coverage"}, - "CELERITAS_CXX_FLAGS": {"type": "STRING", "value": "-fprofile-arcs;-ftest-coverage"}, + "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_C_FLAGS": {"type": "STRING", "value": "-fprofile-arcs;-ftest-coverage"}, + "CELERITAS_CXX_FLAGS": {"type": "STRING", "value": "-fprofile-arcs;-ftest-coverage;-DCELERITAS_GCOV"}, "CELERITAS_LINK_FLAGS": {"type": "STRING", "value": "-fprofile-arcs"} } }, diff --git a/README.md b/README.md index b5c068e3a2..cd00321f1a 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,15 @@ requirements of the [HL-LHC upgrade][HLLHC]. # Documentation Most of the Celeritas documentation is readable through the codebase through a -combination of [static RST documentation][inline-docs] and Doxygen-markup -comments in the source code itself. The full [Celeritas user -documentation][user-docs] (including selected code documentation incorporated -by Breathe) and the [Celeritas code documentation][dev-docs] are mirrored on -our GitHub pages site. You can generate these yourself (if the necessary -prerequisites are installed) by -setting the `CELERITAS_BUILD_DOCS=ON` configuration option and running -`ninja doc` (user) or `ninja doxygen` (developer). - -[inline-docs]: doc/index.rst +combination of [static RST documentation][doc/index.rst] and Doxygen-markup +comments in the source code itself. +The full [Celeritas user documentation][user-docs] (including selected code +documentation incorporated by Breathe) and the [Celeritas code +documentation][dev-docs] are mirrored on our GitHub pages site. +You can generate these yourself (if the necessary prerequisites are installed) by +setting the `CELERITAS_BUILD_DOCS=ON` configuration option and running `ninja +doc` (user) or `ninja doxygen` (developer). + [user-docs]: https://celeritas-project.github.io/celeritas/user/index.html [dev-docs]: https://celeritas-project.github.io/celeritas/dev/index.html @@ -146,14 +145,14 @@ installed and want to do development on a CUDA system with Ampere-class graphics cards, execute the following steps from within the cloned Celeritas source directory: ```console -# Set up CUDA (optional) -$ spack external find cuda -# Install celeritas dependencies +# Create an environment from celeritas dependencies $ spack env create celeritas scripts/spack.yaml $ spack env activate celeritas -$ spack config add packages:all:variants:"cxxstd=17 +cuda cuda_arch=80" +# Set up CUDA/HIP (optional; example here is for Nvidia A100) +$ spack external find --not-buildable cuda +$ spack config add packages:all:prefer:"+cuda cuda_arch=80" +# Install dependencies $ spack install -# Set up # Configure, build, and test with a default development configure $ ./scripts/build.sh dev ``` @@ -169,9 +168,9 @@ $ make && ctest > [!NOTE] > It is **highly** recommended to use the `build.sh` script to set up your -> environment, even when not using Spack. The first time you run it, edit the -> `CMakeUserPresets.json` symlink it creates, and submit it in your next pull -> request. +> environment, even when not using Spack, for reproducibility and error +> checking. The first time you run it, edit the `CMakeUserPresets.json` +> symlink it creates, and submit it in your next pull request. Celeritas guarantees full compatibility and correctness only on the combinations of compilers and dependencies tested under continuous integration. @@ -188,7 +187,7 @@ See the configure output from the [GitHub runners][runners] for the full list of - C++ standard - C++17 and C++20 - Dependencies: - - Geant4 11.0.4 + - Geant4 10.5-11.4 - VecGeom 1.2.10 Partial compatibility and correctness is available for an extended range of @@ -214,9 +213,14 @@ Compatibility fixes that do not cause newer versions to fail are welcome. -See the [contribution guide][contributing-guidelines] for the contribution process, +See +[the contribution guide][contributing-guidelines] for the contribution process, [the development guidelines][development-guidelines] for further -details on coding in Celeritas, and [the administration guidelines][administration-guidelines] for community standards and roles. +details on coding in Celeritas, and +[the administration guidelines][administration-guidelines] for community +standards and roles. +The [AGENTS.md file][agents.md] contains instructions for AI tools but is also +a good quick-start guide for humans. [contributing-guidelines]: https://celeritas-project.github.io/celeritas/user/development/contributing.html [development-guidelines]: https://celeritas-project.github.io/celeritas/user/development/coding.html diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 5afc83a5ef..9250d28f00 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -48,9 +48,25 @@ celeritas_target_include_directories(celer_app_utils ) #-----------------------------------------------------------------------------# -# Geant4/ROOT data interface +# Helper apps #-----------------------------------------------------------------------------# +# Configuration output +celeritas_add_executable(celer-config celer-config.cc) +celeritas_target_link_libraries(celer-config + Celeritas::corecel + nlohmann_json::nlohmann_json + CLI11::CLI11 + celer_app_utils +) + +# Target to run celer-config +add_custom_target(get-config + COMMAND "$" "-d" "--indent" -1 + VERBATIM + DEPENDS celer-config +) + # Orange JSON updater script celeritas_add_executable(orange-update orange-update.cc) celeritas_target_link_libraries(orange-update diff --git a/app/celer-config.cc b/app/celer-config.cc new file mode 100644 index 0000000000..910c4c5804 --- /dev/null +++ b/app/celer-config.cc @@ -0,0 +1,106 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celer-config.cc +//! \brief Write the Celeritas JSON configuration to stdout +//---------------------------------------------------------------------------// +#include +#include +#include +#include +#include + +#include "corecel/Assert.hh" +#include "corecel/AssertIO.json.hh" // IWYU pragma keep +#include "corecel/io/BuildOutput.hh" +#include "corecel/io/ExceptionOutput.hh" +#include "corecel/io/JsonPimpl.hh" +#include "corecel/io/Logger.hh" +#include "corecel/sys/Device.hh" +#include "corecel/sys/DeviceIO.json.hh" // IWYU pragma keep +#include "corecel/sys/ScopedMpiInit.hh" + +#include "CliUtils.hh" + +namespace celeritas +{ +namespace app +{ +namespace +{ +//---------------------------------------------------------------------------// +struct Args +{ + int indent{1}; + bool show_device{false}; +}; + +//---------------------------------------------------------------------------// +nlohmann::json device_to_json() +{ + if (Device::num_devices() == 0) + { + CELER_LOG(info) << "No GPUs were detected"; + return nullptr; + } + + try + { + celeritas::activate_device(); + CELER_VALIDATE(celeritas::Device::num_devices() != 0, + << "no GPUs were detected"); + } + catch (...) + { + return output_to_json(ExceptionOutput{std::current_exception()}); + } + + return celeritas::device(); +} + +void run(Args const& args) +{ + CELER_VALIDATE(args.indent >= -1 && args.indent < 80, + << "invalid indentation " << args.indent); + + auto result = output_to_json(BuildOutput{}); + if (args.show_device) + { + result["device"] = device_to_json(); + } + + std::cout << result.dump(/* indent = */ args.indent) << std::endl; +} + +//---------------------------------------------------------------------------// +} // namespace +} // namespace app +} // namespace celeritas + +//---------------------------------------------------------------------------// +/*! + * Execute and run. + */ +int main(int argc, char* argv[]) +{ + using namespace celeritas::app; + + celeritas::ScopedMpiInit scoped_mpi(&argc, &argv); + if (scoped_mpi.is_world_multiprocess()) + { + CELER_LOG(critical) << "This app cannot run in parallel"; + return EXIT_FAILURE; + } + + auto& cli = cli_app(); + cli.description("Write the Celeritas build configuration to stdout"); + + Args args; + cli.add_flag("-d,--device", args.show_device, "Activate and query GPU"); + cli.add_option("-i,--indent", args.indent, "JSON indentation") + ->default_val(args.indent); + + CELER_CLI11_PARSE(argc, argv); + return run_safely(run, args); +} diff --git a/app/celer-g4/ActionInitialization.cc b/app/celer-g4/ActionInitialization.cc index ddec13b33f..77cc23953b 100644 --- a/app/celer-g4/ActionInitialization.cc +++ b/app/celer-g4/ActionInitialization.cc @@ -58,7 +58,7 @@ ActionInitialization::ActionInitialization(SPParams params) //---------------------------------------------------------------------------// /*! - * Construct actions on the master thread. + * Construct actions on the manager thread. * * Since our \c RunAction::EndOfRunAction only calls \c SharedParams::Finalize * on the master thread, we need a special case for MT mode. diff --git a/app/celer-g4/CMakeLists.txt b/app/celer-g4/CMakeLists.txt index 814dbfd36e..44ebcdbb17 100644 --- a/app/celer-g4/CMakeLists.txt +++ b/app/celer-g4/CMakeLists.txt @@ -57,9 +57,11 @@ endif() set(_driver "${CMAKE_CURRENT_SOURCE_DIR}/test-harness.py") set(_exe "$") set(_model "${CELER_APP_DATA_DIR}/simple-cms.gdml") +set(_test_ext "cms") if(NOT CELERITAS_DEBUG) set(_events "${CELER_APP_DATA_DIR}/ttbarsplit-12evt-1108prim.hepmc3") + set(_test_ext "${_label}-large") else() # Use fewer events for debug set(_events @@ -119,11 +121,12 @@ function(celer_app_test ext) list(APPEND _extra_env "CELER_DISABLE=1") endif() - add_test(NAME "app/celer-g4:${ext}" + set(_name "app/celer-g4:${_test_ext}:${ext}") + add_test(NAME "${_name}" COMMAND "${CELER_PYTHON}" "${_driver}" "${_exe}" "${_model}" "${_events}" ) - set_tests_properties("app/celer-g4:${ext}" PROPERTIES + set_tests_properties("${_name}" PROPERTIES ENVIRONMENT "${_env};${_extra_env}" REQUIRED_FILES "${_required_files}" LABELS "${_labels}" diff --git a/app/celer-g4/DetectorConstruction.cc b/app/celer-g4/DetectorConstruction.cc index 713255b680..3486974671 100644 --- a/app/celer-g4/DetectorConstruction.cc +++ b/app/celer-g4/DetectorConstruction.cc @@ -68,8 +68,8 @@ DetectorConstruction::DetectorConstruction(SPParams params) /*! * Load geometry and sensitive detector volumes. * - * This should only be called once from the master thread, toward the very - * beginning of the program. + * This will be called only once, from the manager/single thread, during + * Geant4 initialization. */ G4VPhysicalVolume* DetectorConstruction::Construct() { diff --git a/app/celer-g4/GeantDiagnostics.cc b/app/celer-g4/GeantDiagnostics.cc index 321b9201be..2c5c07ee76 100644 --- a/app/celer-g4/GeantDiagnostics.cc +++ b/app/celer-g4/GeantDiagnostics.cc @@ -53,7 +53,7 @@ auto GeantDiagnostics::queued_output() -> VecOutputInterface& //---------------------------------------------------------------------------// /*! - * Construct from shared Celeritas params on the master thread. + * Construct from shared Celeritas params on the main thread. */ GeantDiagnostics::GeantDiagnostics(SharedParams const& params) { diff --git a/app/celer-g4/GeantDiagnostics.hh b/app/celer-g4/GeantDiagnostics.hh index b631636d8a..dfd1286a11 100644 --- a/app/celer-g4/GeantDiagnostics.hh +++ b/app/celer-g4/GeantDiagnostics.hh @@ -29,8 +29,8 @@ class DetectorConstruction; /*! * Diagnostics for Geant4 (i.e., for tracks not offloaded to Celeritas). * - * A single instance of this class should be created by the master thread and - * shared across all threads. + * A single instance of this class should be created by the main thread and + * shared among worker threads. */ class GeantDiagnostics { diff --git a/app/celer-g4/LogHandlers.cc b/app/celer-g4/LogHandlers.cc index a4c19c25e8..12c0b0784e 100644 --- a/app/celer-g4/LogHandlers.cc +++ b/app/celer-g4/LogHandlers.cc @@ -47,7 +47,7 @@ void handle_serial(LogProvenance prov, LogLevel lev, std::string msg) } //---------------------------------------------------------------------------// -//! Tag a singular output with worker/master: should usually be master +//! Tag a singular output with worker/main: should usually be main void handle_mt_world(LogProvenance prov, LogLevel lev, std::string msg) { if (G4Threading::G4GetThreadId() > 0) @@ -143,7 +143,7 @@ LogHandler make_world_handler() return SelfLogHandler{get_geant_num_threads()}; } - // Only master and the first worker write + // Only the main thread (and a single worker if MT) write return handle_mt_world; } diff --git a/app/celer-g4/RunAction.cc b/app/celer-g4/RunAction.cc index c1866fdb8b..c6c09aa9b6 100644 --- a/app/celer-g4/RunAction.cc +++ b/app/celer-g4/RunAction.cc @@ -66,7 +66,7 @@ void RunAction::BeginOfRunAction(G4Run const* run) if (init_shared_) { - // This worker (or master thread) is responsible for initializing + // This is the main thread responsible for initializing // celeritas: initialize shared data and setup GPU on all threads CELER_TRY_HANDLE(params_->Initialize(*options_), call_g4exception); CELER_ASSERT(*params_); diff --git a/app/celer-g4/RunInput.cc b/app/celer-g4/RunInput.cc index f9f46c9bff..4551290126 100644 --- a/app/celer-g4/RunInput.cc +++ b/app/celer-g4/RunInput.cc @@ -6,175 +6,12 @@ //---------------------------------------------------------------------------// #include "RunInput.hh" -#include - -#include "corecel/Config.hh" - #include "corecel/io/EnumStringMapper.hh" -#include "corecel/io/Logger.hh" -#include "corecel/math/ArrayUtils.hh" -#include "geocel/GeantUtils.hh" -#include "celeritas/inp/StandaloneInput.hh" -#include "celeritas/phys/PrimaryGeneratorOptions.hh" -#include "accel/SharedParams.hh" namespace celeritas { namespace app { -namespace -{ -//---------------------------------------------------------------------------// -inp::System load_system(RunInput const& ri) -{ - inp::System s; - - if (celeritas::Device::num_devices()) - { - inp::Device d; - d.stack_size = ri.cuda_stack_size; - d.heap_size = ri.cuda_heap_size; - s.device = std::move(d); - } - - s.environment = {ri.environ.begin(), ri.environ.end()}; - - return s; -} - -//---------------------------------------------------------------------------// -inp::Problem load_problem(RunInput const& ri) -{ - inp::Problem p; - - // Model definition - p.model.geometry = ri.geometry_file; - - p.control.num_streams = get_geant_num_threads(); - - // NOTE: old SetupOptions input *per stream*, but inp::Problem needs - // *integrated* over streams - p.control.capacity = [&ri, num_streams = p.control.num_streams] { - inp::CoreStateCapacity c; - c.tracks = ri.num_track_slots * num_streams; - c.initializers = ri.initializer_capacity * num_streams; - c.secondaries = static_cast( - ri.secondary_stack_factor * ri.num_track_slots * num_streams); - c.primaries = ri.auto_flush; - return c; - }(); - - if (celeritas::Device::num_devices()) - { - inp::DeviceDebug dd; - dd.sync_stream = ri.action_times; - p.control.device_debug = std::move(dd); - } - - if (ri.track_order != TrackOrder::size_) - { - p.control.track_order = ri.track_order; - } - - { - inp::CoreTrackingLimits& limits = p.tracking.limits; - limits.steps = ri.max_steps; - } - - // Field setup - if (ri.field_type == "rzmap") - { - CELER_LOG(info) << "Loading RZMapField from " << ri.field_file; - std::ifstream inp(ri.field_file); - CELER_VALIDATE(inp, - << "failed to open field map file at '" << ri.field_file - << "'"); - - // Read RZ map from file - RZMapFieldInput rzmap; - inp >> rzmap; - - // Replace driver options with user options - rzmap.driver_options = ri.field_options; - - p.field = std::move(rzmap); - } - else if (ri.field_type == "uniform") - { - inp::UniformField field; - field.strength = ri.field; - - auto field_val = norm(field.strength); - if (field_val > 0) - { - CELER_LOG(info) << "Using a uniform field " << field_val << " [T]"; - field.driver_options = ri.field_options; - p.field = std::move(field); - } - } - else - { - CELER_VALIDATE(false, - << "invalid field type '" << ri.field_type << "'"); - } - - if (ri.sd_type != SensitiveDetectorType::none) - { - // Activate Geant4 SD callbacks - p.scoring.sd.emplace(); - } - - { - // Diagnostics - auto& d = p.diagnostics; - d.output_file = ri.output_file; - d.export_files.physics = ri.physics_output_file; - d.export_files.offload = ri.offload_output_file; - d.timers.action = ri.action_times; - d.perfetto_file = ri.tracing_file; - - if (!ri.slot_diagnostic_prefix.empty()) - { - inp::SlotDiagnostic slot_diag; - slot_diag.basename = ri.slot_diagnostic_prefix; - d.slot = std::move(slot_diag); - } - - if (ri.step_diagnostic) - { - inp::StepDiagnostic step; - step.bins = ri.step_diagnostic_bins; - d.step = std::move(step); - } - } - - CELER_VALIDATE(ri.macro_file.empty(), - << "macro file is no longer supported"); - - return p; -} - -//---------------------------------------------------------------------------// -inp::Events load_events(RunInput const& ri) -{ - CELER_VALIDATE(ri.event_file.empty() != !ri.primary_options, - << "either a event filename or options to generate " - "primaries must be provided (but not both)"); - - if (!ri.event_file.empty()) - { - inp::ReadFileEvents rfe; - rfe.event_file = ri.event_file; - return rfe; - } - - CELER_ASSERT(ri.primary_options); - return to_input(ri.primary_options); -} - -//---------------------------------------------------------------------------// -} // namespace - //---------------------------------------------------------------------------// /*! * Get a string corresponding to the physics list selection. @@ -218,42 +55,6 @@ RunInput::operator bool() const && (step_diagnostic_bins > 0 || !step_diagnostic); } -//---------------------------------------------------------------------------// -/*! - * Convert to standalone input format. - */ -inp::StandaloneInput to_input(RunInput const& ri) -{ - inp::StandaloneInput si; - - si.system = load_system(ri); - si.problem = load_problem(ri); - - // Set up Geant4 - if (ri.physics_list == PhysicsListSelection::celer_ftfp_bert) - { - // TODO: physics list is handled elsewhere - } - else - { - CELER_VALIDATE(ri.physics_list == PhysicsListSelection::celer_em, - << "invalid physics list selection '" - << to_cstring(ri.physics_list) << "' (must be 'celer')"); - } - - si.geant_setup = ri.physics_options; - - inp::PhysicsFromGeant geant_import; - geant_import.ignore_processes.push_back("CoulombScat"); - geant_import.data_selection.interpolation.type = ri.interpolation; - geant_import.data_selection.interpolation.order = ri.poly_spline_order; - si.physics_import = std::move(geant_import); - - si.events = load_events(ri); - - return si; -} - //---------------------------------------------------------------------------// } // namespace app } // namespace celeritas diff --git a/app/celer-g4/RunInput.hh b/app/celer-g4/RunInput.hh index 9620e610ce..dd083bdbbc 100644 --- a/app/celer-g4/RunInput.hh +++ b/app/celer-g4/RunInput.hh @@ -78,10 +78,10 @@ struct RunInput PrimaryGeneratorOptions primary_options; // Control - size_type num_track_slots{}; //!< Defaults to 2^18 on device, 2^10 on host + size_type num_track_slots{}; size_type max_steps{unspecified}; - size_type initializer_capacity{}; //!< Defaults to 8 * num_track_slots - real_type secondary_stack_factor{2}; + size_type initializer_capacity{}; + real_type secondary_stack_factor{}; size_type auto_flush{}; //!< Defaults to num_track_slots bool action_times{false}; @@ -128,8 +128,6 @@ struct RunInput char const* to_cstring(PhysicsListSelection value); char const* to_cstring(SensitiveDetectorType value); -inp::StandaloneInput to_input(RunInput const& run_input); - //---------------------------------------------------------------------------// } // namespace app } // namespace celeritas diff --git a/app/celer-g4/RunInputIO.json.cc b/app/celer-g4/RunInputIO.json.cc index c4f35f24e8..4b7cf3a910 100644 --- a/app/celer-g4/RunInputIO.json.cc +++ b/app/celer-g4/RunInputIO.json.cc @@ -13,9 +13,11 @@ #include "corecel/io/StringEnumMapper.hh" #include "corecel/sys/Environment.hh" #include "corecel/sys/EnvironmentIO.json.hh" +#include "geocel/GeantUtils.hh" #include "celeritas/TypesIO.json.hh" #include "celeritas/ext/GeantPhysicsOptionsIO.json.hh" #include "celeritas/field/FieldDriverOptionsIO.json.hh" +#include "celeritas/inp/Control.hh" #include "celeritas/phys/PrimaryGeneratorOptionsIO.json.hh" namespace celeritas @@ -88,19 +90,28 @@ void from_json(nlohmann::json const& j, RunInput& v) // DEPRECATED: remove in v1.0 RI_LOAD_DEPRECATED(sync, action_times); + // Get default capacities *integrated* over streams + auto capacity = inp::CoreStateCapacity::from_default( + celeritas::Device::num_devices()); + + // celer-g4 input capacities are *per-stream* + size_type num_streams = get_geant_num_threads(); + RI_LOAD_OPTION(num_track_slots); - RI_LOAD_DEFAULT(num_track_slots, - celeritas::Device::num_devices() ? 262144 : 1024); + RI_LOAD_DEFAULT(num_track_slots, capacity.tracks / num_streams); RI_LOAD_OPTION(max_steps); - RI_LOAD_DEFAULT(initializer_capacity, 8 * v.num_track_slots); - RI_LOAD_OPTION(secondary_stack_factor); + RI_LOAD_DEFAULT(initializer_capacity, capacity.initializers / num_streams); + CELER_ASSERT(capacity.secondaries); + RI_LOAD_DEFAULT( + secondary_stack_factor, + static_cast(*capacity.secondaries) / capacity.tracks); RI_LOAD_OPTION(action_times); if (j.count("default_stream")) { // DEPRECATED: remove in v1.0 CELER_LOG(warning) << "Ignoring removed option 'default_stream'"; } - RI_LOAD_DEFAULT(auto_flush, v.num_track_slots); + RI_LOAD_DEFAULT(auto_flush, capacity.primaries / num_streams); RI_LOAD_OPTION(track_order); diff --git a/app/celer-g4/celer-g4.cc b/app/celer-g4/celer-g4.cc index 83950bad68..350ed1ca55 100644 --- a/app/celer-g4/celer-g4.cc +++ b/app/celer-g4/celer-g4.cc @@ -99,7 +99,6 @@ void run(std::string_view filename, std::shared_ptr params) // Disable external error handlers ScopedRootErrorHandler scoped_root_errors; - disable_geant_signal_handler(); // Set the random seed *before* the run manager is instantiated // (G4MTRunManager constructor uses the RNG) @@ -142,10 +141,12 @@ void run(std::string_view filename, std::shared_ptr params) celeritas::TracingSession tracing{setup.input().tracing_file}; // Set up loggers - world_logger() = Logger::from_handle_env(make_world_handler(), "CELER_LOG"); - self_logger() = Logger::from_handle_env( - make_self_handler(get_geant_num_threads(*run_manager)), - "CELER_LOG_LOCAL"); + world_logger() = Logger{make_world_handler(), + getenv_loglevel("CELER_LOG", LogLevel::status)}; + + self_logger() + = Logger{make_self_handler(get_geant_num_threads(*run_manager)), + getenv_loglevel("CELER_LOG_LOCAL", LogLevel::warning)}; // Redirect Geant4 output and exceptions through Celeritas objects ScopedGeantLogger scoped_logger; diff --git a/app/celer-geo/Runner.cc b/app/celer-geo/Runner.cc index f46c36acee..8058dc03e5 100644 --- a/app/celer-geo/Runner.cc +++ b/app/celer-geo/Runner.cc @@ -55,7 +55,6 @@ Runner::Runner(ModelSetup const& input) } else { - // GCOVR_EXCL_BR_SOURCE CELER_VALIDATE(std::ifstream{input_.geometry_file}.is_open(), << "input model filename '" << input_.geometry_file << "' does not exist"); diff --git a/app/celer-geo/celer-geo.cc b/app/celer-geo/celer-geo.cc index 6880db078e..d6ad994ca8 100644 --- a/app/celer-geo/celer-geo.cc +++ b/app/celer-geo/celer-geo.cc @@ -97,7 +97,7 @@ void put_json_line(nlohmann::json const& j) */ void put_json_line(OutputInterface const& oi) { - return put_json_line(json_pimpl_output(oi)); + return put_json_line(output_to_json(oi)); } //---------------------------------------------------------------------------// @@ -336,7 +336,7 @@ void run(std::string const& filename) {"device", device()}, {"kernels", kernel_registry()}, {"environment", environment()}, - {"build", json_pimpl_output(BuildOutput{})}, + {"build", output_to_json(BuildOutput{})}, }, }, })); diff --git a/app/celer-sim/RunnerInput.hh b/app/celer-sim/RunnerInput.hh index 42c843bc7f..49e80c6673 100644 --- a/app/celer-sim/RunnerInput.hh +++ b/app/celer-sim/RunnerInput.hh @@ -60,7 +60,7 @@ struct RunnerInput struct OpticalOptions { - // Sizes are divided among streams + // *Per-process* capacities size_type num_track_slots{}; //!< Number of optical loop tracks slots size_type buffer_capacity{}; //!< Number of steps that created photons size_type auto_flush{}; //!< Threshold number of primaries for @@ -114,14 +114,12 @@ struct RunnerInput // Control unsigned int seed{}; - size_type num_track_slots{}; //!< Divided among streams. Defaults to 2^20 - //!< on device, 2^12 on host - size_type initializer_capacity{}; //!< Divided among streams. Defaults to - //!< 8 * num_track_slots + size_type num_track_slots{}; //!< Per-process track slots + size_type initializer_capacity{}; //!< Per-process initializer buffer size + real_type secondary_stack_factor{}; size_type max_steps = static_cast(-1); //!< Step *iterations* InterpolationType interpolation{InterpolationType::linear}; size_type poly_spline_order{1}; - real_type secondary_stack_factor{2}; bool use_device{}; bool action_times{}; bool merge_events{false}; //!< Run all events at once on a single stream diff --git a/app/celer-sim/RunnerInputIO.json.cc b/app/celer-sim/RunnerInputIO.json.cc index 948a52a1b5..513072c601 100644 --- a/app/celer-sim/RunnerInputIO.json.cc +++ b/app/celer-sim/RunnerInputIO.json.cc @@ -21,6 +21,7 @@ #include "celeritas/TypesIO.json.hh" #include "celeritas/ext/GeantPhysicsOptionsIO.json.hh" #include "celeritas/field/FieldDriverOptionsIO.json.hh" +#include "celeritas/inp/Control.hh" #include "celeritas/phys/PrimaryGeneratorOptionsIO.json.hh" #include "celeritas/user/RootStepWriterIO.json.hh" @@ -90,10 +91,17 @@ void from_json(nlohmann::json const& j, RunnerInput& v) LDIO_LOAD_OPTION(seed); LDIO_LOAD_REQUIRED(use_device); - LDIO_LOAD_DEFAULT(num_track_slots, v.use_device ? 1048576 : 4096); + + // Get default capacities *integrated* over streams + auto capacity = inp::CoreStateCapacity::from_default(v.use_device); + + LDIO_LOAD_DEFAULT(num_track_slots, capacity.tracks); LDIO_LOAD_OPTION(max_steps); - LDIO_LOAD_DEFAULT(initializer_capacity, 16 * v.num_track_slots); - LDIO_LOAD_OPTION(secondary_stack_factor); + LDIO_LOAD_DEFAULT(initializer_capacity, capacity.initializers); + CELER_ASSERT(capacity.secondaries); + LDIO_LOAD_DEFAULT( + secondary_stack_factor, + static_cast(*capacity.secondaries) / capacity.tracks); LDIO_LOAD_OPTION(interpolation); LDIO_LOAD_OPTION(poly_spline_order); LDIO_LOAD_OPTION(action_times); diff --git a/app/celer-sim/celer-sim.cc b/app/celer-sim/celer-sim.cc index f291c44fb9..8b03b959e3 100644 --- a/app/celer-sim/celer-sim.cc +++ b/app/celer-sim/celer-sim.cc @@ -166,7 +166,7 @@ std::string get_device_string() celeritas::activate_device(); CELER_VALIDATE(celeritas::Device::num_devices() != 0, - << "No GPUs were detected"); + << "no GPUs were detected"); return nlohmann::json(celeritas::device()).dump(1); } diff --git a/app/orange-update.cc b/app/orange-update.cc index c02130e182..4a0b4b7f3c 100644 --- a/app/orange-update.cc +++ b/app/orange-update.cc @@ -6,20 +6,14 @@ //! \brief Read in and write back an ORANGE JSON file //---------------------------------------------------------------------------// #include -#include -#include #include -#include #include #include -#include "corecel/Config.hh" - -#include "corecel/Assert.hh" #include "corecel/io/FileOrConsole.hh" #include "corecel/io/Logger.hh" #include "corecel/sys/ScopedMpiInit.hh" -#include "orange/OrangeInputIO.json.hh" +#include "orange/OrangeInputIO.json.hh" // IWYU pragma: keep #include "CliUtils.hh" diff --git a/cmake/CeleritasConfig.cmake.in b/cmake/CeleritasConfig.cmake.in index e81760356d..75d426f41f 100644 --- a/cmake/CeleritasConfig.cmake.in +++ b/cmake/CeleritasConfig.cmake.in @@ -98,6 +98,10 @@ elseif(CELERITAS_USE_HIP) find_dependency(hip REQUIRED QUIET) endif() +if(CELERITAS_USE_DD4hep) + find_dependency(DD4hep REQUIRED) +endif() + if(CELERITAS_USE_Geant4) # Geant4 calls `include_directories` for CLHEP :( which is not what we want! # Save and restore include directories around the call -- even though as a @@ -110,6 +114,10 @@ if(CELERITAS_USE_Geant4) cmake_policy(VERSION 3.10...4.0) endif() +if(CELERITAS_USE_G4VG AND NOT CELERITAS_BUILTIN_G4VG) + find_dependency(G4VG @G4VG_VERSION@ REQUIRED) +endif() + if(CELERITAS_USE_HepMC3) find_dependency(HepMC3 @HepMC3_VERSION@ REQUIRED) endif() @@ -161,10 +169,6 @@ if(CELERITAS_USE_VecGeom) "and Celeritas (CELERITAS_USE_CUDA=${CELERITAS_USE_CUDA})" ) endif() - - if(CELERITAS_USE_Geant4 AND NOT CELERITAS_BUILTIN_G4VG) - find_dependency(G4VG @G4VG_VERSION@ REQUIRED) - endif() endif() cmake_policy(POP) diff --git a/cmake/CeleritasG4Tests.cmake b/cmake/CeleritasG4Tests.cmake index 1b9134b4f0..9ca85deb9c 100644 --- a/cmake/CeleritasG4Tests.cmake +++ b/cmake/CeleritasG4Tests.cmake @@ -20,14 +20,16 @@ run manager. celeritas_g4_add_tests( - [PREFIX string] # before target in test name - [SUFFIX string] # after target in test name + [NAME string] # test name (default: target) [ARGS arg [...]] # passed to command line [LABELS label [...]] # tagged in CTestFile + [RMTYPE [serial] [mt] [task]] # run manager + [OFFLOAD [cpu] [gpu] [g4] [ko]] # offload [DISABLE] # display but don't run [WILL_FAIL] # expect the test to exit with a failure ) + Note that DISABLE silently overrides WILL_FAIL. #]=======================================================================] diff --git a/cmake/CeleritasOptionUtils.cmake b/cmake/CeleritasOptionUtils.cmake index db22647010..de5a58208e 100644 --- a/cmake/CeleritasOptionUtils.cmake +++ b/cmake/CeleritasOptionUtils.cmake @@ -248,6 +248,7 @@ endmacro() macro(celeritas_force_package package value) set(_var "CELERITAS_USE_${package}") set(${_var} ${value}) + message(VERBOSE "Celeritas: forced ${_var}=${value}") # Append to list of components _celeritas_append_optional_component(${package} "${value}") # Append to list of forced packages for export diff --git a/cmake/CgvFindVersion.cmake b/cmake/CgvFindVersion.cmake index 6d4edc4ae0..3a149acd94 100644 --- a/cmake/CgvFindVersion.cmake +++ b/cmake/CgvFindVersion.cmake @@ -1,5 +1,6 @@ #------------------------------- -*- cmake -*- -------------------------------# # SPDX-License-Identifier: Apache-2.0 +# SPDX-PackageName: "CGV: CMake Git Version" # # https://github.com/sethrj/cmake-git-version # @@ -65,6 +66,11 @@ CgvFindVersion metadata, and it will re-run cmake if that file changes, and re-run the associated git commands only if the file changes. + .. note:: Since there are cases where no metadata is available (e.g., a + shallow clone), you can provide a *fallback* version by specifying + ``${PROJECT}_VERSION`` and (optionally) ``${PROJECT}_VERSION_STRING`` at the + top level of the cmake or (if using a package manager) via the command line. + .. note:: In order for this script to work properly with archived git repositories (generated with ``git-archive`` or GitHub's release tarball feature), it's necessary to add to your ``.gitattributes`` file:: @@ -109,8 +115,8 @@ macro(_cgv_git_call_output output_var) COMMAND "${GIT_EXECUTABLE}" ${ARGN} WORKING_DIRECTORY "${CGV_SOURCE_DIR}" OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_VARIABLE GIT_ERR - RESULT_VARIABLE GIT_RESULT + ERROR_VARIABLE ${output_var}_ERR + RESULT_VARIABLE ${output_var}_RESULT OUTPUT_VARIABLE ${output_var} ) endmacro() @@ -119,9 +125,25 @@ endmacro() # Save the version with a timestamp to a cache variable function(_cgv_store_version vstring vsuffix vhash tsfile) + if(NOT vstring) + # Use fallback version 0.1.2 + set(vstring "${${CGV_PROJECT}_VERSION}") + if(vstring) + # Look for and use version string "0.1.2-x+yz" + string(REPLACE "${vstring}" "" vsuffix "${${CGV_PROJECT}_VERSION_STRING}") + message(VERBOSE "Using fallback version and string: " + "${CGV_PROJECT}_VERSION=${vstring}, " + "${CGV_PROJECT}_VERSION_STRING=${${CGV_PROJECT}_VERSION_STRING}" + ) + endif() + endif() if(NOT vstring) message(WARNING "The version metadata for ${CGV_PROJECT} could not " - "be determined: installed version number may be incorrect") + "be determined: installed version number may be incorrect. Try " + "downloading an official release tarball, using `git archive`, " + "using `git clone` without `--shallow` nor deleting `.git`, or " + "manually specifying a known version by configuring with " + "`-D${CGV_PROJECT}_VERSION=1.2.3`") endif() # Replace 11-03 with 11.3 string(REGEX REPLACE "-+0*" "." vstring "${vstring}") @@ -137,7 +159,7 @@ function(_cgv_store_version vstring vsuffix vhash tsfile) "${vstring}" "${vsuffix}" "${vhash}" "${tsfile}" "${_vtimestamp}" ) # Note: extra 'unset' is necessary if using CMake presets with - # ${CGV_PROJECT}_GIT_DESCRIBE="", even with INTERNAL/FORCE + # ${CGV_PROJECT}_CGV_CACHE="", even with INTERNAL/FORCE unset("${CGV_CACHE_VAR}" CACHE) set("${CGV_CACHE_VAR}" "${_CACHED_VERSION}" CACHE INTERNAL "Version string and hash for ${CGV_PROJECT}") @@ -146,15 +168,10 @@ endfunction() #-----------------------------------------------------------------------------# # Get the path to the git head used to describe the current repository -function(_cgv_git_path resultvar) - if(GIT_EXECUTABLE) - _cgv_git_call_output(_TSFILE "rev-parse" "--git-path" "HEAD") - else() - set(GIT_RESULT 1) - set(GIT_ERR "GIT_EXECUTABLE is not defined") - endif() - if(GIT_RESULT) - message(AUTHOR_WARNING "Failed to get path to git head: ${GIT_ERR}") +function(_cgv_git_path_head resultvar) + _cgv_git_call_output(_TSFILE "rev-parse" "--git-path" "HEAD") + if(_TSFILE_RESULT) + message(AUTHOR_WARNING "Failed to get path to git head: ${_TSFILE_ERR}") set(_TSFILE) else() get_filename_component(_TSFILE "${_TSFILE}" ABSOLUTE BASE_DIR @@ -192,23 +209,23 @@ function(_cgv_try_parse_git_describe version_string branch_string tsfile) if(CMAKE_MATCH_2) # After a pre-release, e.g. -rc.1, for SemVer compatibility - set(_prerelease "${CMAKE_MATCH_2}.${CMAKE_MATCH_4}") + set(_suffix "${CMAKE_MATCH_2}.${CMAKE_MATCH_4}") else() # After a release, e.g. -123 - set(_prerelease "-${CMAKE_MATCH_4}") + set(_suffix "-${CMAKE_MATCH_4}") endif() if(branch_string) - set(_suffix "${branch_string}.${CMAKE_MATCH_5}") + set(_hash "${branch_string}.${CMAKE_MATCH_5}") else() - set(_suffix "${CMAKE_MATCH_5}") + set(_hash "${CMAKE_MATCH_5}") endif() # Qualify the version number with the distance-to-tag and hash _cgv_store_version( "${CMAKE_MATCH_1}" # 1.2.3 - "${_prerelease}" # -rc.2.3, -beta.1, -123 - "${_suffix}" # abcdef + "${_suffix}" # -rc.2.3, -beta.1, -123 + "${_hash}" # abcdef "${tsfile}" # timestamp file ) endfunction() @@ -271,13 +288,8 @@ endfunction() #-----------------------------------------------------------------------------# # Try git's 'describe' function function(_cgv_try_git_describe) - # First time calling "git describe" if(NOT Git_FOUND) - find_package(Git QUIET) - if(NOT Git_FOUND) - message(WARNING "Could not find Git, needed to find the version tag") - return() - endif() + return() endif() if(CGV_TAG_REGEX MATCHES "^\\^?([a-z-]+)") @@ -288,12 +300,12 @@ function(_cgv_try_git_describe) # Load git description _cgv_git_call_output(_VERSION_STRING "describe" "--tags" ${_match}) - if(GIT_RESULT) - message(AUTHOR_WARNING "No suitable git tags found': ${GIT_ERR}") + if(_VERSION_STRING_RESULT) + message(AUTHOR_WARNING "No git tags match '${CGV_TAG_REGEX}': ${_VERSION_STRING_ERR}") return() endif() - if(GIT_ERR) - message(AUTHOR_WARNING "git describe warned: ${GIT_ERR}") + if(_VERSION_STRING_ERR) + message(AUTHOR_WARNING "git describe warned: ${_VERSION_STRING_ERR}") endif() if(NOT _VERSION_STRING) message(AUTHOR_WARNING "Failed to get ${CGV_PROJECT} version from git: " @@ -305,16 +317,17 @@ function(_cgv_try_git_describe) # the desired behavior _cgv_git_call_output(_BRANCH_STRING "symbolic-ref" "--short" "HEAD") - _cgv_git_path(_TSFILE) + _cgv_git_path_head(_TSFILE) _cgv_try_parse_git_describe("${_VERSION_STRING}" "${_BRANCH_STRING}" "${_TSFILE}") endfunction() #-----------------------------------------------------------------------------# function(_cgv_try_git_hash) - if(NOT GIT_EXECUTABLE) + if(NOT Git_FOUND) return() endif() + # Fall back to just getting the hash _cgv_git_call_output(_VERSION_HASH "log" "-1" "--format=%h" "HEAD") if(_VERSION_HASH_RESULT) @@ -323,10 +336,24 @@ function(_cgv_try_git_hash) return() endif() - _cgv_git_path(_TSFILE) + _cgv_git_path_head(_TSFILE) _cgv_store_version("" "" "${_VERSION_HASH}" "${_TSFILE}") endfunction() +#-----------------------------------------------------------------------------# + +function(_cgv_try_cmake) + if(NOT DEFINED "${${CGV_PROJECT}_VERSION}") + message(AUTHOR_WARNING + "No fallback version specified from ${CGV_PROJECT}_VERSION") + return() + endif() + + _cgv_store_version("" "" "" "${CMAKE_PARENT_LIST_FILE}") +endfunction() + +#-----------------------------------------------------------------------------# + function(_cgv_try_all) if(${CGV_CACHE_VAR}) # Previous configure already set the variable: check the timestamp @@ -335,7 +362,7 @@ function(_cgv_try_all) list(GET ${CGV_CACHE_VAR} 3 _tsfile) list(GET ${CGV_CACHE_VAR} 4 _timestamp) else() - message(VERBOSE "Old cache variable ${CGV_CACHE_VAR}: length=${_len}") + message(VERBOSE "Invalid cache variable ${CGV_CACHE_VAR}: length=${_len}") set(_tsfile) endif() if(_tsfile) @@ -358,6 +385,22 @@ function(_cgv_try_all) return() endif() + # Now check for git + if(NOT Git_FOUND) + find_package(Git QUIET) + if(NOT Git_FOUND) + message(WARNING "Could not find Git (needed to find the version tag)") + endif() + endif() + if(Git_FOUND) + _cgv_git_call_output(_GIT_DIR "rev-parse" "--git-dir") + if(_GIT_DIR_RESULT) + message(VERBOSE "git: ${_GIT_DIR_ERR}") + message(WARNING "Not a git repository (needed to find the version tag)") + set(Git_FOUND FALSE) + endif() + endif() + _cgv_try_git_describe() if(${CGV_CACHE_VAR}) return() @@ -368,8 +411,13 @@ function(_cgv_try_all) return() endif() + _cgv_try_cmake() + if(${CGV_CACHE_VAR}) + return() + endif() + # Fallback: no metadata detected - set(${CGV_CACHE_VAR} "" "-unknown" "") + _cgv_store_version("" "" "unknown" "none") endfunction() #-----------------------------------------------------------------------------# @@ -389,7 +437,7 @@ function(cgv_find_version) set(CGV_TAG_REGEX "v([0-9.]+)(-[a-z]+[0-9.]*)?") endif() - set(CGV_CACHE_VAR "${CGV_PROJECT}_GIT_DESCRIBE") + set(CGV_CACHE_VAR "${CGV_PROJECT}_CGV_CACHE") # Try all possible ways of obtaining metadata _cgv_try_all() @@ -427,17 +475,17 @@ if(CMAKE_SCRIPT_MODE_FILE) if(DEFINED SOURCE_DIR) set(CGV_SOURCE_DIR ${SOURCE_DIR}) endif() - cgv_find_version(TEMP) + cgv_find_version(LOCAL) if(DEFINED ONLY) # Print only the given variable, presumably VERSION or VERSION_STRING # (will print to stderr) - set(VERSION "${TEMP_VERSION}") - set(VERSION_STRING "${TEMP_VERSION_STRING}") + set(VERSION "${LOCAL_VERSION}") + set(VERSION_STRING "${LOCAL_VERSION_STRING}") message("${${ONLY}}") else() - message("VERSION=\"${TEMP_VERSION}\"") - message("VERSION_STRING=\"${TEMP_VERSION_STRING}\"") + message("VERSION=\"${LOCAL_VERSION}\"") + message("VERSION_STRING=\"${LOCAL_VERSION_STRING}\"") endif() endif() -# cmake-git-version 1.2.1-5+main.bdcc7d7 +# cmake-git-version 1.3.1 diff --git a/cmake/FindDD4hep.cmake b/cmake/FindDD4hep.cmake new file mode 100644 index 0000000000..9c3a72d77a --- /dev/null +++ b/cmake/FindDD4hep.cmake @@ -0,0 +1,32 @@ +#------------------------------- -*- cmake -*- -------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#[=======================================================================[.rst: + +FindDD4hep +---------- + +Find the Detector Description Toolkit for High Energy Physics. + +#]=======================================================================] + +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) + cmake_language(GET_MESSAGE_LOG_LEVEL _orig_log_lev) +else() + set(_orig_log_lev ${CMAKE_MESSAGE_LOG_LEVEL}) +endif() + +if(NOT _orig_log_lev OR _orig_log_lev STREQUAL "STATUS") + # Suppress verbose DD4hep cmake configuration output + set(CMAKE_MESSAGE_LOG_LEVEL WARNING) +endif() + +find_package(DD4hep QUIET CONFIG) + +set(CMAKE_MESSAGE_LOG_LEVEL ${_orig_log_lev}) +unset(_orig_log_lev) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(DD4hep CONFIG_MODE) + +#-----------------------------------------------------------------------------# diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index eb2d012e88..d797729cce 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -107,7 +107,7 @@ doxygen_add_docs(doxygen ) #-----------------------------------------------------------------------------# -# USER MANUAL (Sphinx/Breathe) +# USER MANUAL #-----------------------------------------------------------------------------# if(CELERITAS_PYTHONPATH_RERUN) @@ -137,6 +137,10 @@ else() mark_as_advanced(CELERITAS_SPHINX_ARGS) celeritas_get_pyenv(CELER_PYTHONENV) + # Build sphinx docs: + # - type: html, pdf, dummy + # - output: relative path of main output file (index.html, Celeritas.tex) + # - depends: list of file dependencies function(celeritas_build_sphinx type output depends) if(NOT type STREQUAL "dummy") set(_comment "Building Sphinx ${type} documentation: doc/${output}") @@ -184,7 +188,8 @@ function(celeritas_build_latex input output) endfunction() #-----------------------------------------------------------------------------# -# USER DOXYGEN +# USER MANUAL: Doxygen setup + function(_set_jsonbool varname) if(ARGC GREATER 1 AND ARGV1) set(${varname} true PARENT_SCOPE) @@ -263,6 +268,12 @@ file(GLOB _DOXYGEN_SOURCE "${PROJECT_SOURCE_DIR}/src/celeritas/em/msc/detail/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/em/process/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/em/xs/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/data/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/executor/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/interactor/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/model/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/model/detail/*.hh" + "${PROJECT_SOURCE_DIR}/src/celeritas/mucf/process/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/neutron/interactor/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/neutron/interactor/detail/*.hh" "${PROJECT_SOURCE_DIR}/src/celeritas/neutron/model/*.hh" @@ -273,6 +284,7 @@ file(GLOB _DOXYGEN_SOURCE "${PROJECT_SOURCE_DIR}/src/celeritas/optical/surface/model/*.hh" "${PROJECT_SOURCE_DIR}/test/*.hh" ) + # IMPORTANT: the target name and USE_STAMP_FILE generate a dependency used by # the downstream sphinx docs when Breathe is available doxygen_add_docs(doxygen_xml @@ -283,7 +295,7 @@ doxygen_add_docs(doxygen_xml ) #-----------------------------------------------------------------------------# -# GRAPHICS PROCESSING +# USER MANUAL: graphics generation (GraphViz) # Note that *all* dot files must be present at the same time to prevent # errors when doing separate HTML/PDF builds: the doctrees directory caches the @@ -329,7 +341,7 @@ foreach(_inp IN LISTS _dot_input) endforeach() #-----------------------------------------------------------------------------# -# RST PROCESSING +# USER MANUAL: Sphinx RST pre-processing if(Sphinx_FOUND) file(GLOB_RECURSE _sphinx_deps "${CMAKE_CURRENT_SOURCE_DIR}/*.rst") @@ -348,7 +360,8 @@ celeritas_build_sphinx(dummy "${_doctree_env}" "${_sphinx_deps}") add_custom_target(doc_preprocess DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${_doctree_env}") #-----------------------------------------------------------------------------# -# HTML +# USER MANUAL: Sphinx HTML generation + set(_doc_html "html/index.html") celeritas_build_sphinx(html "${_doc_html}" "${_doctree_env}") add_custom_target(doc DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${_doc_html}") @@ -359,7 +372,7 @@ install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html/" ) #-----------------------------------------------------------------------------# -# PDF +# USER MANUAL: Sphinx PDF generation set(_doc_tex "latex/Celeritas.tex") celeritas_build_sphinx(latex "${_doc_tex}" "${_doctree_env}") diff --git a/doc/development/administration.rst b/doc/development/administration.rst index c44cb07c33..3cf1c32729 100644 --- a/doc/development/administration.rst +++ b/doc/development/administration.rst @@ -462,7 +462,9 @@ commit on the ``develop`` branch that is *not* intended for version 1.0.1 (i.e., the first new feature) should be tagged with ``v1.1.0-dev``, so that ``git describe --tags --match 'v*'`` shows the new features as being part of the -``v1.1.0`` series. +``v1.1.0`` series. That pull request should *also* update the "fallback" +version number ``Celeritas_VERSION`` stored in the top-level +:file:`CMakeLists.txt`. .. _helper notebook: https://github.com/celeritas-project/celeritas-docs/blob/master/nb/admin/github-stats.ipynb .. _geant-val: https://geant-val.cern.ch diff --git a/doc/implementation/corecel/fundamentals.rst b/doc/implementation/corecel/fundamentals.rst index 8fc7e6d2be..5bc8af8c29 100644 --- a/doc/implementation/corecel/fundamentals.rst +++ b/doc/implementation/corecel/fundamentals.rst @@ -24,7 +24,7 @@ CMake configuration options are set. The assertion macros ``CELER_EXPECT``, ``CELER_ASSERT``, and ``CELER_ENSURE`` correspond to "precondition contract", "internal assertion", and "postcondition -contract". +contract". They all throw :cpp:class:`celeritas::DebugError`. .. doxygendefine:: CELER_EXPECT .. doxygendefine:: CELER_ASSERT @@ -36,24 +36,45 @@ behavior at runtime to allow compiler optimizations: .. doxygendefine:: CELER_ASSERT_UNREACHABLE .. doxygendefine:: CELER_ASSUME -Finally, a few runtime macros will always throw helpful errors based on -incorrect configuration or input values. +Finally, a few runtime macros will always throw helpful +:cpp:class:`celeritas::RuntimeError` errors based on incorrect configuration or +input values. .. doxygendefine:: CELER_VALIDATE .. doxygendefine:: CELER_NOT_CONFIGURED .. doxygendefine:: CELER_NOT_IMPLEMENTED +These macros all throw subclasses of standard exceptions. See the developer +documentation for more details. + +.. doxygenclass:: celeritas::DebugError +.. doxygenclass:: celeritas::RuntimeError + + Utility macros ^^^^^^^^^^^^^^ The :file:`corecel/Macros.hh` file defines language and compiler abstraction macro definitions. -.. doxygendefine:: CELER_TRY_HANDLE -.. doxygendefine:: CELER_TRY_HANDLE_CONTEXT - .. doxygendefine:: CELER_DEFAULT_COPY_MOVE .. doxygendefine:: CELER_DELETE_COPY_MOVE .. doxygendefine:: CELER_DEFAULT_MOVE_DELETE_COPY .. doxygendefine:: CELER_DISCARD + +.. _exception_handling: + +Exception handling +^^^^^^^^^^^^^^^^^^ + +During multithreaded execution or when called by a Geant4 simulation, it is +important not to throw C++ exceptions lest they be uncaught and result in an +immediate program abort. Celeritas provides macros and classes for managing +exceptions and providing detailed diagnostics. + +.. doxygendefine:: CELER_TRY_HANDLE +.. doxygendefine:: CELER_TRY_HANDLE_CONTEXT +.. doxygenclass:: celeritas::RichContextException +.. doxygenclass:: celeritas::MultiExceptionHandler +.. doxygenfunction:: celeritas::log_and_rethrow diff --git a/doc/implementation/geant4-interface/details.rst b/doc/implementation/geant4-interface/details.rst index 1892021dbe..43f6860601 100644 --- a/doc/implementation/geant4-interface/details.rst +++ b/doc/implementation/geant4-interface/details.rst @@ -11,58 +11,61 @@ Geant4. .. doxygenclass:: celeritas::LocalTransporter Interface utilities -------------------- +^^^^^^^^^^^^^^^^^^^ .. celerstruct:: AlongStepFactoryInput -.. doxygenfunction:: celeritas::MakeMTLogger .. doxygenclass:: celeritas::ExceptionConverter .. doxygenclass:: celeritas::AlongStepFactoryInterface .. _api_accel_adapters: Classes usable by Geant4 ------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^ These utilities are based on Celeritas data structures and capabilities but are written to be usable both by the ``celer-g4`` app and potential other users. Fields -^^^^^^ +"""""" .. doxygenclass:: celeritas::RZMapMagneticField .. doxygenclass:: celeritas::CylMapMagneticField .. doxygenfunction:: celeritas::MakeCylMapFieldInput Primary generators -^^^^^^^^^^^^^^^^^^ +"""""""""""""""""" .. doxygenclass:: celeritas::HepMC3PrimaryGenerator .. doxygenclass:: celeritas::PGPrimaryGeneratorAction -.. _api_geant4_physics_options: +Physics lists +""""""""""""" -Physics constructors -^^^^^^^^^^^^^^^^^^^^ +Two physics constructors build exclusively processes supported by Celeritas for +Geant4: -A Geant4 physics constructor :cpp:class:`celeritas::SupportedEmStandardPhysics` allows -very fine-grained selection of the EM physics processes supported by Celeritas. -The input options incorporate process and model selection as well as default EM -parameters to send to Geant4. - -.. celerstruct:: GeantPhysicsOptions .. doxygenclass:: celeritas::SupportedEmStandardPhysics +.. doxygenclass:: celeritas::SupportedOpticalPhysics -Physics lists -^^^^^^^^^^^^^ - -Two physics lists (one using Geant4 hadronics, the other using pure Celeritas) -allow setup of EM physics using only processes supported by Celeritas. +Two "modular" physics lists (one using Geant4 hadronics, the other using pure +Celeritas) are stand-ins for physics factories suitable for sending to +``G4RunManager::SetUserInitialization``. .. doxygenclass:: celeritas::EmPhysicsList .. doxygenclass:: celeritas::FtfpBertPhysicsList +.. _api_geant4_physics_options: + +Physics setup +""""""""""""" + +The input options incorporate process and model selection as well as default EM +parameters to send to Geant4. + +.. celerstruct:: GeantPhysicsOptions + Sensitive detectors -^^^^^^^^^^^^^^^^^^^ +""""""""""""""""""" .. doxygenclass:: celeritas::GeantSimpleCalo diff --git a/doc/implementation/geant4-interface/high-level.rst b/doc/implementation/geant4-interface/high-level.rst index 164dd1d6b0..572b4a5f13 100644 --- a/doc/implementation/geant4-interface/high-level.rst +++ b/doc/implementation/geant4-interface/high-level.rst @@ -4,17 +4,20 @@ .. _api_accel_high_level: High level interfaces -===================== +--------------------- The high-level integration classes are the easiest way to add Celeritas to a Geant4 application. Under the hood, it contains a singleton class instance that sets up the UI commands (see :cpp:class:`celeritas::SetupOptionsMessenger`), -MPI (if configured), and Celeritas logging. +MPI (if configured), and Celeritas logging (redirecting "world" logging with +:cpp:func:`celeritas::MakeMTWorldLogger` and "self" logging with +:cpp:func:`celeritas::MakeMTSelfLogger`) . .. doxygenclass:: celeritas::IntegrationBase + :members: Tracking manager ----------------- +^^^^^^^^^^^^^^^^ Using Celeritas to "offload" all electrons, photons, and gammas from Geant4 can be done using the new-ish Geant4 interface :cpp:class:`G4VTrackingManager` @@ -31,7 +34,7 @@ See :ref:`example_template` for a template of adding to a user application. :members: Fast simulation ---------------- +^^^^^^^^^^^^^^^ It is currently *not* recommended to offload tracks on a per-region basis, since tracks exiting that region remain in Celeritas and on GPU. @@ -41,7 +44,7 @@ tracks exiting that region remain in Celeritas and on GPU. :members: User action ------------ +^^^^^^^^^^^ For compatibility with older versions of Geant4, you may use the following class to integrate Celeritas by manually intercepting tracks with a diff --git a/doc/implementation/geant4-interface/low-level.rst b/doc/implementation/geant4-interface/low-level.rst index 1ed44c380e..6106a78f8e 100644 --- a/doc/implementation/geant4-interface/low-level.rst +++ b/doc/implementation/geant4-interface/low-level.rst @@ -1,13 +1,13 @@ .. Copyright Celeritas contributors: see top-level COPYRIGHT file for details .. SPDX-License-Identifier: CC-BY-4.0 -Low-level Celeritas integration -=============================== +Low-level integration utilities +------------------------------- -This subsection contains details of importing Geant4 data into Celeritas. +This subsection contains details of integrating Geant4 with Celeritas. -Geant4 geometry utilities -^^^^^^^^^^^^^^^^^^^^^^^^^ +Geometry utilities +^^^^^^^^^^^^^^^^^^ These utility classes are used to set up the Geant4 global geometry state. @@ -16,11 +16,23 @@ These utility classes are used to set up the Geant4 global geometry state. .. doxygenfunction:: celeritas::save_gdml .. doxygenfunction:: celeritas::find_geant_volumes -Geant4 physics interfaces -^^^^^^^^^^^^^^^^^^^^^^^^^ +Physics interfaces +^^^^^^^^^^^^^^^^^^ This will be replaced by other utilities in conjunction with the :ref:`problem input `. .. doxygenclass:: celeritas::GeantImporter .. doxygenclass:: celeritas::GeantSetup + + +.. _g4_utils: + +Utility interfaces +^^^^^^^^^^^^^^^^^^ + +.. doxygenfunction:: celeritas::MakeMTSelfLogger +.. doxygenfunction:: celeritas::MakeMTWorldLogger + +.. doxygenclass:: celeritas::ScopedGeantLogger +.. doxygenclass:: celeritas::ScopedGeantExceptionHandler diff --git a/doc/implementation/geometry/orange/construction.rst b/doc/implementation/geometry/orange/construction.rst index 4e983f8f87..bc5fb181e3 100644 --- a/doc/implementation/geometry/orange/construction.rst +++ b/doc/implementation/geometry/orange/construction.rst @@ -94,7 +94,6 @@ be reused in multiple locations. :numref:`fig-orangeinp-types` summarizes these .. doxygenclass:: celeritas::orangeinp::PolySegments .. doxygenclass:: celeritas::orangeinp::PolyCone -.. doxygenclass:: celeritas::orangeinp::PolyPrism .. doxygenclass:: celeritas::orangeinp::RevolvedPolygon .. doxygenclass:: celeritas::orangeinp::StackedExtrudedPolygon diff --git a/doc/implementation/mucf-physics.rst b/doc/implementation/mucf-physics.rst index ec6d81f84e..75e13de3bf 100644 --- a/doc/implementation/mucf-physics.rst +++ b/doc/implementation/mucf-physics.rst @@ -72,8 +72,8 @@ sticking factor and the fusion cycle time are the main conditions that define how many fusion cycles a muon can undergo. The fusion cycle time depends on the d-t mixture, its temperature, and on the final spin of the molecule. Only muonic molecules where the total spin :math:`F = I_N \pm 1/2` is on, or has a -projection onto the total angular momentum J = 1 are reactive. The spin states -of the three possible muonic molecules are summarized in table +projection onto the total angular momentum :math:`J = 1` are reactive. The spin +states of the three possible muonic molecules are summarized in table :numref:`muon_spin_states`. @@ -108,9 +108,36 @@ enabling the ``mucf_physics`` option in Geant4 integration ------------------ -For integration interfaces, if ``mucf_physics`` option in -:cpp:class:`celeritas::ext::GeantPhysicsOptions` is enabled, the muon-catalyzed -fusion data is constructed when the ``G4MuonMinusAtomicCapture`` process is -registered. +For integration interfaces, enabling the ``mucf_physics`` option in +:cpp:class:`celeritas::ext::GeantPhysicsOptions` will check if the +``G4MuonMinusAtomicCapture`` process is registered in the Geant4's Physics List. +If the process is present, the :cpp:class:`celeritas::inp::MucfPhysics` will be +populated, and the :cpp:class:`celeritas::MucfProcess` will be initialized. -.. todo:: Add process/model/executor details +Code implementation +=================== + +The :cpp:class:`celeritas::MucfProcess` process has only the +:cpp:class:`celeritas::DTMixMucfModel` attached to it, responsible for +deuterium-tritium mixtures. It can simulate materials from near absolute zero to +1500 kelvin. It is an *at rest* model that encompasses the full cycle---atom +formation, molecule formation, and fusion. + +.. doxygenclass:: celeritas::MucfProcess +.. doxygenclass:: celeritas::DTMixMucfModel + +Most of the data is material-dependent, being calculated and cached during model +construction. All of the cached quantities are calculated and added to +host/device data via :cpp:class:`celeritas::detail::MucfMaterialInserter`. + +.. doxygenclass:: celeritas::detail::MucfMaterialInserter + +The main cycle is managed by the model's +:cpp:class:`celeritas::DTMixMucfExecutor`, with the Interactors reserved for +sampling final states of the outgoing secondaries. + +.. note:: Only reactive channels are implemented. + +.. doxygenclass:: celeritas::DDMucfInteractor +.. doxygenclass:: celeritas::DTMucfInteractor +.. doxygenclass:: celeritas::TTMucfInteractor diff --git a/doc/usage/execution/environment.rst b/doc/usage/execution/environment.rst index 1e99519ed6..3141a40d90 100644 --- a/doc/usage/execution/environment.rst +++ b/doc/usage/execution/environment.rst @@ -93,15 +93,25 @@ Celeritas or its apps: thread access the ``celeritas::environment()`` struct (see :ref:`api_system`), and call ``insert`` for the desired key/value pairs. +.. doxygenfunction:: celeritas::use_color + .. _logging: Logging ------- -The Celeritas library writes informational messages to ``stderr``. The given -levels can be used with the ``CELER_LOG`` and ``CELER_LOG_LOCAL`` environment -variables to suppress or increase the output. The default is to print -diagnostic messages and higher. +The Celeritas library has an internal logger that, by default, writes +informational messages to ``stderr`` through the ``std::clog`` (buffered +standard error) handle. +The "world" logger generally prints messages during setup or at circumstances +shared by all threads/tasks/processes, and the "self" logger is for messages +related to a particular thread. By default the self logger is mutexed for +thread safety, and log messages are assembled internally before printing to +reduce I/O contention. + +The levels below can be used with the ``CELER_LOG`` and ``CELER_LOG_LOCAL`` +environment variables to suppress or increase the output. +The default is to print diagnostic messages and higher. .. table:: Logging levels in increasing severity. diff --git a/doc/usage/installation.rst b/doc/usage/installation.rst index c0c988a1ba..8c614c5fde 100644 --- a/doc/usage/installation.rst +++ b/doc/usage/installation.rst @@ -54,6 +54,7 @@ internet if required but not available on the user's system. CLI11_, Runtime*, "Command line parsing" CUDA_, Runtime, "GPU computation" + DD4hep_, Runtime, "HEP detector framework integration" Geant4_, Runtime, "Physics data and user integration" G4EMLOW_, Runtime, "EM physics model data" G4VG_, Runtime, "Geant4-to-VecGeom translation" @@ -83,6 +84,7 @@ internet if required but not available on the user's system. .. _CLI11: https://cliutils.github.io/CLI11/book/ .. _CMake: https://cmake.org .. _CUDA: https://developer.nvidia.com/cuda-toolkit +.. _DD4hep: https://dd4hep.web.cern.ch .. _Doxygen: https://www.doxygen.nl .. _G4VG: https://github.com/celeritas-project/g4vg .. _G4EMLOW: https://geant4.web.cern.ch/support/download @@ -233,7 +235,7 @@ It includes environment files for quickly getting started on systems including N $ cd celeritas $ ./scripts/build.sh base -You can, of course, manually build as a +You can, of course, build manually: .. code-block:: console @@ -245,7 +247,7 @@ You can, of course, manually build as a .. _zip file: https://github.com/celeritas-project/celeritas/archive/refs/heads/develop.zip .. _Perlmutter: https://docs.nersc.gov/systems/perlmutter/ -.. _ExCL: https://docs.olcf.ornl.gov/systems/excl_user_guide.html +.. _ExCL: https://docs.excl.ornl.gov .. _Frontier: https://docs.olcf.ornl.gov/systems/frontier_user_guide.html .. _JLSE: https://www.jlse.anl.gov/ diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000000..cb58885a57 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,2 @@ +*/build/ +*/install/ diff --git a/example/ddceler/.gitignore b/example/ddceler/.gitignore new file mode 100644 index 0000000000..ea1472ec1f --- /dev/null +++ b/example/ddceler/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/example/ddceler/input/Preshower.xml b/example/ddceler/input/Preshower.xml new file mode 100644 index 0000000000..3ce753067e --- /dev/null +++ b/example/ddceler/input/Preshower.xml @@ -0,0 +1,89 @@ + + + + + + + + Simple two-layer preshower detector + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + system:8,x:24:-12,y:-12 + + + + + + + + + + diff --git a/example/ddceler/input/steeringFile.py b/example/ddceler/input/steeringFile.py new file mode 100644 index 0000000000..4b497c7cac --- /dev/null +++ b/example/ddceler/input/steeringFile.py @@ -0,0 +1,61 @@ +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""DDG4 steering file for Preshower benchmark with Celeritas integration.""" + +from DDSim.DD4hepSimulation import DD4hepSimulation + +runner = DD4hepSimulation() + +# Action configuration +runner.action.run = "CelerRun" +runner.action.tracker = "Geant4TrackerAction" +runner.action.trackerSDTypes = ["tracker"] +runner.action.calo = "Geant4CalorimeterAction" +runner.action.calorimeterSDTypes = ["calorimeter"] + +# Output configuration +runner.outputConfig.forceDD4HEP = True + +# Number of events +runner.numberOfEvents = 100 + +# Particle gun configuration +runner.enableGun = True +runner.gun.particle = "e-" +runner.gun.energy = "5*GeV" +runner.gun.distribution = "uniform" +runner.gun.etaMin = 5.0 +runner.gun.etaMax = 5.0 + +# Field tracking configuration - defined once, used by both +# DD4hep/Geant4 and Celeritas +runner.field.delta_chord = 0.025 # mm +runner.field.delta_intersection = 1e-2 # mm +runner.field.delta_one_step = 0.001 # mm +runner.field.eps_min = 5e-5 # mm +runner.field.eps_max = 0.001 # mm +runner.field.min_chord_step = 1e-6 # mm + + +def setup_physics(kernel): + """Configure physics list with Celeritas integration.""" + from DDG4 import Geant4, PhysicsList + + phys = Geant4(kernel).setupPhysics("QGSP_BERT") + celer_phys = PhysicsList(kernel, str("CelerPhysics")) + # MaxNumTracks: max number of tracks in flight + # InitCapacity: initial capacity for state data allocation + # CPU defaults: MaxNumTracks=2048, InitCapacity=245760 + # GPU recommendation: MaxNumTracks=262144, InitCapacity=8388608 + celer_phys.MaxNumTracks = 2048 + celer_phys.InitCapacity = 245760 + # Celeritas does not support single scattering + celer_phys.IgnoreProcesses = ["CoulombScat"] + phys.adopt(celer_phys) + phys.dump() + return None + + +runner.physics.setupUserPhysics(setup_physics) + +runner.part.userParticleHandler = "" diff --git a/example/ddceler/run-preshower.sh b/example/ddceler/run-preshower.sh new file mode 100755 index 0000000000..846edf6387 --- /dev/null +++ b/example/ddceler/run-preshower.sh @@ -0,0 +1,111 @@ +#!/bin/sh -e +#-------------------------------- -*- sh -*- ---------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#-----------------------------------------------------------------------------# +# Run Preshower benchmark with or without Celeritas offload +# +# Usage: +# ./run-preshower.sh [celeritas|geant4] [additional ddsim options] +# +# Examples: +# ./run-preshower.sh celeritas --numberOfEvents 1000 +# ./run-preshower.sh geant4 --random.seed=42 +#-----------------------------------------------------------------------------# + +log() { + printf "%s\n" "$1" >&2 +} + +# Resolve symlink chain to actual file/executable +resolve_symlinks() { + _path="$1" + while [ -L "$_path" ]; do + printf "Following symlink: %s -> " "$_path" >&2 + _path=$(readlink -f "$_path") + log "$_path" + done + printf "%s\n" "$_path" +} + +# Parse mode argument +MODE=$1 +if [ "$MODE" != "celeritas" ] && [ "$MODE" != "geant4" ]; then + log "Usage: $0 [celeritas|geant4] [additional ddsim options]" + log "" + log " celeritas - Run with Celeritas offload (default)" + log " geant4 - Run with Geant4 only" + exit 1 +fi +shift # Remove first argument + +# Disable Celeritas if running in geant4 mode +if [ "$MODE" = "geant4" ]; then + export CELER_DISABLE=1 +fi + +EXAMPLE_DIR=$(cd "$(dirname $0)" && pwd) + +if [ -z "${Celeritas_ROOT}" ]; then + Celeritas_ROOT=$(cd "$EXAMPLE_DIR"/../.. && pwd)/install + log "warning: Celeritas_ROOT is undefined: using ${Celeritas_ROOT}" +fi + +# Resolve ddsim +DDSIM=$(command -v "ddsim" 2>/dev/null || printf "") +if [ -z "$DDSIM" ]; then + log "error: ddsim: command not found" + exit 1 +fi +DDSIM=$(resolve_symlinks "$DDSIM" true) + +if [ -z "$DD4hepINSTALL" ]; then + log "error: DD4hepINSTALL environment variable is not set" + log " You must load DD4HEP's environment (including its PYTHONPATH and ROOT's)" + exit 1 +fi + +CELER_LIB_DIR=$(ls -1 -d "$Celeritas_ROOT"/lib 2>/dev/null | head -1) +if [ -z "$CELER_LIB_DIR" ]; then + log "error: celeritas installation not found inside $Celeritas_ROOT" + exit 1 +fi + +# Plugin must be available in the runtime library path for DD4hep to find it +if [ "$(uname -s)" = "Darwin" ]; then + _ld_prefix=DY + export DYLD_LIBRARY_PATH=${CELER_LIB_DIR}:${DYLD_LIBRARY_PATH} + if [ -z "$DD4HEP_LIBRARY_PATH" ]; then + # Needed by dd4hep load on macos + log "error: DD4HEP_LIBRARY_PATH environment variable is not set" + exit 1 + fi +else + _ld_prefix= + export LD_LIBRARY_PATH=${CELER_LIB_DIR}:${LD_LIBRARY_PATH} +fi +log "info: Prepended ${CELER_LIB_DIR} to ${_ld_prefix}LD_LIBRARY_PATH" + +# Find python interpreter (prefer python3) +PYTHON=$(command -v "python3" 2>/dev/null || command -v "python" 2>/dev/null || printf "") +if [ -z "$PYTHON" ]; then + log "error: failed to find python3 or python" + exit 1 +fi +PYTHON=$(resolve_symlinks "$PYTHON" true) + +# Set output file name based on run mode +log "Running with ${MODE} physics" +output_file="preshower.root" +log "info: Output will be written to ${output_file}" + +# Create mode-specific subdirectory and change to it +mkdir -p "${EXAMPLE_DIR}/output/${MODE}" +cd "${EXAMPLE_DIR}/output/${MODE}" + +set -x +exec "$PYTHON" "$DDSIM" \ + --compactFile="${EXAMPLE_DIR}/input/Preshower.xml" \ + --steering="${EXAMPLE_DIR}/input/steeringFile.py" \ + --outputFile="${output_file}" \ + "$@" diff --git a/example/geant4/.gitignore b/example/geant4/.gitignore deleted file mode 100644 index a084120796..0000000000 --- a/example/geant4/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/install diff --git a/example/minimal/.gitignore b/example/minimal/.gitignore deleted file mode 100644 index a084120796..0000000000 --- a/example/minimal/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build -/install diff --git a/example/offload-template/.gitignore b/example/offload-template/.gitignore deleted file mode 100644 index 39e512ccc0..0000000000 --- a/example/offload-template/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/build -/build* -/install diff --git a/example/offload-template/src/EventAction.cc b/example/offload-template/src/EventAction.cc index 090d135388..5ac4b33edb 100644 --- a/example/offload-template/src/EventAction.cc +++ b/example/offload-template/src/EventAction.cc @@ -34,7 +34,7 @@ EventAction::SPStepDiagnostic& step_diagnostic() //---------------------------------------------------------------------------// /*! - * From MakeCelerOptions during setup on master, set the step diagnostic. + * From MakeCelerOptions during initialization, set the step diagnostic. * * This should only be called once from the main thread during BeginRun via * MakeCelerOptions. diff --git a/example/offload-template/src/EventAction.hh b/example/offload-template/src/EventAction.hh index c6e5a031fa..cc8f0a31ec 100644 --- a/example/offload-template/src/EventAction.hh +++ b/example/offload-template/src/EventAction.hh @@ -31,7 +31,8 @@ class EventAction final : public G4UserEventAction //!@} public: - // From MakeCelerOptions during setup on master, set the step diagnostic + // From MakeCelerOptions during setup on main thread, set the step + // diagnostic static void SetStepDiagnostic(SPStepDiagnostic&&); // During problem destruction, clear the diagnostic static void ClearStepDiagnostic(); diff --git a/example/offload-template/src/RunAction.cc b/example/offload-template/src/RunAction.cc index f7b059a377..4521699af2 100644 --- a/example/offload-template/src/RunAction.cc +++ b/example/offload-template/src/RunAction.cc @@ -20,7 +20,7 @@ RunAction::RunAction() : G4UserRunAction() {} //---------------------------------------------------------------------------// /*! - * Initialize master and worker threads in Celeritas. + * Initialize main and worker threads in Celeritas. */ void RunAction::BeginOfRunAction(G4Run const* run) { diff --git a/example/offload-template/src/StepDiagnostic.cc b/example/offload-template/src/StepDiagnostic.cc index d266fe49a3..838ad473f7 100644 --- a/example/offload-template/src/StepDiagnostic.cc +++ b/example/offload-template/src/StepDiagnostic.cc @@ -149,7 +149,7 @@ void StepDiagnostic::step(CoreParams const& params, CoreStateHost& state) const auto& step_state = state.aux_data(aux_id_); // Accumulate counters - this->accum_counters(state.counters(), step_state.host_data); + this->accum_counters(state.sync_get_counters(), step_state.host_data); // Create a functor that gathers data from a single track slot auto execute = make_active_track_executor( diff --git a/example/offload-template/src/StepDiagnostic.cu b/example/offload-template/src/StepDiagnostic.cu index f8dccefdbb..a8e30d1768 100644 --- a/example/offload-template/src/StepDiagnostic.cu +++ b/example/offload-template/src/StepDiagnostic.cu @@ -28,7 +28,7 @@ void StepDiagnostic::step(CoreParams const& params, CoreStateDevice& state) cons auto& step_state = state.aux_data(aux_id_); // Accumulate counters - this->accum_counters(state.counters(), step_state.host_data); + this->accum_counters(state.sync_get_counters(), step_state.host_data); // Create a functor that gathers data from a single track slot auto execute = make_active_track_executor( diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 0ffbf4e1c8..60120760a3 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -3,27 +3,36 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) #-----------------------------------------------------------------------------# -# Print extra information about downloading: hash failures, for example, are -# silent otherwise -set(CMAKE_MESSAGE_LOG_LEVEL VERBOSE) - # Download the package, save version, add to a list set(_celer_builtin_packages) macro(celer_fetch_external name version url sha) - # Save version and annotated string for corecel/Config.cc include - set(${name}_VERSION ${version} PARENT_SCOPE) - set(${name}_VERSION_STRING "${version}+builtin" PARENT_SCOPE) - - string(REGEX REPLACE "@VERSION@" "${version}" _url "${url}") - FetchContent_Declare( - "${name}" - URL "${_url}" - URL_HASH SHA256=${sha} - DOWNLOAD_NO_PROGRESS TRUE - ) - list(APPEND _celer_builtin_packages "${name}") + if(CELERITAS_BUILTIN_${name} AND NOT CELERITAS_USE_${name}) + message(WARNING "Ignoring CELERITAS_BUILTIN_${name} " + "since CELERITAS_USE_${name} is disabled") + else() + # Save version and annotated string for corecel/Config.cc include + # Note that since this a macro, PARENT_SCOPE will be the top-level + # celeritas/CMakeLists.txt + set(${name}_VERSION ${version} PARENT_SCOPE) + set(${name}_VERSION_STRING "${version}+builtin" PARENT_SCOPE) + + string(REGEX REPLACE "@VERSION@" "${version}" _url "${url}") + FetchContent_Declare( + "${name}" + URL "${_url}" + URL_HASH SHA256=${sha} + DOWNLOAD_NO_PROGRESS TRUE + ) + list(APPEND _celer_builtin_packages "${name}") + endif() endmacro() +#-----------------------------------------------------------------------------# + +# Print extra information about downloading: hash failures, for example, are +# silent otherwise +set(CMAKE_MESSAGE_LOG_LEVEL VERBOSE) + #-----------------------------------------------------------------------------# # CLI11 #-----------------------------------------------------------------------------# @@ -51,6 +60,7 @@ if(CELERITAS_BUILTIN_covfie) "https://github.com/acts-project/covfie/archive/refs/tags/v@VERSION@.tar.gz" 72da1147c44731caf9163f3931de78d7605a44f056f22a2f6ea024ad02a1ba71 ) + set(COVFIE_PLATFORM_CPU ON) set(COVFIE_PLATFORM_CUDA ${CELERITAS_USE_CUDA}) set(COVFIE_PLATFORM_HIP ${CELERITAS_USE_HIP}) diff --git a/scripts/build.sh b/scripts/build.sh index af430e639f..74238c5e69 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -98,11 +98,15 @@ ln_presets() { # Return early if it exists if [ -L "${dst}" ]; then - src="$(readlink "${dst}" 2>/dev/null || printf "")" - log debug "CMake preset already exists: ${dst} -> ${src}" - return + actual="$(readlink "${dst}" 2>/dev/null || printf "")" + if [ "${src}" = "${actual}" ]; then + log debug "CMake preset already exists: ${dst} -> ${actual}" + return + else + log warning "${dst} points to ${actual}, not ${src}: overwriting" + fi elif [ -e "${dst}" ]; then - log debug "CMake preset already exists: ${dst}" + log warning "${PWD}/${dst} already exists but is not a symlink (should link to ${src})" return fi @@ -113,7 +117,7 @@ ln_presets() { git add "${src}" || log error "Could not stage presets" fi log info "Linking presets to ${dst}" - ln -s "${src}" "${dst}" + ln -f -s "${src}" "${dst}" } # Check if ccache is full and warn user @@ -273,7 +277,7 @@ setup_ccache # Check arguments and give presets if missing if [ $# -eq 0 ] ; then printf '%s\n' "Usage: $0 PRESET [config_args...]" >&2 - if [ -n "${CMAKE}" ]; then + if [ -z "${CMAKE}" ]; then log error "cmake unavailable: cannot call --list-presets" exit 1 fi @@ -288,16 +292,18 @@ CMAKE_PRESET=$1 shift # Configure, build, and test -log info "Configuring with verbosity" +log info "Configuring with --preset=${CMAKE_PRESET} --log-level=VERBOSE $@" cmake --preset="${CMAKE_PRESET}" --log-level=VERBOSE "$@" -log info "Building" +log info "Building with --preset=${CMAKE_PRESET}" if cmake --build --preset="${CMAKE_PRESET}"; then - log info "Testing" + log info "Testing with --preset=${CMAKE_PRESET} --timeout 15" if ctest --preset="${CMAKE_PRESET}" --timeout 15; then log info "Celeritas was successfully built and tested for development!" else log warning "Celeritas built but some tests failed" log info "Ask the Celeritas team whether the failures indicate an actual error" + log info "Provide the system configuration:" + cmake --build-target get-config --preset=${CMAKE_PRESET} fi install_precommit_if_git diff --git a/scripts/ci/gcovr.cfg b/scripts/ci/gcovr.cfg index 38cc24cc06..0522d49197 100644 --- a/scripts/ci/gcovr.cfg +++ b/scripts/ci/gcovr.cfg @@ -16,3 +16,4 @@ gcov-ignore-parse-errors = negative_hits.warn gcov-ignore-parse-errors = suspicious_hits.warn exclude-unreachable-branches = yes +exclude-lines-by-pattern = ^\s*CELER_((ASSERT_)?UNREACHABLE|NOT_CONFIGURED|(DEVICE_API|MPI)_CALL|EXPECT|ASSERT|ENSURE|DEBUG_FAIL).* diff --git a/scripts/ci/host-system-info.cmake b/scripts/ci/host-system-info.cmake deleted file mode 100644 index 62f9d26c14..0000000000 --- a/scripts/ci/host-system-info.cmake +++ /dev/null @@ -1,20 +0,0 @@ -#------------------------------- -*- cmake -*- -------------------------------# -# Copyright Celeritas contributors: see top-level COPYRIGHT file for details -# SPDX-License-Identifier: (Apache-2.0 OR MIT) -#[=======================================================================[.rst: - -Run with `cmake -P 2>&1`, optionally passing -DKEY= - -See: -https://cmake.org/cmake/help/latest/command/cmake_host_system_information.html -for possible values. - -#]=======================================================================] - -if(NOT KEY) - set(KEY NUMBER_OF_LOGICAL_CORES) -endif() -cmake_host_system_information(RESULT result QUERY ${KEY}) -message(${result}) - -#-----------------------------------------------------------------------------# diff --git a/scripts/ci/run-ci.sh b/scripts/ci/run-ci.sh deleted file mode 100755 index 8421742cf9..0000000000 --- a/scripts/ci/run-ci.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/sh -e - -OS=$1 -CMAKE_PRESET=$2 - -set -x -test -n "${OS}" -test -n "${CMAKE_PRESET}" - -if [ -z "${CELER_SOURCE_DIR}" ]; then - CELER_SOURCE_DIR="$(dirname $0)"/../.. -fi -cd "${CELER_SOURCE_DIR}" -CELER_SOURCE_DIR=$(pwd) - -# Source environment script if necessary -_ENV_SCRIPT="scripts/env/ci-${OS}.sh" -if [ -f "${_ENV_SCRIPT}" ]; then - . "${_ENV_SCRIPT}" -fi - -# Fetch tags for version provenance -git fetch -f --tags - -# Clean older builds from jenkins *BEFORE* setting up presets -git clean -fxd -# Stale tmp files? -rm -rf /tmp/ompi.* - -# Link preset script -ln -fs scripts/cmake-presets/ci-${OS}.json CMakeUserPresets.json -# Configure -cmake --preset=${CMAKE_PRESET} -# Build and (optionally) install -cmake --build --preset=${CMAKE_PRESET} - -# Require regression-like tests to be enabled and pass -export CELER_TEST_STRICT=1 - -# Run tests -cd build -ctest -T Test \ - -j16 --timeout 180 \ - --no-compress-output --output-on-failure \ - --test-output-size-passed=65536 --test-output-size-failed=1048576 \ -# List XML files generated: jenkins will upload these later -find Testing -name '*.xml' - -# Install and test that the executables are there -cmake --install . -cd .. -test -x "${CELER_SOURCE_DIR}/install/bin/celer-sim" -test -x "${CELER_SOURCE_DIR}/install/bin/celer-g4" -"${CELER_SOURCE_DIR}/install/bin/celer-sim" --version - - -# Test examples against installed celeritas -export CMAKE_PRESET -export CELER_SOURCE_DIR -case "${CMAKE_PRESET}" in - *-vecgeom*) - # VecGeom is in use: ubuntu flags are too strict for it - export LDFLAGS=-Wl,--no-as-needed - ;; -esac - -# Test examples against installed celeritas -exec ${CELER_SOURCE_DIR}/scripts/ci/test-examples.sh diff --git a/scripts/cmake-presets/ci-rocky-cuda.json b/scripts/cmake-presets/ci-rocky-cuda.json index e265f53179..3b9c20d567 100644 --- a/scripts/cmake-presets/ci-rocky-cuda.json +++ b/scripts/cmake-presets/ci-rocky-cuda.json @@ -74,11 +74,12 @@ } }, { - "name": "reldeb-vecgeom-codecov", - "description": "Build with RelWithDebInfo, VecGeom and code coverage (no CUDA)", - "inherits": [".reldeb", ".vecgeom", ".gcov", "base"], + "name": "ndebug-vecgeom-codecov", + "description": "Build with code coverage with vecgeom (no CUDA, no assertions)", + "inherits": [".gcov", ".ndebug", ".vecgeom", "base"], "cacheVariables": { - "CELERITAS_USE_CUDA": {"type": "BOOL", "value": "OFF"} + "CELERITAS_USE_CUDA": {"type": "BOOL", "value": "OFF"}, + "CMAKE_CXX_FLAGS": null } } ], diff --git a/scripts/cmake-presets/ci-ubuntu-github.json b/scripts/cmake-presets/ci-ubuntu-github.json index 2299dd876b..4a831df8da 100644 --- a/scripts/cmake-presets/ci-ubuntu-github.json +++ b/scripts/cmake-presets/ci-ubuntu-github.json @@ -11,11 +11,19 @@ "cacheVariables": { "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_covfie": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_CLI11": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_G4VG": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_GTest": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_nlohmann_json": {"type": "BOOL", "value": "OFF"}, "CELERITAS_TEST_VERBOSE":{"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_CUDA": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_HIP": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_LarSoft": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_Perfetto":{"type": "BOOL", "value": "OFF"}, @@ -23,7 +31,6 @@ "CELERITAS_USE_Python": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"}, "CELERITAS_HOSTNAME": "ubuntu-github", "CELERITAS_TEST_XML": "$env{GITHUB_WORKSPACE}/test-output/google/", "CMAKE_CXX_COMPILER_LAUNCHER": "ccache", @@ -34,53 +41,61 @@ } }, { - "name": "spack", + "name": "tool-spack", "inherits": ["base", ".spack-base"], "displayName": "CONFIG: use full spack toolchain", "cacheVariables": { "CELERITAS_USE_covfie": {"type": "BOOL", "value": "ON"}, - "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "ON"}, - "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"}, - "CELERITAS_USE_ROOT": null, - "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"} + "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "ON"}, + "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"}, + "CELERITAS_USE_ROOT": null } }, { - "name": ".system", + "name": "tool-system", "inherits": ["base"], - "displayName": "CONFIG: build with apt-get dependencies", + "displayName": "TOOLCHAIN: build with apt-get dependencies", "cacheVariables": { + "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILTIN_CLI11": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILTIN_G4VG": {"type": "BOOL", "value": "OFF"}, "CELERITAS_BUILTIN_GTest": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILTIN_covfie": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILTIN_nlohmann_json": {"type": "BOOL", "value": "OFF"} + "CELERITAS_BUILTIN_covfie": {"type": "BOOL", "value": "ON"} } }, { "name": ".vecgeom", + "hidden": true, "description": "CONFIG: options to enable VecGeom on Ubuntu", "cacheVariables": { "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "ON"} } }, + { + "name": ".nogtest", + "hidden": true, + "description": "CONFIG: disable googletest", + "cacheVariables": { + "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILTIN_GTest": {"type": "BOOL", "value": "OFF"} + } + }, { "name": "doc", - "inherits": [".system"], + "inherits": [".nogtest", "tool-system"], "displayName": "FINAL: build only documentation", "cacheVariables": { "BUILD_TESTING": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_BUILD_APPS": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "ON"}, - "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILD_APPS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "ON"}, + "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, "CELERITAS_DOXYGEN_BUILD_TESTS": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_PNG": {"type": "BOOL", "value": "OFF"} + "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_PNG": {"type": "BOOL", "value": "OFF"} } }, { "name": "type-reldeb", + "hidden": true, "description": "TYPE: debug assertions but no debug symbols", "cacheVariables": { "BUILD_SHARED_LIBS":{"type": "BOOL", "value": "ON"}, @@ -90,17 +105,16 @@ }, { "name": "type-ndebug", - "description": "TYPE: release build for testing *only* apps", + "hidden": true, + "description": "TYPE: release build", "cacheVariables": { - "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, - "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, - "CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Release"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"} + "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, + "CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Release"} } }, { "name": "fast", - "inherits": ["type-reldeb", ".system"], + "inherits": ["type-reldeb", "tool-system"], "displayName": "FINAL: fast build using apt-get", "cacheVariables": { "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"} @@ -108,36 +122,28 @@ }, { "name": "ultralite", - "inherits": ["type-ndebug", ".system"], - "displayName": "FINAL: ultralite build with only app tests", - "cacheVariables": { - "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"} - } + "inherits": [".nogtest", "type-ndebug", "tool-system"], + "displayName": "FINAL: ultralite build with only app tests" }, { "name": "ultralite-codecov", - "inherits": ["type-ndebug", ".system", ".gcov"], + "inherits": [".nogtest", ".gcov", "type-ndebug", "tool-system"], "displayName": "FINAL: ultralite code coverage with only app tests", "cacheVariables": { - "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_covfie": {"type": "BOOL", "value": "OFF"} + "CMAKE_CXX_FLAGS": null } }, { "name": "reldeb-orange", "description": "FINAL: ORANGE", - "inherits": ["type-reldeb", "spack"] + "inherits": ["type-reldeb", "tool-spack"] }, { "name": "reldeb-vecgeom-tidy", "description": "FINAL: VecGeom and clang-tidy checks", - "inherits": ["type-reldeb", ".vecgeom", "spack"], + "inherits": [".nogtest", ".vecgeom", "type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_DEBUG": {"type": "BOOL", "value": "ON"}, - "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"}, "CMAKE_CXX_SCAN_FOR_MODULES": {"type": "BOOL", "value": "OFF"}, @@ -148,7 +154,7 @@ { "name": "reldeb-vecgeom-clhep", "description": "FINAL: VecGeom and CLHEP units", - "inherits": ["type-reldeb", ".vecgeom", "spack"], + "inherits": [".vecgeom", "type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_UNITS": "CLHEP" } @@ -156,17 +162,20 @@ { "name": "reldeb-vecgeom", "description": "FINAL: release, assertions, VecGeom", - "inherits": ["type-reldeb", ".vecgeom", "spack"] + "inherits": [".vecgeom", "type-reldeb", "tool-spack"] }, { - "name": "reldeb-vecgeom-codecov", + "name": "ndebug-vecgeom-codecov", "description": "FINAL: VecGeom and code coverage", - "inherits": ["type-reldeb", ".vecgeom", ".gcov", "spack"] + "inherits": [".vecgeom", ".gcov", "type-reldeb", "tool-spack"], + "cacheVariables": { + "CMAKE_CXX_FLAGS": null + } }, { "name": "reldeb-vgsurf", "description": "FINAL: release, assertions, VecGeom 2+surf", - "inherits": ["type-reldeb", ".vecgeom", "spack"], + "inherits": [".vecgeom", "type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "ON"} } @@ -174,7 +183,7 @@ { "name": "reldeb-vecgeom2", "description": "FINAL: release, assertions, VecGeom 2~surf", - "inherits": ["type-reldeb", ".vecgeom", "spack"], + "inherits": [".vecgeom", "type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "OFF"} } @@ -182,12 +191,12 @@ { "name": "ndebug-vecgeom", "description": "FINAL: vecgeom app only", - "inherits": ["type-ndebug", ".vecgeom", "spack"] + "inherits": [".vecgeom", ".nogtest", "type-ndebug", "tool-spack"] }, { "name": "ndebug-orange-static", "description": "FINAL: static libraries and perfetto, no tests", - "inherits": ["type-ndebug", "spack"], + "inherits": ["type-ndebug", "tool-spack"], "cacheVariables": { "BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_Perfetto":{"type": "BOOL", "value": "ON"}, @@ -198,7 +207,7 @@ { "name": "reldeb-geant4", "description": "FINAL: Geant4 geometry", - "inherits": ["type-reldeb", "spack"], + "inherits": ["type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_CORE_GEO": "Geant4", "CELERITAS_UNITS": "CLHEP" @@ -207,7 +216,7 @@ { "name": "reldeb-orange-minimal", "description": "FINAL: minimal reasonable dependencies", - "inherits": ["type-reldeb", "spack"], + "inherits": ["type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "OFF"}, @@ -217,9 +226,9 @@ } }, { - "name": "reldebinfo-orange-asanlite", + "name": "reldeb-orange-asanlite", "description": "FINAL: address sanitizer flags and debug symbols", - "inherits": ["spack"], + "inherits": ["tool-spack"], "cacheVariables": { "BUILD_SHARED_LIBS":{"type": "BOOL", "value": "ON"}, "CELERITAS_DEBUG": {"type": "BOOL", "value": "OFF"}, @@ -233,7 +242,7 @@ { "name": "reldeb-orange-float", "description": "FINAL: single-precision arithmetic", - "inherits": ["type-reldeb", "spack"], + "inherits": ["type-reldeb", "tool-spack"], "cacheVariables": { "CELERITAS_REAL_TYPE": "float" } @@ -282,7 +291,7 @@ "name": ".app", "configurePreset": "base", "execution": { - "timeout": 30 + "timeout": 60 }, "filter": { "include": { @@ -295,12 +304,12 @@ }, { "name": "spack-unit", - "configurePreset": "spack", + "configurePreset": "tool-spack", "inherits": [".unit", ".base"] }, { "name": "spack-app", - "configurePreset": "spack", + "configurePreset": "tool-spack", "inherits": [".app", ".base"] }, { diff --git a/scripts/cmake-presets/executor.json b/scripts/cmake-presets/executor.json index 557b789c18..e0a6f4d541 100644 --- a/scripts/cmake-presets/executor.json +++ b/scripts/cmake-presets/executor.json @@ -21,12 +21,13 @@ "BUILD_TESTING": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_covfie": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_CUDA": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_HepMC3": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_HIP": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_Geant4": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "ON"}, + "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"}, "CELERITAS_BUILD_TESTS": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILTIN_CLI11": {"type": "BOOL", "value": "OFF"}, diff --git a/scripts/cmake-presets/hudson.json b/scripts/cmake-presets/hudson.json index 0f2348961a..dee0188902 100644 --- a/scripts/cmake-presets/hudson.json +++ b/scripts/cmake-presets/hudson.json @@ -11,6 +11,7 @@ "cacheVariables": { "BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"}, "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "ON"}, "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, "CMAKE_CXX_FLAGS": "-Wall -Wextra -pedantic -Werror -Wno-stringop-overread", @@ -75,6 +76,26 @@ "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "OFF"} } }, + { + "name": "release-vecgeom2-surface", + "displayName": "Build with full optimizations (VecGeom 2 with surface geo)", + "inherits": ["release-vecgeom2-solid"], + "cacheVariables": { + "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "ON"}, + "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "ON"} + } + }, + { + "name": "release-vecgeom2-solid", + "displayName": "Build with full optimizations (VecGeom 2 with solid geo)", + "inherits": ["release"], + "environment": { + "CMAKE_PREFIX_PATH": "$env{SPACK_ROOT}/var/spack/environments/celeritas-vg2/.spack-env/view" + }, + "cacheVariables": { + "CELERITAS_VecGeom_SURFACE": {"type": "BOOL", "value": "OFF"} + } + }, { "name": "release-vecgeom2-surface", "displayName": "Build with full optimizations (VecGeom 2 with surface geo)", diff --git a/scripts/cmake-presets/milan0.json b/scripts/cmake-presets/milan0.json index a83a0b7d6b..f0afa467db 100644 --- a/scripts/cmake-presets/milan0.json +++ b/scripts/cmake-presets/milan0.json @@ -13,7 +13,9 @@ "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, - "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_LArSoft": {"type": "BOOL", "value": "OFF"}, "CMAKE_CXX_FLAGS": "-Wall -Wextra -pedantic -Werror -Wno-stringop-overread", "CMAKE_CXX_STANDARD": "20", "CMAKE_CXX_COMPILER": "/usr/bin/c++", diff --git a/scripts/cmake-presets/milan2.json b/scripts/cmake-presets/milan2.json new file mode 100644 index 0000000000..14a52e64af --- /dev/null +++ b/scripts/cmake-presets/milan2.json @@ -0,0 +1,97 @@ +{ + "version": 3, + "cmakeMinimumRequired": {"major": 3, "minor": 21, "patch": 0}, + "configurePresets": [ + { + "name": ".base", + "hidden": true, + "inherits": [".cuda", "full", ".spack-base"], + "binaryDir": "/scratch/$env{USER}/build/celeritas-${presetName}", + "generator": "Ninja", + "cacheVariables": { + "BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"}, + "CELERITAS_BUILD_DOCS": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_OpenMP": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_MPI": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_ROOT": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_DD4hep": {"type": "BOOL", "value": "OFF"}, + "CELERITAS_USE_LArSoft": {"type": "BOOL", "value": "OFF"}, + "CMAKE_CXX_FLAGS": "-Wall -Wextra -pedantic -Werror -Wno-stringop-overread", + "CMAKE_CXX_STANDARD": "20", + "CMAKE_CXX_COMPILER": "/usr/bin/c++", + "CMAKE_CXX_EXTENSIONS": {"type": "BOOL", "value": "OFF"}, + "CMAKE_CUDA_FLAGS": "-Werror all-warnings -Wno-deprecated-gpu-targets", + "CMAKE_CUDA_HOST_COMPILER": "/usr/bin/c++", + "CMAKE_CUDA_ARCHITECTURES": "70", + "CMAKE_INSTALL_PREFIX": "/scratch/$env{USER}/install/celeritas-${presetName}", + "CMAKE_EXPORT_COMPILE_COMMANDS": {"type": "BOOL", "value": "ON"} + } + }, + { + "name": ".orange", + "hidden": true, + "cacheVariables": { + "CELERITAS_USE_VecGeom": {"type": "BOOL", "value": "OFF"}, + "CMAKE_EXE_LINKER_FLAGS": "-Wl,-z,defs", + "CMAKE_SHARED_LINKER_FLAGS": "-Wl,-z,defs" + } + }, + { + "name": "release", + "displayName": "Build with full optimizations (VecGeom)", + "inherits": [".base", ".ndebug"] + }, + { + "name": "reldeb", + "displayName": "Build with optimizations, but enable host debugging (VecGeom)", + "inherits": [".base", ".reldeb"], + "cacheVariables": { + "CELERITAS_DEVICE_DEBUG":{"type": "BOOL", "value": "OFF"} + } + }, + { + "name": "release-orange", + "displayName": "Build with full optimizations (ORANGE)", + "inherits": [".orange", "release"] + }, + { + "name": "reldeb-orange", + "displayName": "Build with optimizations, but enable host debugging (ORANGE)", + "inherits": [".orange", "reldeb"] + }, + { + "name": "debug-orange", + "displayName": "Build without optimization, enabling full host debugging (ORANGE)", + "inherits": [".orange", ".base", ".debug"], + "cacheVariables": { + "CELERITAS_DEVICE_DEBUG":{"type": "BOOL", "value": "OFF"} + } + } + ], + "buildPresets": [ + { + "name": ".base", + "configurePreset": ".base", + "jobs": 48, + "nativeToolOptions": ["-k0"] + }, + {"name": "release", "configurePreset": "release", "inherits": ".base"}, + {"name": "reldeb", "configurePreset": "reldeb", "inherits": ".base"}, + {"name": "release-orange", "configurePreset": "release-orange", "inherits": ".base"}, + {"name": "reldeb-orange", "configurePreset": "reldeb-orange", "inherits": ".base"}, + {"name": "debug-orange", "configurePreset": "debug-orange", "inherits": ".base"} + ], + "testPresets": [ + { + "name": ".base", + "configurePreset": ".base", + "output": {"outputOnFailure": true}, + "execution": {"noTestsAction": "error", "stopOnFailure": false, "jobs": 8} + }, + {"name": "release", "configurePreset": "release", "inherits": ".base"}, + {"name": "reldeb", "configurePreset": "reldeb", "inherits": ".base"}, + {"name": "release-orange", "configurePreset": "release-orange", "inherits": ".base"}, + {"name": "reldeb-orange", "configurePreset": "reldeb-orange", "inherits": ".base"}, + {"name": "debug-orange", "configurePreset": "debug-orange", "inherits": ".base"} + ] +} diff --git a/scripts/env/excl.sh b/scripts/env/excl.sh index 86e85ca332..ea18e61405 100755 --- a/scripts/env/excl.sh +++ b/scripts/env/excl.sh @@ -14,7 +14,7 @@ if ! command -v celerlog >/dev/null 2>&1; then } fi if test -z "${SYSTEM_NAME}"; then - SYSTEM_NAME=$(uname -s) + SYSTEM_NAME=$(hostname -s) celerlog debug "Set SYSTEM_NAME=${SYSTEM_NAME}" fi diff --git a/scripts/env/milan2.sh b/scripts/env/milan2.sh new file mode 100755 index 0000000000..33e91372c8 --- /dev/null +++ b/scripts/env/milan2.sh @@ -0,0 +1,19 @@ +#!/bin/sh -ex +#-------------------------------- -*- sh -*- ---------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#-----------------------------------------------------------------------------# + +if ! command -v load_system_env >/dev/null 2>&1; then + printf "error: expected load_system_env helper function via build.sh or shell\n" >&2 + return 1 +fi + +# Redundant with cmake prefix but useful if this is being used for other env +export CUDAARCHS=70 +# Set C++ compiler +export CXX=/usr/bin/c++ +export CC=/usr/bin/cc + +# Dispatch common loading to the 'excl' system +load_system_env excl || return $? diff --git a/scripts/env/scisoftbuild01.sh b/scripts/env/scisoftbuild01.sh index 227942b866..d1cd954bf1 100755 --- a/scripts/env/scisoftbuild01.sh +++ b/scripts/env/scisoftbuild01.sh @@ -48,12 +48,16 @@ if [ -n "${APPTAINER_CONTAINER}" ]; then export MRB_PROJECT_VERSION=v10_14_01 export MRB_QUALS=e26:prof celerlog info "Running in apptainer ${APPTAINER_CONTAINER}" - if [ -z "${UPS_DIR}" ]; then + if [ -n "${UPS_DIR}" ]; then + celerlog debug "Dune UPS already set up: ${UPS_DIR}" + else celerlog info "Setting up DUNE UPS" . /cvmfs/dune.opensciencegrid.org/products/dune/setup_dune.sh celerlog debug "Using UPS_OVERRIDE=${UPS_OVERRIDE}, MRB_PROJECT=${MRB_PROJECT}" fi - if [ -z "${SETUP_LARCORE}" ]; then + if [ -n "${SETUP_LARCORE}" ]; then + celerlog debug "LARCORE is already set up" + else # Set up larsoft build defaults with UPS celerlog info "Setting up ${MRB_PROJECT} ${MRB_PROJECT_VERSION} with qualifiers '${MRB_QUALS}'" setup ${MRB_PROJECT} ${MRB_PROJECT_VERSION} -q ${MRB_QUALS} || return $? @@ -76,19 +80,21 @@ done # Set up larsoft if running inside an apptainer if [ -n "${MRB_PROJECT}" ]; then LARSCRATCHDIR="${SCRATCHDIR}/${MRB_PROJECT}" - if ! [ -d "${LARSCRATCHDIR}" ]; then - celerlog info "Creating MRB dev area in ${LARSCRATCHDIR}..." + if [ -d "${LARSCRATCHDIR}" ]; then + celerlog debug "MRB dev area already exists at ${LARSCRATCHDIR}" + else + celerlog info "Creating MRB dev area in ${LARSCRATCHDIR}" mkdir -p "${LARSCRATCHDIR}" || return $? ( cd "${LARSCRATCHDIR}" mrb newDev - ) + ) || return 1 celerlog debug "MRB environment created" fi _setup_filename="${LARSCRATCHDIR}/localProducts_${MRB_PROJECT}_${MRB_PROJECT_VERSION}_${MRB_QUALS//:/_}/setup" if ! [ -f "${_setup_filename}" ]; then - celerlog warn "Expected setup file at ${_setup_filename}: MRB may not have been set up correctly" - _setup_filename=$(print %s"${LARSCRATCHDIR}/localProducts_${MRB_PROJECT}*/setup") + celerlog warning "Expected setup file at ${_setup_filename}: MRB may not have been set up correctly" + _setup_filename=$(printf %s "${LARSCRATCHDIR}/localProducts_${MRB_PROJECT}"*/setup) if [ -f "${_setup_filename}" ]; then celerlog info "Found setup file ${_setup_filename}" fi @@ -100,39 +106,50 @@ fi if [ -n "${MRB_SOURCE}" ]; then _pkg=larsim if ! [ -d "${MRB_SOURCE}/${_pkg}" ]; then - celerlog info "Installing ${_pkg}" - mrb g ${_pkg} + _tag=LARSOFT_SUITE_${MRB_PROJECT_VERSION} + celerlog info "Installing ${_pkg} @${_tag}" + mrb g -t ${_tag} ${_pkg} fi # Now that a package exists in MRB source, cmake and dependencies can load celerlog info "Activating MRB environment" - mrbsetenv - celerlog debug "MRB setup complete" + # Note that this may be a shell script + if ! command -v mrbsetenv >/dev/null 2>&1 ; then + celerlog warning "mrbsetenv is not defined: run manually in shell" + else + celerlog debug "MRB setup complete" + fi fi if [ -n "$CELER_SOURCE_DIR" ]; then _clangd="$CELER_SOURCE_DIR/.clangd" if [ ! -e "${_clangd}" ]; then # Create clangd compatible with the system and build config - _gcc_version=$(gcc -dumpversion | cut -d. -f1) - celerlog info "Creating clangd config using GCC ${_gcc_version}: ${_clangd}" - cat > "${_clangd}" << EOF + _cxx=$GCC_FQ_DIR/bin/g++ + if [ ! -x "${_cxx}" ]; then + celerlog info "GCC isn't loaded as expected at \$GCC_FQ_DIR/bin/g++ = ${_cxx}" + else + celerlog info "Creating clangd config using ${_cxx}: ${_clangd}" + + # Extract include paths from GCC + _gcc_includes=$("${_cxx}" -E -x c++ - -v < /dev/null 2>&1 | \ + sed -n '/^#include <...> search starts here:/,/^End of search list\./p' | \ + grep '^ ' | sed 's/^ *//' | \ + awk '{printf " -isystem,\n %s,\n", $0}' | \ + sed '$s/,$//') + + cat > "${_clangd}" << EOF CompileFlags: CompilationDatabase: ${SCRATCHDIR}/build/celeritas-reldeb-orange Add: [ - -isystem, - /usr/include/c++/${_gcc_version}, - -isystem, - /usr/local/include, - -isystem, - /usr/include, - -isystem, - /usr/include/x86_64-linux-gnu/c++/${_gcc_version}, +${_gcc_includes} ] EOF + unset _gcc_includes + fi + unset _clangd fi - unset _clangd fi export XDG_CACHE_HOME="${SCRATCHDIR}/cache" diff --git a/scripts/spack.yaml b/scripts/spack.yaml index 6514fbfa69..8de7f30f65 100644 --- a/scripts/spack.yaml +++ b/scripts/spack.yaml @@ -1,45 +1,76 @@ spack: specs: - # Packages required for a *truly minimal* build - - cli11 - - cmake - - nlohmann-json - # ... plus packages required for *basic* realistic development - - "geant4@11" - - git - - "googletest@1.10:" - - "py-pre-commit ^py-identify@2.6:" - - "python@3.9:" - # ... plus *recommended* options - - "covfie@0.13:" - - "g4vg@1.0.3:" - - hepmc3 - - libpng - - ninja - - "root@6.28:" - - "vecgeom@1.2.10:1 +gdml" - # ... plus additional documentation tools - - doxygen - - git-lfs - - py-breathe - - py-furo - - py-sphinx - - py-sphinxcontrib-bibtex - # ... plus experimental options - - mpi + # Packages required for a *truly minimal* build + - cli11 + - cmake + - nlohmann-json + # ... plus packages required for *basic* realistic development + - geant4@11.3 + - git + - googletest + - py-pre-commit + - python + # ... plus *recommended* options + - covfie + - g4vg + - hepmc3 + - ninja + - vecgeom@1 + # Uncomment the following lines for additional supported optional packages +# - root +# # documentation +# - doxygen +# - py-breathe +# - py-furo +# - py-sphinx +# - py-sphinxcontrib-bibtex +# # experimental +# - dd4hep +# - mpi +# # R&D repositories +# - git-lfs view: true concretizer: unify: true + # These encode version and variant requirements for packages used by Celeritas. + # Update this file when modifying the top-level CMakeLists.txt. packages: - root: - # Note: dd4hep requires +gsl+math - # ROOT here is built without GUI to reduce build time - variants: ~aqua ~davix ~examples ~opengl ~x ~tbb all: - require: - # Note: C++17 is the minimum required but we'll default to C++20 - - any_of: [cxxstd=20, '@:'] - - any_of: [build_system=cmake, '@:'] - - any_of: [build_type=Release, '@:'] + prefer: + - "generator=ninja build_system=cmake" + - "build_type=Release" + - "cxxstd=20" # Note: for CUDA support run this command in your environment: - # spack config add packages:all:variants:"+cuda cuda_arch=" + # spack config add packages:all:prefer:"+cuda cuda_arch=" + cli11: + require: '@2.4:' + cmake: + require: '@3.18:' + nlohmann-json: + require: '@3.7.0:' + geant4: + require: '@10.5:' + googletest: + require: '@1.10:' + py-identify: # needed by py-pre-commit + require: '@2.6:' + python: + require: '@3.9:' + covfie: + require: '@0.13:' + g4vg: + require: '@1.0.3:' + vecgeom: + require: + - '@1.2.10:' + - +gdml + root: + require: + - '@6.28:' + variants: + # Prefer building without GUI and with ROOT 7 support if available + - ~aqua ~davix ~examples ~opengl ~x ~tbb ~webgui +root7 + dd4hep: + require: + - '@1.18:' + - ~utilityapps ~ddeve diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d08ffc8bec..49b80b529f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -180,6 +180,9 @@ add_subdirectory(celeritas) if(CELERITAS_USE_Geant4) add_subdirectory(accel) endif() +if(CELERITAS_USE_DD4hep) + add_subdirectory(ddceler) +endif() if(CELERITAS_USE_LArSoft) add_subdirectory(larceler) endif() diff --git a/src/accel/CMakeLists.txt b/src/accel/CMakeLists.txt index 0800349f4f..4ffd78e95b 100644 --- a/src/accel/CMakeLists.txt +++ b/src/accel/CMakeLists.txt @@ -30,7 +30,8 @@ list(APPEND SOURCES GeantSimpleCalo.cc GeantStepDiagnostic.cc IntegrationBase.cc - LocalOpticalOffload.cc + LocalOpticalGenOffload.cc + LocalOpticalTrackOffload.cc LocalTransporter.cc Logger.cc PGPrimaryGeneratorAction.cc diff --git a/src/accel/ExceptionConverter.cc b/src/accel/ExceptionConverter.cc index 04626e39a2..216eea1253 100644 --- a/src/accel/ExceptionConverter.cc +++ b/src/accel/ExceptionConverter.cc @@ -88,7 +88,7 @@ void log_state(Logger::Message& msg, /*! * Capture the current exception and convert it to a G4Exception call. */ -void ExceptionConverter::operator()(std::exception_ptr eptr) const +void ExceptionConverter::operator()(std::exception_ptr eptr) { try { @@ -151,6 +151,7 @@ void ExceptionConverter::operator()(std::exception_ptr eptr) const return std::move(os).str(); }(); + forwarded_ = true; G4Exception(where.c_str(), err_code_, FatalException, what.c_str()); } catch (DebugError const& e) @@ -160,6 +161,7 @@ void ExceptionConverter::operator()(std::exception_ptr eptr) const where << detail::CleanedProvenance{e.details().file, e.details().line}; std::ostringstream what; what << to_cstring(e.details().which) << ": " << e.details().condition; + forwarded_ = true; G4Exception( where.str().c_str(), err_code_, FatalException, what.str().c_str()); } diff --git a/src/accel/ExceptionConverter.hh b/src/accel/ExceptionConverter.hh index ac48ec95d0..24ade1131f 100644 --- a/src/accel/ExceptionConverter.hh +++ b/src/accel/ExceptionConverter.hh @@ -26,6 +26,10 @@ class SharedParams; // Transport any tracks left in the buffer celeritas::ExceptionConverter call_g4exception{"celer.event.flush"}; CELER_TRY_HANDLE(transport_->Flush(), call_g4exception); + + // Debug error checking + CELER_ENSURE(transport_.GetBufferSize() == 0 + || call_g4exception.forwarded()); } * \endcode */ @@ -39,11 +43,15 @@ class ExceptionConverter inline explicit ExceptionConverter(char const* err_code); // Capture the current exception and convert it to a G4Exception call - void operator()(std::exception_ptr p) const; + void operator()(std::exception_ptr p); + + //! Whether an exception was passed to G4Exception + bool forwarded() const { return forwarded_; } private: char const* err_code_; SharedParams const* params_{nullptr}; + bool forwarded_{false}; void convert_device_exceptions(std::exception_ptr p) const; }; diff --git a/src/accel/FastSimulationIntegration.cc b/src/accel/FastSimulationIntegration.cc index f4b95e5b29..c14005e9d5 100644 --- a/src/accel/FastSimulationIntegration.cc +++ b/src/accel/FastSimulationIntegration.cc @@ -8,28 +8,12 @@ #include -#include "corecel/sys/Stopwatch.hh" +#include "corecel/io/Logger.hh" #include "ExceptionConverter.hh" -#include "TimeOutput.hh" - -#include "detail/IntegrationSingleton.hh" namespace celeritas { -namespace -{ -//---------------------------------------------------------------------------// -/*! - * Check fast simulation is set up. - * - * \todo This is currently a null op, but we should loop through the regions to - * ensure that at least one of them is a celeritas::FastSimulationModel. - */ -void verify_fast_sim() {} - -//---------------------------------------------------------------------------// -} // namespace //---------------------------------------------------------------------------// /*! @@ -43,35 +27,12 @@ FastSimulationIntegration& FastSimulationIntegration::Instance() //---------------------------------------------------------------------------// /*! - * Start the run, initializing Celeritas options. + * Verify fast simulation setup. */ -void FastSimulationIntegration::BeginOfRunAction(G4Run const*) +void FastSimulationIntegration::verify_local_setup() { - Stopwatch get_setup_time; - - auto& singleton = detail::IntegrationSingleton::instance(); - - if (G4Threading::IsMasterThread()) - { - singleton.initialize_shared_params(); - } - - bool enable_offload = singleton.initialize_local_transporter(); - - if (enable_offload) - { - // Set tracking manager on workers when Celeritas is not fully disabled - CELER_LOG(debug) << "Verifying fast simulation"; - - CELER_TRY_HANDLE(verify_fast_sim(), - ExceptionConverter{"celer.init.verify"}); - } - - if (G4Threading::IsMasterThread()) - { - singleton.shared_params().timer()->RecordSetupTime(get_setup_time()); - singleton.start_timer(); - } + // TODO: Loop through regions to ensure at least one has a + // celeritas::FastSimulationModel } //---------------------------------------------------------------------------// diff --git a/src/accel/FastSimulationIntegration.hh b/src/accel/FastSimulationIntegration.hh index 5f9921177c..ea6c06b13d 100644 --- a/src/accel/FastSimulationIntegration.hh +++ b/src/accel/FastSimulationIntegration.hh @@ -18,16 +18,13 @@ namespace celeritas * application. To use this class in your Geant4 application to offload tracks * to Celeritas: * - * - Add the \c FastSimulationModel to regions of interest. * - Use \c SetOptions to set up options before \c G4RunManager::Initialize: * usually in \c main for simple applications. + * - In your \c G4VUserDetectorConstruction::ConstructSDandField, called during + * initialization, attach the \c FastSimulationModel to regions of interest * - Call \c BeginOfRunAction and \c EndOfRunAction from \c UserRunAction * - * The \c CELER_DISABLE environment variable, if set and non-empty, will - * disable offloading so that Celeritas will not be built nor kill tracks. - * - * The method names correspond to methods in Geant4 User Actions and \em must - * be called from all threads, both worker and master. + * See further documentation in \c celeritas::IntegrationBase. */ class FastSimulationIntegration final : public IntegrationBase { @@ -35,12 +32,12 @@ class FastSimulationIntegration final : public IntegrationBase // Access the public-facing integration singleton static FastSimulationIntegration& Instance(); - // Start the run - void BeginOfRunAction(G4Run const* run) final; - private: // Tracking manager can only be created privately FastSimulationIntegration(); + + // Verify fast simulation setup + void verify_local_setup() final; }; //---------------------------------------------------------------------------// diff --git a/src/accel/FastSimulationModel.hh b/src/accel/FastSimulationModel.hh index 4607a3d35d..13e03a3cf6 100644 --- a/src/accel/FastSimulationModel.hh +++ b/src/accel/FastSimulationModel.hh @@ -23,9 +23,6 @@ class LocalTransporter; * `G4VUserDetectorConstruction::ConstructSDandField()`. * * Note that the argument \c G4Envelope is a type alias to \c G4Region. - * - * \todo Maybe need a helper to create a single fast sim model for multiple - * regions? */ class FastSimulationModel final : public G4VFastSimulationModel { diff --git a/src/accel/IntegrationBase.cc b/src/accel/IntegrationBase.cc index 9a62b6e27d..6f52a0b764 100644 --- a/src/accel/IntegrationBase.cc +++ b/src/accel/IntegrationBase.cc @@ -11,7 +11,6 @@ #include "corecel/Assert.hh" #include "ExceptionConverter.hh" -#include "TimeOutput.hh" #include "detail/IntegrationSingleton.hh" @@ -20,54 +19,68 @@ using celeritas::detail::IntegrationSingleton; namespace celeritas { //---------------------------------------------------------------------------// +//!@{ +//! \name User integration points + /*! * Set options before starting the run. * - * This captures the input to indicate that options cannot be modified after - * this point. + * This captures the input to indicate that options cannot be modified by the + * framework after this point. */ void IntegrationBase::SetOptions(SetupOptions&& opts) { IntegrationSingleton::instance().setup_options(std::move(opts)); } -//---------------------------------------------------------------------------// /*! - * Access whether Celeritas is set up, enabled, or uninitialized. + * Start the run. * - * This is only legal to call after \c SetOptions. + * This handles shared/local setup and calls verify_setup if offload is + * enabled. */ -OffloadMode IntegrationBase::GetMode() const +void IntegrationBase::BeginOfRunAction(G4Run const*) { - return IntegrationSingleton::instance().mode(); + auto& singleton = IntegrationSingleton::instance(); + + // Initialize shared params and local transporter + bool enable_offload = singleton.initialize_offload(); + + if (enable_offload) + { + // Allow derived classes to perform their specific verification + CELER_TRY_HANDLE(this->verify_local_setup(), + ExceptionConverter{"celer.init.verify"}); + } } -//---------------------------------------------------------------------------// /*! * End the run. */ void IntegrationBase::EndOfRunAction(G4Run const*) { - CELER_LOG(status) << "Finalizing Celeritas"; - auto& singleton = IntegrationSingleton::instance(); - // Record the run time - auto time = singleton.stop_timer(); + singleton.finalize_offload(); +} - // Remove local transporter - singleton.finalize_local_transporter(); +//!@} +//---------------------------------------------------------------------------// +//!@{ +//! \name Low-level Celeritas accessors - if (G4Threading::IsMasterThread()) - { - singleton.shared_params().timer()->RecordTotalTime(time); - singleton.finalize_shared_params(); - } +/*! + * Access whether Celeritas is set up, enabled, or uninitialized. + * + * This is only legal to call after \c SetOptions. + */ +OffloadMode IntegrationBase::GetMode() const +{ + return IntegrationSingleton::instance().mode(); } -//---------------------------------------------------------------------------// /*! - * Access Celeritas shared params. + * Access \em global Celeritas shared params during a run, if not disabled. */ CoreParams const& IntegrationBase::GetParams() { @@ -84,12 +97,11 @@ CoreParams const& IntegrationBase::GetParams() return *singleton.shared_params().Params(); } -//---------------------------------------------------------------------------// /*! - * Access THREAD-LOCAL Celeritas core state data for user diagnostics. + * Access \em thread-local Celeritas core state data for user diagnostics. * * - This can \em only be called when Celeritas is enabled (not kill-offload, - * not disabled) + * not disabled). * - This cannot be called from the main thread of an MT application. */ CoreStateInterface& IntegrationBase::GetState() @@ -109,6 +121,7 @@ CoreStateInterface& IntegrationBase::GetState() return singleton.local_transporter().GetState(); } +//!@} //---------------------------------------------------------------------------// /*! diff --git a/src/accel/IntegrationBase.hh b/src/accel/IntegrationBase.hh index 946b30d053..d85b8ba292 100644 --- a/src/accel/IntegrationBase.hh +++ b/src/accel/IntegrationBase.hh @@ -27,9 +27,6 @@ class CoreParams; * The \c GetParams and \c GetState methods may only be used during a run with * Celeritas offloading enabled. * - * \note It cannot be accessed before the Geant4 run manager is created (this - * requirement may be relaxed in the future). - * * \sa celeritas::UserActionIntegration * \sa celeritas::TrackingManagerIntegration * @@ -39,12 +36,11 @@ class CoreParams; class IntegrationBase { public: + //// USER INTEGRATION POINTS //// + // Set options before starting the run void SetOptions(SetupOptions&& opts); - // Access Celeritas offload mode type after options are set - OffloadMode GetMode() const; - // REMOVE in v0.7 [[deprecated]] void BuildForMaster() {} @@ -52,12 +48,15 @@ class IntegrationBase [[deprecated]] void Build() {} // Start the run - virtual void BeginOfRunAction(G4Run const* run) = 0; + void BeginOfRunAction(G4Run const* run); // End the run void EndOfRunAction(G4Run const* run); - //// ACCESSORS //// + //// LOW-LEVEL ACCESSORS //// + + // Access Celeritas offload mode type after options are set + OffloadMode GetMode() const; // Access Celeritas shared params CoreParams const& GetParams(); @@ -69,6 +68,9 @@ class IntegrationBase IntegrationBase(); ~IntegrationBase() = default; CELER_DEFAULT_COPY_MOVE(IntegrationBase); + + //! Verify setup after initialization (called if thread is doing offload) + virtual void verify_local_setup() = 0; }; //---------------------------------------------------------------------------// diff --git a/src/accel/LocalOpticalOffload.cc b/src/accel/LocalOpticalGenOffload.cc similarity index 89% rename from src/accel/LocalOpticalOffload.cc rename to src/accel/LocalOpticalGenOffload.cc index 8ef2870aae..309908671c 100644 --- a/src/accel/LocalOpticalOffload.cc +++ b/src/accel/LocalOpticalGenOffload.cc @@ -2,9 +2,9 @@ // Copyright Celeritas contributors: see top-level COPYRIGHT file for details // SPDX-License-Identifier: (Apache-2.0 OR MIT) //---------------------------------------------------------------------------// -//! \file accel/LocalOpticalOffload.cc +//! \file accel/LocalOpticalGenOffload.cc //---------------------------------------------------------------------------// -#include "LocalOpticalOffload.hh" +#include "LocalOpticalGenOffload.hh" #include #include @@ -32,8 +32,8 @@ namespace celeritas /*! * Construct with options and shared data. */ -LocalOpticalOffload::LocalOpticalOffload(SetupOptions const& options, - SharedParams& params) +LocalOpticalGenOffload::LocalOpticalGenOffload(SetupOptions const& options, + SharedParams& params) { CELER_VALIDATE(params.mode() == SharedParams::Mode::enabled, << "cannot create local optical offload when Celeritas " @@ -44,16 +44,16 @@ LocalOpticalOffload::LocalOpticalOffload(SetupOptions const& options, << "invalid optical photon generation mechanism for local " "optical offload"); - // Check the thread ID and MT model - validate_geant_threading(params.Params()->max_streams()); - // Save a pointer to the optical transporter - transport_ = params.optical_transporter(); + transport_ = params.optical_problem_loaded().transporter; CELER_ASSERT(transport_); CELER_ASSERT(transport_->params()); auto const& optical_params = *transport_->params(); + // Check the thread ID and MT model + validate_geant_threading(optical_params.max_streams()); + // Save a pointer to the generator action auto const& gen_reg = *optical_params.gen_reg(); for (auto gen_id : range(GeneratorId(gen_reg.size()))) @@ -88,10 +88,10 @@ LocalOpticalOffload::LocalOpticalOffload(SetupOptions const& options, } // Allocate auxiliary data - if (params.Params()->aux_reg()) + if (optical_params.aux_reg()) { state_->aux() = std::make_shared( - *params.Params()->aux_reg(), memspace, stream_id, capacity.tracks); + *optical_params.aux_reg(), memspace, stream_id, capacity.tracks); } CELER_ENSURE(*this); @@ -101,17 +101,17 @@ LocalOpticalOffload::LocalOpticalOffload(SetupOptions const& options, /*! * Initialize with options and shared data. */ -void LocalOpticalOffload::Initialize(SetupOptions const& options, - SharedParams& params) +void LocalOpticalGenOffload::Initialize(SetupOptions const& options, + SharedParams& params) { - *this = LocalOpticalOffload(options, params); + *this = LocalOpticalGenOffload(options, params); } //---------------------------------------------------------------------------// /*! * Set the event ID and reseed the Celeritas RNG at the start of an event. */ -void LocalOpticalOffload::InitializeEvent(int id) +void LocalOpticalGenOffload::InitializeEvent(int id) { CELER_EXPECT(*this); CELER_EXPECT(id >= 0); @@ -132,7 +132,7 @@ void LocalOpticalOffload::InitializeEvent(int id) /*! * Buffer distribution data for generating optical photons. */ -void LocalOpticalOffload::Push(optical::GeneratorDistributionData const& data) +void LocalOpticalGenOffload::Push(optical::GeneratorDistributionData const& data) { CELER_EXPECT(*this); CELER_EXPECT(data); @@ -152,7 +152,7 @@ void LocalOpticalOffload::Push(optical::GeneratorDistributionData const& data) /*! * Generate and transport optical photons from the buffered distribution data. */ -void LocalOpticalOffload::Flush() +void LocalOpticalGenOffload::Flush() { CELER_EXPECT(*this); @@ -207,7 +207,7 @@ void LocalOpticalOffload::Flush() /*! * Get the accumulated action times. */ -auto LocalOpticalOffload::GetActionTime() const -> MapStrDbl +auto LocalOpticalGenOffload::GetActionTime() const -> MapStrDbl { CELER_EXPECT(*this); return transport_->get_action_times(*state_->aux()); @@ -217,7 +217,7 @@ auto LocalOpticalOffload::GetActionTime() const -> MapStrDbl /*! * Clear local data. */ -void LocalOpticalOffload::Finalize() +void LocalOpticalGenOffload::Finalize() { CELER_EXPECT(*this); diff --git a/src/accel/LocalOpticalOffload.hh b/src/accel/LocalOpticalGenOffload.hh similarity index 92% rename from src/accel/LocalOpticalOffload.hh rename to src/accel/LocalOpticalGenOffload.hh index 48039b849d..343c27860b 100644 --- a/src/accel/LocalOpticalOffload.hh +++ b/src/accel/LocalOpticalGenOffload.hh @@ -2,7 +2,7 @@ // Copyright Celeritas contributors: see top-level COPYRIGHT file for details // SPDX-License-Identifier: (Apache-2.0 OR MIT) //---------------------------------------------------------------------------// -//! \file accel/LocalOpticalOffload.hh +//! \file accel/LocalOpticalGenOffload.hh //---------------------------------------------------------------------------// #pragma once @@ -33,7 +33,7 @@ class SharedParams; /*! * Manage offloading of optical distribution data to Celeritas. */ -class LocalOpticalOffload final : public LocalOffloadInterface +class LocalOpticalGenOffload final : public LocalOffloadInterface { public: //!@{ @@ -43,10 +43,10 @@ class LocalOpticalOffload final : public LocalOffloadInterface public: // Construct in an invalid state - LocalOpticalOffload() = default; + LocalOpticalGenOffload() = default; // Construct with shared (across threads) params - LocalOpticalOffload(SetupOptions const& options, SharedParams& params); + LocalOpticalGenOffload(SetupOptions const& options, SharedParams& params); //!@{ //! \name LocalOffload interface diff --git a/src/accel/LocalOpticalTrackOffload.cc b/src/accel/LocalOpticalTrackOffload.cc new file mode 100644 index 0000000000..878f9a3d3c --- /dev/null +++ b/src/accel/LocalOpticalTrackOffload.cc @@ -0,0 +1,209 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file accel/LocalOpticalTrackOffload.cc +//---------------------------------------------------------------------------// +#include "LocalOpticalTrackOffload.hh" + +#include +#include +#include + +#include "corecel/Assert.hh" +#include "corecel/sys/ScopedProfiling.hh" +#include "geocel/GeantUtils.hh" +#include "geocel/g4/Convert.hh" +#include "celeritas/ext/GeantUnits.hh" +#include "celeritas/global/CoreParams.hh" +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/Transporter.hh" + +#include "SetupOptions.hh" +#include "SharedParams.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Offload Geant4 optical photon tracks to Celeritas + */ +LocalOpticalTrackOffload::LocalOpticalTrackOffload(SetupOptions const& options, + SharedParams& params) +{ + CELER_VALIDATE(params.mode() == SharedParams::Mode::enabled, + << "cannot create local optical track offload when " + "Celeritas " + "offloading is disabled"); + + // Save a pointer to the optical transporter + transport_ = params.optical_problem_loaded().transporter; + + // Save a pointer to the direct generator action to insert tracks + direct_gen_ + = std::dynamic_pointer_cast( + params.optical_problem_loaded().generator); + + CELER_ASSERT(transport_); + CELER_ASSERT(transport_->params()); + + auto const& optical_params = *transport_->params(); + + // Check the thread ID and MT model + validate_geant_threading(optical_params.max_streams()); + + CELER_EXPECT(options.optical); + auto const& capacity = options.optical->capacity; + auto_flush_ = capacity.tracks; + + auto stream_id = id_cast(get_geant_thread_id()); + + // Allocate thread-local state data + auto memspace = celeritas::device() ? MemSpace::device : MemSpace::host; + if (memspace == MemSpace::device) + { + state_ = std::make_shared>( + optical_params, stream_id, capacity.tracks); + } + else + { + state_ = std::make_shared>( + optical_params, stream_id, capacity.tracks); + } + + // Allocate auxiliary data + if (optical_params.aux_reg()) + { + state_->aux() = std::make_shared( + *optical_params.aux_reg(), memspace, stream_id, capacity.tracks); + } + + CELER_ENSURE(*this); +} + +//---------------------------------------------------------------------------// +/*! + * Initialize with options and shared data. + */ +void LocalOpticalTrackOffload::Initialize(SetupOptions const& options, + SharedParams& params) +{ + *this = LocalOpticalTrackOffload(options, params); +} + +//---------------------------------------------------------------------------// +/*! + * Set the event ID and reseed the Celeritas RNG at the start of an event. + */ +void LocalOpticalTrackOffload::InitializeEvent(int id) +{ + CELER_EXPECT(*this); + CELER_EXPECT(id >= 0); + + event_id_ = id_cast(id); + if (!(G4Threading::IsMultithreadedApplication() + && G4MTRunManager::SeedOncePerCommunication())) + { + // Since Geant4 schedules events dynamically, reseed the Celeritas + // RNGs + // using the Geant4 event ID for reproducibility. This guarantees + // that + // an event can be reproduced given the event ID. + state_->reseed(transport_->params()->rng(), id_cast(id)); + } +} + +//---------------------------------------------------------------------------// +/*! + * Buffer optical tracks. + */ +void LocalOpticalTrackOffload::Push(G4Track& g4track) +{ + CELER_EXPECT(*this); + + ++num_pushed_; + + CELER_EXPECT(g4track.GetDefinition()); + CELER_EXPECT(g4track.GetDefinition()->GetParticleName() == "opticalphoton"); + + // Convert Geant4 track to optical::TrackInitializer + TrackData init; + + init.energy = units::MevEnergy( + convert_from_geant(g4track.GetKineticEnergy(), CLHEP::MeV)); + + init.position = convert_from_geant(g4track.GetPosition(), CLHEP::cm); + + init.direction = convert_from_geant(g4track.GetMomentumDirection(), 1); + + init.time = convert_from_geant(g4track.GetGlobalTime(), CLHEP::second); + init.polarization = convert_from_geant(g4track.GetPolarization(), 1); + + ScopedProfiling profile_this{"push"}; + + buffer_.push_back(init); + + if (buffer_.size() >= auto_flush_) + { + this->Flush(); + } +} + +//---------------------------------------------------------------------------// +/*! + * Flush buffered optical photon tracks. + */ +void LocalOpticalTrackOffload::Flush() +{ + CELER_EXPECT(*this); + + if (buffer_.empty()) + { + return; + } + + ScopedProfiling profile_this("flush"); + + // Insert tracks + if (direct_gen_) + { + direct_gen_->insert(*state_, make_span(buffer_)); + } + + // Transport tracks + (*transport_)(*state_); + + ++num_flushed_; + buffer_.clear(); +} +//---------------------------------------------------------------------------// +auto LocalOpticalTrackOffload::GetActionTime() const -> MapStrDbl +{ + CELER_EXPECT(*this); + // TODO Add Per-track optical transport action timing once + // optical track insertion and transport are implemented. + return transport_->get_action_times(*state_->aux()); +} + +//---------------------------------------------------------------------------// +/*! + * Finalize the local optical track offload state + */ +void LocalOpticalTrackOffload::Finalize() +{ + CELER_EXPECT(*this); + + CELER_VALIDATE(buffer_.empty(), + << buffer_.size() << " optical tracks were not flushed"); + + CELER_LOG(info) << "Finalizing Celeritas after " << num_pushed_ + << " optical tracks pushed (over " << num_flushed_ + << " ) flushes"; + + *this = {}; + + CELER_ENSURE(!*this); +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/accel/LocalOpticalTrackOffload.hh b/src/accel/LocalOpticalTrackOffload.hh new file mode 100644 index 0000000000..281878f4c2 --- /dev/null +++ b/src/accel/LocalOpticalTrackOffload.hh @@ -0,0 +1,102 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file accel/LocalOpticalTrackOffload.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Types.hh" +#include "celeritas/Types.hh" +#include "celeritas/optical/TrackInitializer.hh" +#include "celeritas/optical/gen/DirectGeneratorAction.hh" + +#include "TrackOffloadInterface.hh" + +namespace celeritas +{ +namespace optical +{ +class CoreStateBase; +class Transporter; +} // namespace optical + +struct SetupOptions; +class SharedParams; + +//---------------------------------------------------------------------------// +/*! + * Offload Geant4 optical photon tracks to Celeritas. + */ +class LocalOpticalTrackOffload final : public TrackOffloadInterface +{ + public: + //!@{ + //! \name Type aliases + using TrackData = optical::TrackInitializer; + //!@} + + public: + // Construct in an invalid state + LocalOpticalTrackOffload() = default; + + // Construct with shared (across threads) params + LocalOpticalTrackOffload(SetupOptions const& options, SharedParams& params); + + //!@{ + //! \name TrackOffloadInterface + + // Initialize with options and shared data + void Initialize(SetupOptions const&, SharedParams&) final; + + // Set the event ID and reseed the Celeritas RNG at the start of an event + void InitializeEvent(int) final; + + // Transport all buffered tracks to completion + void Flush() final; + + // Clear local data and return to an invalid state + void Finalize() final; + + // Whether the class instance is initialized + bool Initialized() const final { return static_cast(transport_); } + + // Number of buffered tracks + size_type GetBufferSize() const final { return buffer_.size(); } + + // Get accumulated action times + MapStrDbl GetActionTime() const final; + //!@} + + // Offload optical distribution track to Celeritas + void Push(G4Track&) final; + // Number of optical tracks pushed to offload + size_type num_pushed() const { return num_pushed_; } + + private: + // Transport pending optical tracks + std::shared_ptr transport_; + + // Thread-local state data + std::shared_ptr state_; + + std::shared_ptr direct_gen_; + + // Buffered tracks for offloading + std::vector buffer_; + + // Number of photons tracks to buffer before offloading + size_type auto_flush_{}; + + // Accumulated number of optical photon tracks + size_type num_pushed_{}; + + // Accumulated number of tracks pushed over flushes + size_type num_flushed_{}; + + // Current event ID for obtaining it + UniqueEventId event_id_; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/accel/LocalTransporter.cc b/src/accel/LocalTransporter.cc index dea3bd074a..8248fc4f49 100644 --- a/src/accel/LocalTransporter.cc +++ b/src/accel/LocalTransporter.cc @@ -40,8 +40,10 @@ #include "celeritas/global/ActionSequence.hh" #include "celeritas/global/CoreParams.hh" #include "celeritas/global/Stepper.hh" +#include "celeritas/inp/Control.hh" #include "celeritas/io/EventWriter.hh" #include "celeritas/io/JsonEventWriter.hh" +#include "celeritas/io/OffloadWriter.hh" #include "celeritas/io/RootEventWriter.hh" #include "celeritas/optical/CoreState.hh" #include "celeritas/optical/OpticalCollector.hh" @@ -51,8 +53,6 @@ #include "SetupOptions.hh" #include "SharedParams.hh" -#include "detail/OffloadWriter.hh" - namespace celeritas { namespace @@ -150,9 +150,7 @@ void trace(StepperResult const& track_counts) */ LocalTransporter::LocalTransporter(SetupOptions const& options, SharedParams& params) - : auto_flush_( - get_default(options, params.Params()->max_streams()).primaries) - , max_step_iters_(options.max_step_iters) + : max_step_iters_(options.max_step_iters) , dump_primaries_{params.offload_writer()} { CELER_VALIDATE(params.mode() == SharedParams::Mode::enabled, @@ -164,6 +162,18 @@ LocalTransporter::LocalTransporter(SetupOptions const& options, << "invalid optical photon generation mechanism for local " "transporter"); + if (options.auto_flush) + { + auto_flush_ = options.auto_flush; + } + else + { + // Get default *per-process* auto flush and divide by number of streams + auto capacity = inp::CoreStateCapacity::from_default( + celeritas::Device::num_devices()); + auto_flush_ = capacity.primaries / params.Params()->max_streams(); + } + particles_ = params.Params()->particle(); CELER_ASSERT(particles_); bbox_ = params.bbox(); @@ -198,7 +208,7 @@ LocalTransporter::LocalTransporter(SetupOptions const& options, params.set_state(stream_id.get(), step_->sp_state()); // Save optical pointers if available, for diagnostics - optical_ = params.optical_collector(); + optical_ = params.problem_loaded().optical_collector; CELER_ENSURE(*this); } diff --git a/src/accel/LocalTransporter.hh b/src/accel/LocalTransporter.hh index b9510d564b..68b9781b07 100644 --- a/src/accel/LocalTransporter.hh +++ b/src/accel/LocalTransporter.hh @@ -15,7 +15,7 @@ #include "celeritas/Types.hh" #include "celeritas/phys/Primary.hh" -#include "LocalOffloadInterface.hh" +#include "TrackOffloadInterface.hh" class G4EventManager; class G4Track; @@ -26,11 +26,11 @@ namespace celeritas namespace detail { class HitProcessor; -class OffloadWriter; } // namespace detail struct SetupOptions; class CoreStateInterface; +class OffloadWriter; class OpticalCollector; class ParticleParams; class SharedParams; @@ -49,10 +49,8 @@ class StepperInterface; * * \warning Due to Geant4 thread-local allocators, this class \em must be * finalized or destroyed on the same CPU thread in which is created and used! - * - * \todo Rename \c LocalOffload or something? */ -class LocalTransporter final : public LocalOffloadInterface +class LocalTransporter final : public TrackOffloadInterface { public: // Construct in an invalid state @@ -88,7 +86,7 @@ class LocalTransporter final : public LocalOffloadInterface //!@} // Offload this track - void Push(G4Track&); + void Push(G4Track&) override; // Access core state data for user diagnostics CoreStateInterface const& GetState() const; @@ -102,7 +100,7 @@ class LocalTransporter final : public LocalOffloadInterface private: //// TYPES //// - using SPOffloadWriter = std::shared_ptr; + using SPOffloadWriter = std::shared_ptr; using BBox = BoundingBox; struct BufferAccum diff --git a/src/accel/Logger.cc b/src/accel/Logger.cc index 603d766edb..b5d3020f2c 100644 --- a/src/accel/Logger.cc +++ b/src/accel/Logger.cc @@ -34,7 +34,7 @@ void write_serial(LogProvenance prov, LogLevel lev, std::string msg) } //---------------------------------------------------------------------------// -//! Tag a singular output with worker/master: should usually be master +//! Tag a singular output with worker/main: should usually be main void write_mt_world(LogProvenance prov, LogLevel lev, std::string msg) { if (G4Threading::G4GetThreadId() > 0) @@ -88,11 +88,12 @@ Logger MakeMTWorldLogger(G4RunManager const& runman) } else { - // Only master and the first worker write + // Only main (and the first worker in MT mode) write handle = write_mt_world; } } - return Logger::from_handle_env(std::move(handle), "CELER_LOG"); + return Logger{std::move(handle), + getenv_loglevel("CELER_LOG", LogLevel::status)}; } //---------------------------------------------------------------------------// @@ -122,7 +123,8 @@ Logger MakeMTSelfLogger(G4RunManager const& runman) { handle = detail::MtSelfWriter{get_geant_num_threads(runman)}; } - return Logger::from_handle_env(std::move(handle), "CELER_LOG_LOCAL"); + return Logger{std::move(handle), + getenv_loglevel("CELER_LOG_LOCAL", LogLevel::warning)}; } //---------------------------------------------------------------------------// diff --git a/src/accel/SetupOptions.cc b/src/accel/SetupOptions.cc index b19ce1c1ba..dcadf3e1ce 100644 --- a/src/accel/SetupOptions.cc +++ b/src/accel/SetupOptions.cc @@ -91,13 +91,27 @@ void ProblemSetup::operator()(inp::Problem& p) const // NOTE: old SetupOptions input *per stream*, but inp::Problem needs // *integrated* over streams p.control.capacity = [this, num_streams = p.control.num_streams] { - auto capacity = get_default(so, num_streams); - inp::CoreStateCapacity c; - c.tracks = capacity.tracks; - c.initializers = capacity.initializers; - c.primaries = capacity.primaries; - c.secondaries - = static_cast(so.secondary_stack_factor * c.tracks); + auto c = inp::CoreStateCapacity::from_default( + celeritas::Device::num_devices()); + + // Override default values if capacities were specified + if (so.max_num_tracks) + { + c.tracks = so.max_num_tracks * num_streams; + } + if (so.initializer_capacity) + { + c.initializers = so.initializer_capacity * num_streams; + } + if (so.auto_flush) + { + c.primaries = so.auto_flush * num_streams; + } + if (so.secondary_stack_factor) + { + c.secondaries + = static_cast(so.secondary_stack_factor * c.tracks); + } return c; }(); @@ -112,9 +126,7 @@ void ProblemSetup::operator()(inp::Problem& p) const if (so.optical) { p.control.optical_capacity = so.optical->capacity; - p.physics.optical_generator = so.optical->generator; - p.tracking.optical_limits.steps = so.optical->max_steps; - p.tracking.optical_limits.step_iters = so.optical->max_step_iters; + p.tracking.optical_limits = so.optical->limits; } if (so.track_order != TrackOrder::size_) @@ -204,6 +216,45 @@ void ProblemSetup::operator()(inp::Problem& p) const p.diagnostics.add_user_actions = so.add_user_actions; } +//---------------------------------------------------------------------------// +/*! + * FrameworkInput adapter function for optical-only offload. + */ +struct OpticalProblemSetup +{ + SetupOptions const& so; + + void operator()(inp::OpticalProblem&) const; +}; + +//---------------------------------------------------------------------------// +/*! + * Set a Celeritas optical problem input definition. + */ +void OpticalProblemSetup::operator()(inp::OpticalProblem& p) const +{ + if (!so.geometry_file.empty()) + { + p.model.geometry = so.geometry_file; + } + + p.num_streams = [&so = this->so] { + if (so.get_num_streams) + { + return so.get_num_streams(); + } + return celeritas::get_geant_num_threads(); + }(); + + CELER_ASSERT(so.optical); + p.generator = so.optical->generator; + p.capacity = so.optical->capacity; + p.limits = so.optical->limits; + p.seed = CLHEP::HepRandom::getTheSeed(); + p.timers.action = so.action_times; + p.output_file = so.output_file; +} + //---------------------------------------------------------------------------// } // namespace @@ -282,38 +333,18 @@ inp::FrameworkInput to_inp(SetupOptions const& so) result.physics_import.data_selection.particles = selection; result.physics_import.data_selection.processes = selection; - result.adjust = ProblemSetup{so}; - return result; -} + if (!so.optical + || std::holds_alternative( + so.optical->generator)) + { + result.adjust = ProblemSetup{so}; + } + else + { + // Optical-only offload + result.adjust_optical = OpticalProblemSetup{so}; + } -//---------------------------------------------------------------------------// -/*! - * Get runtime-dependent default capacity values. - * - * \note This must be called after CUDA/MPI have been initialized. - */ -inp::CoreStateCapacity -get_default(SetupOptions const& so, size_type num_streams) -{ - inp::CoreStateCapacity result; - result.tracks = num_streams * [&so] { - if (so.max_num_tracks) - { - return static_cast(so.max_num_tracks); - } - if (celeritas::Device::num_devices()) - { - constexpr size_type device_default = 262144; - return device_default; - } - constexpr size_type host_default = 1024; - return host_default; - }(); - result.initializers = so.initializer_capacity - ? num_streams * so.initializer_capacity - : 8 * result.tracks; - result.primaries = so.auto_flush ? so.auto_flush - : result.tracks / num_streams; return result; } diff --git a/src/accel/SetupOptions.hh b/src/accel/SetupOptions.hh index de110f9beb..4f00743e67 100644 --- a/src/accel/SetupOptions.hh +++ b/src/accel/SetupOptions.hh @@ -64,7 +64,8 @@ struct AlongStepFactoryInput; * are never set. * * The \c force_volumes option can be used for unusual cases (i.e., when using - * a custom run manager) that do not define SDs on the "master" thread. + * a custom run manager) that do not define the same SDs on the main thread + * as on worker threads. * Similarly, the \c skip_volumes option allows optimized GPU-defined SDs to be * used in place of a Geant4 callback. For both options, the \c * FindVolumes helper function can be used to determine LV pointers from @@ -125,10 +126,10 @@ struct OpticalSetupOptions inp::OpticalStateCapacity capacity; //! Optical photon generation mechanism inp::OpticalGenerator generator; - //! Limit on number of steps per track before killing - size_type max_steps{inp::TrackingLimits::unlimited}; - //! Limit on number of optical step iterations before aborting - size_type max_step_iters{inp::TrackingLimits::unlimited}; + //! Limits for the optical stepping loop + inp::OpticalTrackingLimits limits; + + bool offload_optical_tracks = false; }; //---------------------------------------------------------------------------// @@ -145,7 +146,7 @@ struct OpticalSetupOptions * disables Celeritas offloading and immediately kills the \c offload_particles * in Geant4. The only expected use case for an empty \c offload_particles * vector is when offloading optical distribution data to Celeritas through the - * \c LocalOpticalOffload. + * \c LocalOpticalGenOffload. * * Note that the Celeritas core capacity values (\c max_num_tracks, \c * initializer_capacity and \c auto_flush) are per \em stream while the \c @@ -193,7 +194,6 @@ struct SetupOptions //!@{ //! \name Optical photon options - std::optional optical; //!@} @@ -209,7 +209,7 @@ struct SetupOptions //! Maximum number of track initializers (primaries+secondaries) size_type initializer_capacity{}; //! At least the average number of secondaries per track slot - real_type secondary_stack_factor{2.0}; + real_type secondary_stack_factor{}; //! Number of tracks to buffer before offloading (if unset: max num tracks) size_type auto_flush{}; //!@} @@ -291,9 +291,5 @@ inp::GeantSd to_inp(SDSetupOptions const& so); // Construct a framework input inp::FrameworkInput to_inp(SetupOptions const& so); -// Get runtime-dependent default capacity values -inp::CoreStateCapacity -get_default(SetupOptions const& so, size_type num_streams); - //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/accel/SharedParams.cc b/src/accel/SharedParams.cc index 33f23ec135..6f35698f7b 100644 --- a/src/accel/SharedParams.cc +++ b/src/accel/SharedParams.cc @@ -29,21 +29,16 @@ #include "corecel/Assert.hh" #include "corecel/cont/ArrayIO.hh" +#include "corecel/cont/VariantUtils.hh" #include "corecel/io/BuildOutput.hh" #include "corecel/io/Join.hh" #include "corecel/io/Logger.hh" #include "corecel/io/OutputInterfaceAdapter.hh" #include "corecel/io/OutputRegistry.hh" #include "corecel/io/ScopedTimeLog.hh" -#include "corecel/io/StringUtils.hh" #include "corecel/random/params/RngParams.hh" #include "corecel/sys/ActionRegistry.hh" #include "corecel/sys/Device.hh" -#include "corecel/sys/Environment.hh" -#include "corecel/sys/EnvironmentIO.json.hh" -#include "corecel/sys/KernelRegistry.hh" -#include "corecel/sys/MemRegistry.hh" -#include "corecel/sys/MemRegistryIO.json.hh" #include "corecel/sys/ScopedMem.hh" #include "corecel/sys/ScopedProfiling.hh" #include "corecel/sys/ThreadId.hh" @@ -60,12 +55,10 @@ #include "celeritas/global/CoreParams.hh" #include "celeritas/inp/FrameworkInput.hh" #include "celeritas/inp/Scoring.hh" -#include "celeritas/io/EventWriter.hh" #include "celeritas/io/ImportData.hh" -#include "celeritas/io/JsonEventWriter.hh" -#include "celeritas/io/RootEventWriter.hh" #include "celeritas/mat/MaterialParams.hh" #include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/Transporter.hh" #include "celeritas/phys/CutoffParams.hh" #include "celeritas/phys/ParticleParams.hh" #include "celeritas/phys/PhysicsParams.hh" @@ -82,7 +75,6 @@ #include "TimeOutput.hh" #include "detail/IntegrationSingleton.hh" -#include "detail/OffloadWriter.hh" namespace celeritas { @@ -261,7 +253,7 @@ bool SharedParams::KillOffloadTracks() * * This is a separate step from construction because it has to happen at the * beginning of the run, not when user classes are created. It should be called - * from the "master" thread (for MT mode) or from the main thread (for Serial), + * from the main thread ("master" when MT), * and it must complete before any worker thread tries to access the shared * data. */ @@ -282,35 +274,24 @@ SharedParams::SharedParams(SetupOptions const& options) ? *options.offload_particles : default_offload_particles(); } - if (mode_ != Mode::enabled) { // Stop initializing but create output registry for diagnostics output_reg_ = std::make_shared(); - output_filename_ = options.output_file; + loaded_.output_file = options.output_file; // Create the timing output timer_ = std::make_shared(celeritas::get_geant_num_threads()); - if (!output_filename_.empty()) + if (!loaded_.output_file.empty()) { CELER_LOG(debug) << R"(Constructing output registry for no-offload run)"; // Celeritas core params didn't add system metadata: do it // ourselves to save system diagnostic information - output_reg_->insert( - OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, - "memory", - celeritas::mem_registry())); - output_reg_->insert( - OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, - "environ", - celeritas::environment())); - output_reg_->insert(std::make_shared()); + insert_system_diagnostics(*output_reg_); output_reg_->insert(timer_); } @@ -319,60 +300,40 @@ SharedParams::SharedParams(SetupOptions const& options) // Construct input and then build the problem setup auto framework_inp = to_inp(options); - auto loaded = setup::framework_input(framework_inp); - params_ = std::move(loaded.problem.core_params); - CELER_ASSERT(params_); - output_filename_ = loaded.problem.output_file; - - if (auto const& opt = options.optical) - { - if (std::holds_alternative(opt->generator)) - { - optical_transporter_ - = std::move(loaded.problem.optical_transporter); - CELER_ASSERT(optical_transporter_); - } - else if (std::holds_alternative(opt->generator)) - { - optical_collector_ = std::move(loaded.problem.optical_collector); - CELER_ASSERT(optical_collector_); - } - else - { - CELER_VALIDATE(false, - << "invalid optical photon generation mechanism"); - } - } - - // Save action sequence - actions_ = std::move(loaded.problem.actions); - CELER_ASSERT(actions_); + loaded_ = setup::framework_input(framework_inp); // Load geant4 geometry adapter and save as "global" - CELER_ASSERT(loaded.geo); - geant_geo_ = std::move(loaded.geo); - celeritas::global_geant_geo(geant_geo_); - - // Save built attributes - output_reg_ = params_->output_reg(); - geant_sd_ = std::move(loaded.problem.geant_sd); - step_collector_ = std::move(loaded.problem.step_collector); - - // Translate supported particles - verify_offload( - offload_particles_, *params_->particle(), *params_->physics()); + CELER_ASSERT(loaded_.geo); + celeritas::global_geant_geo(loaded_.geo); // Create bounding box from navigator geometry - bbox_ = geant_geo_->get_clhep_bbox(); - - // Create streams - this->set_num_streams(params_->max_streams()); + bbox_ = loaded_.geo->get_clhep_bbox(); + + std::visit( + Overload{ + [&](setup::ProblemLoaded const& p) { + // Translate supported particles + verify_offload(offload_particles_, + *p.core_params->particle(), + *p.core_params->physics()); + + // Set streams and output registry from core params + output_reg_ = p.core_params->output_reg(); + this->set_num_streams(p.core_params->max_streams()); + }, + [&](setup::OpticalProblemLoaded const& p) { + // Set streams and output registry from optical params + output_reg_ = p.transporter->params()->output_reg(); + this->set_num_streams(p.transporter->params()->max_streams()); + }, + }, + loaded_.problem); // Add timing output - timer_ = std::make_shared(params_->max_streams()); + timer_ = std::make_shared(this->num_streams()); output_reg_->insert(timer_); - if (output_filename_ != "-") + if (loaded_.output_file != "-") { // Write output after params are constructed before anything can go // wrong @@ -384,29 +345,6 @@ SharedParams::SharedParams(SetupOptions const& options) << R"(Skipping 'startup' JSON output since writing to stdout)"; } - if (auto const& offload_file = loaded.problem.offload_file; - !offload_file.empty()) - { - std::unique_ptr writer; - if (ends_with(offload_file, ".jsonl")) - { - writer.reset( - new JsonEventWriter(offload_file, params_->particle())); - } - else if (ends_with(offload_file, ".root")) - { - writer.reset(new RootEventWriter( - std::make_shared(offload_file.c_str()), - params_->particle())); - } - else - { - writer.reset(new EventWriter(offload_file, params_->particle())); - } - offload_writer_ - = std::make_shared(std::move(writer)); - } - CELER_ENSURE(*this); } @@ -526,7 +464,7 @@ void SharedParams::set_num_streams(unsigned int num_streams) */ void SharedParams::try_output() const { - std::string filename = output_filename_; + std::string filename = loaded_.output_file; if (filename.empty()) { CELER_LOG(debug) << "Skipping output: SetupOptions::output_file is " diff --git a/src/accel/SharedParams.hh b/src/accel/SharedParams.hh index 357fd43583..be4d472d29 100644 --- a/src/accel/SharedParams.hh +++ b/src/accel/SharedParams.hh @@ -12,6 +12,7 @@ #include "corecel/Assert.hh" #include "geocel/BoundingBox.hh" +#include "celeritas/setup/FrameworkInput.hh" #include "Types.hh" @@ -21,11 +22,6 @@ class G4VPhysicalVolume; namespace celeritas { //---------------------------------------------------------------------------// -namespace detail -{ -class OffloadWriter; -} // namespace detail - namespace optical { class Transporter; @@ -36,6 +32,7 @@ class CoreParams; class CoreStateInterface; class GeantGeoParams; class GeantSd; +class OffloadWriter; class OpticalCollector; class OutputRegistry; class StepCollector; @@ -50,14 +47,14 @@ struct SetupOptions; * The \c CeleritasDisabled accessor queries the \c CELER_DISABLE environment * variable as a global option for disabling Celeritas offloading. * - * This should be instantiated on the master thread during problem setup, + * This should be instantiated on the main thread during problem setup, * preferably as a shared pointer. The shared pointer should be * passed to a thread-local \c LocalTransporter instance. At the beginning of * the run, after Geant4 has initialized physics data, the \c Initialize method - * must be called first on the "master" thread to populate the Celeritas data + * must be called on the main thread to populate the Celeritas data * structures (geometry, physics). \c InitializeWorker must subsequently be - * invoked on all worker threads to set up thread-local data (specifically, - * CUDA device initialization). + * invoked on all worker threads to set up thread-local data (including + * CUDA device initialization and objects that require Geant4 allocators). * * Some low-level objects, such as the output diagnostics and Geant4 geometry * wrapper, can be created independently of Celeritas being enabled. @@ -102,16 +99,16 @@ class SharedParams // Construct in an uninitialized state SharedParams() = default; - // Construct Celeritas using Geant4 data on the master thread. + // Construct Celeritas using Geant4 data on the main thread. explicit SharedParams(SetupOptions const& options); - // Initialize shared data on the "master" thread + // Initialize shared data on the main thread inline void Initialize(SetupOptions const& options); // On worker threads, set up data with thread storage duration void InitializeWorker(SetupOptions const& options); - // Write (shared) diagnostic output and clear shared data on master + // Write (shared) diagnostic output and clear shared data on main thread void Finalize(); //!@} @@ -136,23 +133,20 @@ class SharedParams using SPActionSequence = std::shared_ptr; using SPGeantSd = std::shared_ptr; - using SPOffloadWriter = std::shared_ptr; + using SPOffloadWriter = std::shared_ptr; using SPOutputRegistry = std::shared_ptr; using SPTimeOutput = std::shared_ptr; using SPState = std::shared_ptr; - using SPOpticalCollector = std::shared_ptr; - using SPOpticalTransporter = std::shared_ptr; - using SPConstGeantGeoParams = std::shared_ptr; using BBox = BoundingBox; //! Initialization status and integration mode Mode mode() const { return mode_; } - // Optical properties (only if using optical physics) - inline SPOpticalCollector const& optical_collector() const; + // Access core Celeritas loaded problem data + inline setup::ProblemLoaded const& problem_loaded() const; - // Optical params (only if using optical physics) - inline SPOpticalTransporter const& optical_transporter() const; + // Access optical Celeritas loaded problem data + inline setup::OpticalProblemLoaded const& optical_problem_loaded() const; // Action sequence inline SPActionSequence const& actions() const; @@ -184,16 +178,8 @@ class SharedParams // Created during initialization Mode mode_{Mode::uninitialized}; - SPConstGeantGeoParams geant_geo_; - std::shared_ptr params_; - SPOpticalCollector optical_collector_; - SPOpticalTransporter optical_transporter_; - SPActionSequence actions_; - std::shared_ptr geant_sd_; - std::shared_ptr step_collector_; + setup::FrameworkLoaded loaded_; VecG4PD offload_particles_; - std::string output_filename_; - SPOffloadWriter offload_writer_; std::vector> states_; SPOutputRegistry output_reg_; SPTimeOutput timer_; @@ -223,8 +209,10 @@ void SharedParams::Initialize(SetupOptions const& options) auto SharedParams::Params() -> SPParams const& { CELER_EXPECT(mode_ == Mode::enabled); - CELER_ENSURE(params_); - return params_; + CELER_EXPECT(std::holds_alternative(loaded_.problem)); + auto const& p = std::get(loaded_.problem); + CELER_ENSURE(p.core_params); + return p.core_params; } //---------------------------------------------------------------------------// @@ -236,8 +224,8 @@ auto SharedParams::Params() -> SPParams const& auto SharedParams::Params() const -> SPConstParams { CELER_EXPECT(mode_ == Mode::enabled); - CELER_ENSURE(params_); - return params_; + CELER_ENSURE(this->problem_loaded().core_params); + return this->problem_loaded().core_params; } //---------------------------------------------------------------------------// @@ -252,22 +240,26 @@ auto SharedParams::OffloadParticles() const -> VecG4PD const& //---------------------------------------------------------------------------// /*! - * Optical transporter: null if Celeritas optical physics is disabled. + * Access Celeritas loaded core problem data. */ -auto SharedParams::optical_transporter() const -> SPOpticalTransporter const& +auto SharedParams::problem_loaded() const -> setup::ProblemLoaded const& { CELER_EXPECT(*this); - return optical_transporter_; + CELER_EXPECT(std::holds_alternative(loaded_.problem)); + return std::get(loaded_.problem); } //---------------------------------------------------------------------------// /*! - * Optical data: null if Celeritas optical physics is disabled. + * Access Celeritas loaded optical problem data. */ -auto SharedParams::optical_collector() const -> SPOpticalCollector const& +auto SharedParams::optical_problem_loaded() const + -> setup::OpticalProblemLoaded const& { CELER_EXPECT(*this); - return optical_collector_; + CELER_EXPECT( + std::holds_alternative(loaded_.problem)); + return std::get(loaded_.problem); } //---------------------------------------------------------------------------// @@ -279,7 +271,7 @@ auto SharedParams::optical_collector() const -> SPOpticalCollector const& auto SharedParams::hit_manager() const -> SPGeantSd const& { CELER_EXPECT(*this); - return geant_sd_; + return this->problem_loaded().geant_sd; } //---------------------------------------------------------------------------// @@ -289,7 +281,7 @@ auto SharedParams::hit_manager() const -> SPGeantSd const& auto SharedParams::actions() const -> SPActionSequence const& { CELER_EXPECT(*this); - return actions_; + return this->problem_loaded().actions; } //---------------------------------------------------------------------------// @@ -299,7 +291,7 @@ auto SharedParams::actions() const -> SPActionSequence const& auto SharedParams::offload_writer() const -> SPOffloadWriter const& { CELER_EXPECT(*this); - return offload_writer_; + return this->problem_loaded().offload_writer; } //---------------------------------------------------------------------------// diff --git a/src/accel/TimeOutput.cc b/src/accel/TimeOutput.cc index b8bfaa9fc3..07a145473b 100644 --- a/src/accel/TimeOutput.cc +++ b/src/accel/TimeOutput.cc @@ -77,7 +77,7 @@ void TimeOutput::RecordEventTime(double time) /*! * Record the time for setting up Celeritas. * - * This should be called once by the master thread. + * This should be called once by the main thread. */ void TimeOutput::RecordSetupTime(double time) { @@ -88,7 +88,7 @@ void TimeOutput::RecordSetupTime(double time) /*! * Record the total time spent in transport and hit I/O (excluding setup). * - * This should be called once by the master thread. + * This should be called once by the main thread. */ void TimeOutput::RecordTotalTime(double time) { diff --git a/src/accel/TrackOffloadInterface.hh b/src/accel/TrackOffloadInterface.hh new file mode 100644 index 0000000000..2ced703342 --- /dev/null +++ b/src/accel/TrackOffloadInterface.hh @@ -0,0 +1,32 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file accel/TrackOffloadInterface.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "LocalOffloadInterface.hh" + +class G4Track; + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Interface for offloading complete Geant4 tracks to Celeritas. + * It allows the Geant4 tracking manager to forward full + * track to Celeritas, such as EM or optical track transport. + */ +class TrackOffloadInterface : public LocalOffloadInterface +{ + public: + // Construct with defaults + ~TrackOffloadInterface() override = default; + + // Push a full Geant4 track to Celeritas + virtual void Push(G4Track&) = 0; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/accel/TrackingManager.cc b/src/accel/TrackingManager.cc index 9ce9b86949..6ca3125ac7 100644 --- a/src/accel/TrackingManager.cc +++ b/src/accel/TrackingManager.cc @@ -16,6 +16,7 @@ #include "ExceptionConverter.hh" #include "LocalTransporter.hh" #include "SharedParams.hh" +#include "TrackOffloadInterface.hh" namespace celeritas { @@ -24,14 +25,17 @@ namespace celeritas * Construct a tracking manager with data needed to offload to Celeritas. * * \note The shared/local pointers must remain valid for the lifetime of the + * run. The local transporter should be null on the "master" thread of an MT * run. */ TrackingManager::TrackingManager(SharedParams const* params, - LocalTransporter* local) + TrackOffloadInterface* local) : params_(params), transport_(local) { CELER_EXPECT(params_); - CELER_EXPECT(transport_); + CELER_EXPECT(static_cast(transport_) + == !(G4Threading::IsMasterThread() + && G4Threading::IsMultithreadedApplication())); } //---------------------------------------------------------------------------// @@ -51,11 +55,11 @@ TrackingManager::TrackingManager(SharedParams const* params, */ void TrackingManager::BuildPhysicsTable(G4ParticleDefinition const& part) { - CELER_LOG(debug) << "Building physics table for " << part.GetParticleName(); + CELER_EXPECT(params_->mode() != SharedParams::Mode::disabled); + + CELER_LOG_LOCAL(debug) << "Building physics table for " + << part.GetParticleName(); - CELER_VALIDATE(params_->mode() != SharedParams::Mode::disabled, - << "Celeritas tracking manager cannot be active when " - "Celeritas is disabled"); G4ProcessManager* pManagerShadow = part.GetMasterProcessManager(); G4ProcessManager* pManager = part.GetProcessManager(); CELER_ASSERT(pManager); @@ -82,7 +86,7 @@ void TrackingManager::BuildPhysicsTable(G4ParticleDefinition const& part) * * Messaged by the \c G4ParticleDefinition who stores us whenever cross-section * tables have to be rebuilt (i.e. if new materials have been defined). As with - * BuildPhysicsTable, we override this to ensure all Geant4 + * \c BuildPhysicsTable, we override this to ensure all Geant4 * process/cross-section data is available for Celeritas to use. * * The implementation follows that in \c @@ -91,8 +95,10 @@ void TrackingManager::BuildPhysicsTable(G4ParticleDefinition const& part) */ void TrackingManager::PreparePhysicsTable(G4ParticleDefinition const& part) { - CELER_LOG(debug) << "Preparing physics table for " - << part.GetParticleName(); + CELER_EXPECT(params_->mode() != SharedParams::Mode::disabled); + + CELER_LOG_LOCAL(debug) << "Preparing physics table for " + << part.GetParticleName(); G4ProcessManager* pManagerShadow = part.GetMasterProcessManager(); G4ProcessManager* pManager = part.GetProcessManager(); @@ -117,10 +123,13 @@ void TrackingManager::PreparePhysicsTable(G4ParticleDefinition const& part) //---------------------------------------------------------------------------// /*! * Offload the incoming track to Celeritas. + * + * This will \em not be called in the "master" thread of an MT run. */ void TrackingManager::HandOverOneTrack(G4Track* track) { CELER_EXPECT(track); + CELER_EXPECT(transport_); if (CELER_UNLIKELY(!validated_)) { diff --git a/src/accel/TrackingManager.hh b/src/accel/TrackingManager.hh index 6eba5170dc..8f7162bccf 100644 --- a/src/accel/TrackingManager.hh +++ b/src/accel/TrackingManager.hh @@ -18,29 +18,36 @@ namespace celeritas //---------------------------------------------------------------------------// class SharedParams; -class LocalTransporter; +class TrackOffloadInterface; //---------------------------------------------------------------------------// /*! * Offload to Celeritas via the per-particle Geant4 "tracking manager". * - * Tracking managers are to be created during worker action initialization and - * are thus thread-local. Construction/addition to \c G4ParticleDefinition - * appears to take place on the master thread, typically - * in the ConstructProcess method, but the tracking manager pointer is part of - * the split-class data for the particle. It's observed that different threads - * have distinct pointers to a LocalTransporter instance, and that these match - * those of the global thread-local instances in test problems. + * Tracking managers are created by \c G4VUserPhysicsList::Construct during + * \c G4RunManager::Initialize on each thread. The tracking manager pointer is + * a \em thread-local part of the split-class data for a \em global G4Particle. + * This thread-local manager points to a corresponding thread-local + * transporter. + * + * The \c TrackingManagerConstructor class creates an instance of this class + * for every worker thread (or the main thread when using a serial run + * manager.) * * \note As of Geant4 11.3, instances of this class (one per thread) will never * be deleted. + * + * \warning The physics does \em not reconstruct tracking managers on + * subsequent runs. Therefore the \c SharedParams and \c LocalTransporter \em + * must have lifetimes that span multiple runs (which is the case for using + * global/thread-local). */ class TrackingManager final : public G4VTrackingManager { public: // Construct with shared (across threads) params, and thread-local // transporter. - TrackingManager(SharedParams const* params, LocalTransporter* local); + TrackingManager(SharedParams const* params, TrackOffloadInterface* local); // Prepare cross-section tables for rebuild (e.g. if new materials have // been defined). @@ -62,12 +69,12 @@ class TrackingManager final : public G4VTrackingManager SharedParams const* shared_params() const { return params_; } //! Get the thread-local transporter - LocalTransporter* local_transporter() const { return transport_; } + TrackOffloadInterface* local_transporter() const { return transport_; } private: bool validated_{false}; SharedParams const* params_{nullptr}; - LocalTransporter* transport_{nullptr}; + TrackOffloadInterface* transport_{nullptr}; }; //---------------------------------------------------------------------------// diff --git a/src/accel/TrackingManagerConstructor.cc b/src/accel/TrackingManagerConstructor.cc index e589115347..8d6337fbe0 100644 --- a/src/accel/TrackingManagerConstructor.cc +++ b/src/accel/TrackingManagerConstructor.cc @@ -49,13 +49,21 @@ TrackingManagerConstructor::TrackingManagerConstructor( * * Since there's only ever one tracking manager integration, we can just use * the behind-the-hood objects. + * + * \note When calling from a serial run manager in a threaded G4 build, the + * thread ID is \c G4Threading::MASTER_ID (-1). When calling from the run + * manager of a non-threaded G4 build, the thread is \c + * G4Threading::SEQUENTIAL_ID (-2). */ TrackingManagerConstructor::TrackingManagerConstructor( TrackingManagerIntegration* tmi) : TrackingManagerConstructor( - &detail::IntegrationSingleton::instance().shared_params(), [](int) { + &detail::IntegrationSingleton::instance().shared_params(), + [](int tid) { + CELER_EXPECT(tid >= 0 + || !G4Threading::IsMultithreadedApplication()); return &detail::IntegrationSingleton::instance() - .local_transporter(); + .local_track_offload(); }) { CELER_EXPECT(tmi == &TrackingManagerIntegration::Instance()); @@ -100,8 +108,15 @@ void TrackingManagerConstructor::ConstructProcess() shared_ && get_local_, << R"(invalid null inputs given to TrackingManagerConstructor)"); - auto* transporter = this->get_local_transporter(); - CELER_VALIDATE(transporter, << "invalid null local transporter"); + TrackOffloadInterface* transporter{nullptr}; + + if (G4Threading::IsWorkerThread() + || !G4Threading::IsMultithreadedApplication()) + { + // Don't create or access local transporter on master thread + transporter = this->get_local_(G4Threading::G4GetThreadId()); + CELER_VALIDATE(transporter, << "invalid null local transporter"); + } #if G4VERSION_NUMBER >= 1100 // Create *thread-local* tracking manager with pointers to *global* @@ -134,7 +149,7 @@ void TrackingManagerConstructor::ConstructProcess() /*! * Get the local transporter associated with the current thread ID. */ -LocalTransporter* TrackingManagerConstructor::get_local_transporter() const +TrackOffloadInterface* TrackingManagerConstructor::get_local_transporter() const { CELER_EXPECT(get_local_); return this->get_local_(G4Threading::G4GetThreadId()); diff --git a/src/accel/TrackingManagerConstructor.hh b/src/accel/TrackingManagerConstructor.hh index 0da6799f04..cd6ac1d801 100644 --- a/src/accel/TrackingManagerConstructor.hh +++ b/src/accel/TrackingManagerConstructor.hh @@ -7,16 +7,17 @@ #pragma once #include -#include #include #include "corecel/cont/Span.hh" +#include "accel/SetupOptions.hh" +#include "accel/TrackOffloadInterface.hh" #include "detail/IntegrationSingleton.hh" namespace celeritas { -class LocalTransporter; +class TrackOffloadInterface; class SharedParams; class TrackingManagerIntegration; @@ -33,17 +34,21 @@ class TrackingManagerIntegration; &TrackingManagerIntegration::Instance()}); \endcode * - * but for manual integration it can be constructed with a function to get a - * reference to the thread-local \c LocalTransporter from the Geant4 thread ID: + * but for manual integration it can be constructed with a function to get + a + * reference to the thread-local \c LocalTransporter from the Geant4 thread + ID: * \code auto* physics_list = new FTFP_BERT; physics_list->RegisterPhysics(new TrackingManagerConstructor{ shared_params, [](int){ return &local_transporter; }); \endcode * - * \note If Celeritas is globally disabled, it will not add the track manager. + * \note If Celeritas is globally disabled, it will not add the track + manager. * If Celeritas is configured to "kill offload" mode (for testing maximum - * theoretical performance) then the tracking manager will be added but will + * theoretical performance) then the tracking manager will be added but + will * not send the tracks to Celeritas: it will simply kill them. */ class TrackingManagerConstructor final : public G4VPhysicsConstructor @@ -51,7 +56,8 @@ class TrackingManagerConstructor final : public G4VPhysicsConstructor public: //!@{ //! \name Type aliases - using LocalTransporterFromThread = std::function; + using LocalTransporterFromThread + = std::function; using VecG4PD = SetupOptions::VecG4PD; //!@} @@ -74,8 +80,8 @@ class TrackingManagerConstructor final : public G4VPhysicsConstructor //! Get the shared params associated with this TM SharedParams const* shared_params() const { return shared_; } - // Get the local transporter associated with the current thread ID - LocalTransporter* get_local_transporter() const; + // Get the track transporter associated with the current thread ID + TrackOffloadInterface* get_local_transporter() const; private: SharedParams const* shared_{nullptr}; diff --git a/src/accel/TrackingManagerIntegration.cc b/src/accel/TrackingManagerIntegration.cc index f5b3fa65ba..8d0a66a8e0 100644 --- a/src/accel/TrackingManagerIntegration.cc +++ b/src/accel/TrackingManagerIntegration.cc @@ -6,7 +6,6 @@ //---------------------------------------------------------------------------// #include "TrackingManagerIntegration.hh" -#include #include #include #include @@ -19,12 +18,10 @@ #endif #include "corecel/io/Join.hh" -#include "corecel/sys/Stopwatch.hh" #include "corecel/sys/TypeDemangler.hh" #include "geocel/GeantUtils.hh" #include "ExceptionConverter.hh" -#include "TimeOutput.hh" #include "TrackingManagerConstructor.hh" #include "detail/IntegrationSingleton.hh" @@ -151,50 +148,30 @@ TrackingManagerIntegration& TrackingManagerIntegration::Instance() //---------------------------------------------------------------------------// /*! - * Start the run, initializing Celeritas options. + * Verify tracking manager setup. */ -void TrackingManagerIntegration::BeginOfRunAction(G4Run const*) +void TrackingManagerIntegration::verify_local_setup() { CELER_VALIDATE(G4VERSION_NUMBER >= 1100, << "the current version of Geant4 (" << G4VERSION_NUMBER << ") is too old to support the tracking manager offload " "interface (11.0 or higher is required)"); - Stopwatch get_setup_time; - auto& singleton = detail::IntegrationSingleton::instance(); - if (G4Threading::IsMasterThread()) - { - singleton.initialize_shared_params(); - } - - bool enable_offload = singleton.initialize_local_transporter(); - - if (enable_offload) - { - // Set particle offloading based on user options - auto const& user_offload = singleton.setup_options().offload_particles; - auto const& offload_particles - = user_offload ? *user_offload - : SharedParams::default_offload_particles(); - - // Set tracking manager on workers when Celeritas is not fully disabled - CELER_LOG(debug) << "Verifying tracking manager"; - CELER_TRY_HANDLE( - verify_tracking_managers( - make_span(singleton.shared_params().OffloadParticles()), - make_span(offload_particles), - singleton.shared_params(), - singleton.local_offload()), - ExceptionConverter{"celer.init.verify"}); - } - - if (G4Threading::IsMasterThread()) - { - singleton.shared_params().timer()->RecordSetupTime(get_setup_time()); - singleton.start_timer(); - } + // Set particle offloading based on user options + auto const& user_offload = singleton.setup_options().offload_particles; + auto const& offload_particles + = user_offload ? *user_offload + : SharedParams::default_offload_particles(); + + // Set tracking manager on workers when Celeritas is not fully disabled + CELER_LOG(debug) << "Verifying tracking managers"; + verify_tracking_managers( + make_span(singleton.shared_params().OffloadParticles()), + make_span(offload_particles), + singleton.shared_params(), + singleton.local_offload()); } //---------------------------------------------------------------------------// diff --git a/src/accel/TrackingManagerIntegration.hh b/src/accel/TrackingManagerIntegration.hh index 8a4bde3700..e2aade6265 100644 --- a/src/accel/TrackingManagerIntegration.hh +++ b/src/accel/TrackingManagerIntegration.hh @@ -6,8 +6,6 @@ //---------------------------------------------------------------------------// #pragma once -#include - #include "IntegrationBase.hh" namespace celeritas @@ -26,13 +24,7 @@ namespace celeritas * usually in \c main for simple applications. * - Call \c BeginOfRunAction and \c EndOfRunAction from \c UserRunAction * - * The \c CELER_DISABLE environment variable, if set and non-empty, will - * disable offloading so that Celeritas will not be built nor kill tracks. - * - * The method names correspond to methods in Geant4 User Actions and \em must - * be called from all threads, both worker and master. - * - * \todo Provide default minimal action initialization classes for user + * See further documentation in \c celeritas::IntegrationBase. */ class TrackingManagerIntegration final : public IntegrationBase { @@ -40,12 +32,12 @@ class TrackingManagerIntegration final : public IntegrationBase // Access the public-facing integration singleton static TrackingManagerIntegration& Instance(); - // Start the run - void BeginOfRunAction(G4Run const* run) final; - private: // Tracking manager can only be created privately TrackingManagerIntegration(); + + // Verify tracking manager setup + void verify_local_setup() final; }; //---------------------------------------------------------------------------// diff --git a/src/accel/UserActionIntegration.cc b/src/accel/UserActionIntegration.cc index e83cd7fd93..67e5b1adcd 100644 --- a/src/accel/UserActionIntegration.cc +++ b/src/accel/UserActionIntegration.cc @@ -7,16 +7,17 @@ #include "UserActionIntegration.hh" #include +#include #include #include #include "corecel/sys/Stopwatch.hh" #include "ExceptionConverter.hh" -#include "TimeOutput.hh" +#include "G4OpticalPhoton.hh" +#include "TimeOutput.hh" // IWYU pragma: keep #include "detail/IntegrationSingleton.hh" - namespace celeritas { //---------------------------------------------------------------------------// @@ -29,30 +30,6 @@ UserActionIntegration& UserActionIntegration::Instance() return uai; } -//---------------------------------------------------------------------------// -/*! - * Start the run. - */ -void UserActionIntegration::BeginOfRunAction(G4Run const*) -{ - Stopwatch get_setup_time; - - auto& singleton = detail::IntegrationSingleton::instance(); - - if (G4Threading::IsMasterThread()) - { - singleton.initialize_shared_params(); - } - - singleton.initialize_local_transporter(); - - if (G4Threading::IsMasterThread()) - { - singleton.shared_params().timer()->RecordSetupTime(get_setup_time()); - singleton.start_timer(); - } -} - //---------------------------------------------------------------------------// /*! * Send Celeritas the event ID. @@ -83,6 +60,19 @@ void UserActionIntegration::PreUserTrackingAction(G4Track* track) if (mode == SharedParams::Mode::disabled) return; + // Optical track offload path + if (track->GetDefinition() == G4OpticalPhoton::Definition()) + { + CELER_LOG(debug) << "Entering optical offload in user action"; + auto& opt_local + = detail::IntegrationSingleton::local_optical_track_offload(); + if (opt_local) + { + opt_local.Push(*track); + track->SetTrackStatus(fStopAndKill); + return; + } + } auto const& particles = singleton.shared_params().OffloadParticles(); if (std::find(particles.begin(), particles.end(), track->GetDefinition()) != particles.end()) @@ -98,6 +88,21 @@ void UserActionIntegration::PreUserTrackingAction(G4Track* track) // Either "pushed" or we're in kill_offload mode track->SetTrackStatus(fStopAndKill); + return; + } + if (track->GetDefinition() == G4OpticalPhoton::Definition()) + { + if (mode == SharedParams::Mode::enabled) + { + // Celeritas is transporting this optical photon + CELER_TRY_HANDLE(detail::IntegrationSingleton::instance() + .local_track_offload() + .Push(*track), + ExceptionConverter("celer.track.push", + +&singleton.shared_params())); + } + // Kill the optical photon (offloaded or not) + track->SetTrackStatus(fStopAndKill); } } @@ -116,7 +121,12 @@ void UserActionIntegration::EndOfEventAction(G4Event const*) CELER_TRY_HANDLE( local.Flush(), ExceptionConverter("celer.event.flush", &singleton.shared_params())); - + auto& opt = detail::IntegrationSingleton::local_optical_track_offload(); + if (opt && opt.GetBufferSize() > 0) + { + CELER_LOG(info) << "EOE flushing optical tracks"; + opt.Flush(); + } // Record the time for this event singleton.shared_params().timer()->RecordEventTime(get_event_time_()); } @@ -127,5 +137,11 @@ void UserActionIntegration::EndOfEventAction(G4Event const*) */ UserActionIntegration::UserActionIntegration() = default; +//---------------------------------------------------------------------------// +/*! + * No verification is performed by the user action. + */ +void UserActionIntegration::verify_local_setup() {} + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/accel/UserActionIntegration.hh b/src/accel/UserActionIntegration.hh index 16ba4a9261..f1aa7b985f 100644 --- a/src/accel/UserActionIntegration.hh +++ b/src/accel/UserActionIntegration.hh @@ -29,20 +29,18 @@ struct SetupOptions; * * - Use \c SetOptions to set Celeritas configuration before calling \c * G4RunManager::BeamOn - * - Call \c BeginOfRunAction and \c EndOfRunAction from \c UserRunAction + * - Call \c BeginOfRunAction and \c EndOfRunAction (in \c IntegrationBase) + * from \c UserRunAction * - Call \c BeginOfEvent and \c EndOfEvent from \c UserEventAction * - Call \c PreUserTrackingAction from your \c UserTrackingAction * - * The \c CELER_DISABLE environment variable, if set and non-empty, will - * disable offloading so that Celeritas will not be built nor kill tracks. - * * The method names correspond to methods in Geant4 User Actions and \em must * be called from all threads, both worker and master. * - * \note Prefer to use \c celeritas::TrackingManagerIntegration instead of this - * class, unless you need support for Geant4 earlier than 11.1. + * See further documentation in \c celeritas::IntegrationBase. * - * \todo Provide default minimal action initialization classes for user? + * \note Prefer to use \c celeritas::TrackingManagerIntegration instead of this + * class, unless you need support for Geant4 earlier than 11.0. */ class UserActionIntegration final : public IntegrationBase { @@ -50,9 +48,6 @@ class UserActionIntegration final : public IntegrationBase // Access the singleton static UserActionIntegration& Instance(); - // Start the run - void BeginOfRunAction(G4Run const* run) final; - // Send Celeritas the event ID void BeginOfEventAction(G4Event const* event); @@ -67,6 +62,9 @@ class UserActionIntegration final : public IntegrationBase UserActionIntegration(); Stopwatch get_event_time_; + + // Verify setup after initialization (called if offload is enabled) + void verify_local_setup() final; }; //---------------------------------------------------------------------------// diff --git a/src/accel/detail/IntegrationSingleton.cc b/src/accel/detail/IntegrationSingleton.cc index 69b357e17c..50d2bf8262 100644 --- a/src/accel/detail/IntegrationSingleton.cc +++ b/src/accel/detail/IntegrationSingleton.cc @@ -6,6 +6,7 @@ //---------------------------------------------------------------------------// #include "IntegrationSingleton.hh" +#include #include #include @@ -14,6 +15,7 @@ #include "corecel/io/Logger.hh" #include "corecel/sys/ScopedMpiInit.hh" #include "geocel/GeantUtils.hh" +#include "accel/LocalOpticalTrackOffload.hh" #include "LoggerImpl.hh" #include "../ExceptionConverter.hh" @@ -78,36 +80,51 @@ IntegrationSingleton& IntegrationSingleton::instance() /*! * Static THREAD-LOCAL Celeritas state data. */ -LocalTransporter& IntegrationSingleton::local_transporter() +TrackOffloadInterface& IntegrationSingleton::local_track_offload() { - auto& offload = IntegrationSingleton::local_offload_ptr(); - if (!offload) + if (this->optical_track_offload()) { - offload = std::make_unique(); + return IntegrationSingleton::local_optical_track_offload(); } - auto* lt = dynamic_cast(offload.get()); - CELER_VALIDATE(lt, - << "Cannot access LocalTransporter when " - "LocalOpticalOffload is being used"); - return *lt; + return IntegrationSingleton::local_transporter(); } -//---------------------------------------------------------------------------// -/*! - * Static THREAD-LOCAL Celeritas optical state data. - */ -LocalOpticalOffload& IntegrationSingleton::local_optical_offload() +LocalTransporter& IntegrationSingleton::local_transporter() { - auto& offload = IntegrationSingleton::local_offload_ptr(); - if (!offload) + static G4ThreadLocal UPOffload offload; + + if (CELER_UNLIKELY(!offload)) { - offload = std::make_unique(); + if (!options_) + { + // Cannot construct offload before options are set + CELER_LOG_LOCAL(error) + << R"(cannot access offload before options are set)"; + } + if (options_.optical + && std::holds_alternative( + options_.optical->generator)) + { + CELER_LOG(info) << "optical gen offloading enabled"; + offload = std::make_unique(); + } + else if (options_.optical + && std::holds_alternative( + options_.optical->generator)) + + { + CELER_LOG(info) << "optical track offloading enabled"; + offload = std::make_unique(); + } + else + { + // TODO: if offloading direct optical tracks, return optical + // offload + offload = std::make_unique(); + } } - auto* lt = dynamic_cast(offload.get()); - CELER_VALIDATE(lt, - << "Cannot access LocalOpticalOffload when " - "LocalTransporter is being used"); - return *lt; + + return *offload; } //---------------------------------------------------------------------------// @@ -116,11 +133,23 @@ LocalOpticalOffload& IntegrationSingleton::local_optical_offload() */ LocalOffloadInterface& IntegrationSingleton::local_offload() { - if (this->optical_offload()) + CELER_VALIDATE( + !(this->optical_track_offload() && this->optical_offload()), + << R"(Cannot enable both optical generator offload and optical track offload at the same time)"); + + if (this->optical_track_offload()) + { + return IntegrationSingleton::local_optical_track_offload(); + } + + else if (this->optical_offload()) { return IntegrationSingleton::local_optical_offload(); } - return IntegrationSingleton::local_transporter(); + else + { + return IntegrationSingleton::local_transporter(); + } } //---------------------------------------------------------------------------// @@ -149,7 +178,8 @@ void IntegrationSingleton::setup_options(SetupOptions&& opts) << R"(SetOptions called with incomplete input: you must use the UI to update before /run/initialize)"; } - CELER_ENSURE(!offloaded_.empty() || this->optical_offload()); + CELER_ENSURE(!offloaded_.empty() || this->optical_offload() + || this->optical_track_offload()); } //---------------------------------------------------------------------------// @@ -169,70 +199,41 @@ OffloadMode IntegrationSingleton::mode() const //---------------------------------------------------------------------------// /*! - * Construct shared params on master (or single) thread. + * Initialize shared params and thread-local transporter. * - * \todo The query for CeleritasDisabled initializes the environment before - * we've had a chance to load the user setup options. Make sure we can update - * the environment *first* when refactoring the setup. + * This handles both global (master thread) and local (worker thread) + * initialization. In MT mode, the master thread initializes shared params, + * and worker threads initialize their local state. * - * \note In Geant4 threading, \em only MT mode on non-master thread has - * \c G4Threading::IsWorkerThread()==true. For MT mode, the master thread - * does not track any particles. For single-thread mode, the master thread - * \em does do work. + * \return Whether Celeritas offloading is enabled */ -void IntegrationSingleton::initialize_shared_params() +bool IntegrationSingleton::initialize_offload() { - ExceptionConverter call_g4exception{"celer.init.global"}; - if (G4Threading::IsMasterThread()) { - CELER_LOG(debug) << "Initializing shared params"; - CELER_TRY_HANDLE( - { - CELER_VALIDATE( - options_, - << R"(SetOptions or UI entries were not completely set before BeginRun)"); - CELER_VALIDATE( - !params_, - << R"(BeginOfRunAction cannot be called more than once)"); - - // Update logger in case run manager has changed number of - // threads, or user called initialization after run manager - this->update_logger(); - - params_.Initialize(options_); - }, - call_g4exception); + failed_setup_ = false; + + ExceptionConverter call_g4exception{"celer.init.global"}; + CELER_TRY_HANDLE(this->initialize_master_impl(), call_g4exception); + failed_setup_ = call_g4exception.forwarded(); + + // Start the run timer + get_time_ = {}; } - else + else if (!failed_setup_) { - CELER_LOG(debug) << "Initializing worker"; - CELER_TRY_HANDLE( - { - CELER_ASSERT(G4Threading::IsMultithreadedApplication()); - CELER_VALIDATE( - params_, - << R"(BeginOfRunAction was not called on master thread)"); - params_.InitializeWorker(options_); - }, - call_g4exception); + CELER_TRY_HANDLE(this->initialize_worker_impl(), + ExceptionConverter{"celer.init.worker"}); } + CELER_ASSERT(params_ || failed_setup_); - CELER_ENSURE(params_); -} - -//---------------------------------------------------------------------------// -/*! - * Construct thread-local transporter. - * - * Note that this uses the thread-local static data. It *must not* be called - * from the master thread in a multithreaded run. - * - * \return Whether Celeritas offloading is enabled - */ -bool IntegrationSingleton::initialize_local_transporter() -{ - CELER_EXPECT(params_); + // Now initialize local transporter + if (CELER_UNLIKELY(failed_setup_)) + { + CELER_LOG_LOCAL(debug) + << R"(Skipping local initialization due to failure)"; + return false; + } if (params_.mode() == OffloadMode::disabled) { @@ -244,13 +245,10 @@ bool IntegrationSingleton::initialize_local_transporter() if (G4Threading::IsMultithreadedApplication() && G4Threading::IsMasterThread()) { - // Cannot construct local transporter on master MT thread + // Do not construct local transporter on master MT thread return false; } - CELER_ASSERT(!G4Threading::IsMultithreadedApplication() - || G4Threading::IsWorkerThread()); - if (params_.mode() == OffloadMode::kill_offload) { // When "kill offload", we still need to intercept tracks @@ -259,70 +257,40 @@ bool IntegrationSingleton::initialize_local_transporter() return true; } - CELER_LOG(debug) << "Constructing local state"; - - CELER_TRY_HANDLE( - { - auto& lt = this->local_offload(); - CELER_VALIDATE(!lt, - << "local thread " - << G4Threading::G4GetThreadId() + 1 - << " cannot be initialized more than once"); - lt.Initialize(options_, params_); - }, - ExceptionConverter("celer.init.local")); + CELER_TRY_HANDLE(this->initialize_local_impl(), + ExceptionConverter("celer.init.local")); return true; } //---------------------------------------------------------------------------// /*! - * Destroy local transporter. + * Finalize thread-local transporter and (if main thread) shared params. */ -void IntegrationSingleton::finalize_local_transporter() +void IntegrationSingleton::finalize_offload() { - CELER_EXPECT(params_); - - if (params_.mode() != OffloadMode::enabled) + if (CELER_UNLIKELY(failed_setup_)) { return; } + CELER_EXPECT(params_); - if (G4Threading::IsMultithreadedApplication() - && G4Threading::IsMasterThread()) + // Finalize local transporter + if (params_.mode() == OffloadMode::enabled) { - // Cannot destroy local transporter on master MT thread - return; - } - - CELER_LOG(debug) << "Destroying local state"; - - CELER_TRY_HANDLE( + if (!G4Threading::IsMultithreadedApplication() + || !G4Threading::IsMasterThread()) { - auto& lt = this->local_offload(); - CELER_VALIDATE(lt, - << "local thread " - << G4Threading::G4GetThreadId() + 1 - << " cannot be finalized more than once"); - params_.timer()->RecordActionTime(lt.GetActionTime()); - lt.Finalize(); - }, - ExceptionConverter("celer.finalize.local")); -} + CELER_TRY_HANDLE(this->finalize_local_impl(), + ExceptionConverter("celer.finalize.local")); + } + } -//---------------------------------------------------------------------------// -/*! - * Destroy params. - */ -void IntegrationSingleton::finalize_shared_params() -{ - CELER_LOG(status) << "Finalizing Celeritas"; - CELER_TRY_HANDLE( - { - CELER_VALIDATE(params_, - << "params cannot be finalized more than once"); - params_.Finalize(); - }, - ExceptionConverter("celer.finalize.global")); + // Finalize shared params on main thread + if (G4Threading::IsMasterThread()) + { + CELER_TRY_HANDLE(this->finalize_shared_impl(), + ExceptionConverter("celer.finalize.global")); + } } //---------------------------------------------------------------------------// @@ -355,7 +323,7 @@ auto IntegrationSingleton::local_offload_ptr() -> UPOffload& //---------------------------------------------------------------------------// /*! - * Whether the local optical offload is used. + * Whether the local optical generator offload is used. */ bool IntegrationSingleton::optical_offload() const { @@ -364,6 +332,11 @@ bool IntegrationSingleton::optical_offload() const options_.optical->generator); } +bool IntegrationSingleton::optical_track_offload() const +{ + return options_.optical && options_.optical->offload_tracks; +} + //---------------------------------------------------------------------------// /*! * Create or update the number of threads for the logger. @@ -377,19 +350,106 @@ void IntegrationSingleton::update_logger() celeritas::world_logger() = celeritas::MakeMTWorldLogger(*run_man); celeritas::self_logger() = celeritas::MakeMTSelfLogger(*run_man); have_created_logger_ = true; + CELER_LOG(debug) << "Celeritas logging redirected through Geant4"; } else { - if (auto* handle - = celeritas::world_logger().handle().target()) + if (celeritas::world_logger().handle().target()) { - // Update thread count - *handle = MtSelfWriter{get_geant_num_threads(*run_man)}; + // Update thread count by replacing log handle + celeritas::world_logger().handle( + MtSelfWriter{get_geant_num_threads(*run_man)}); } } } } +//---------------------------------------------------------------------------// +/*! + * Initialize shared params implementation. + */ +void IntegrationSingleton::initialize_master_impl() +{ + CELER_EXPECT(G4Threading::IsMasterThread()); + + CELER_LOG(debug) << "Initializing shared params"; + CELER_VALIDATE( + options_, + << R"(SetOptions or UI entries were not completely set before BeginRun)"); + CELER_VALIDATE(!params_, + << R"(BeginOfRunAction cannot be called more than once)"); + + Stopwatch get_setup_time; + + // Update logger in case run manager has changed number of + // threads, or user called initialization after run manager + this->update_logger(); + + // Perform initialization + params_.Initialize(options_); + + // Record the setup time after initialization + params_.timer()->RecordSetupTime(get_setup_time()); +} + +//---------------------------------------------------------------------------// +/*! + * Initialize worker thread implementation. + */ +void IntegrationSingleton::initialize_worker_impl() +{ + CELER_EXPECT(G4Threading::IsMultithreadedApplication()); + + CELER_LOG(debug) << "Initializing worker"; + CELER_VALIDATE(params_, + << R"(BeginOfRunAction was not called on master thread)"); + params_.InitializeWorker(options_); +} + +//---------------------------------------------------------------------------// +/*! + * Initialize local transporter implementation. + */ +void IntegrationSingleton::initialize_local_impl() +{ + CELER_EXPECT(!G4Threading::IsMultithreadedApplication() + || G4Threading::IsWorkerThread()); + + CELER_LOG(debug) << "Constructing local state"; + auto& lt = this->local_offload(); + CELER_VALIDATE(!lt, + << "local thread " << G4Threading::G4GetThreadId() + 1 + << " cannot be initialized more than once"); + lt.Initialize(options_, params_); +} + +//---------------------------------------------------------------------------// +/*! + * Finalize local transporter implementation. + */ +void IntegrationSingleton::finalize_local_impl() +{ + CELER_LOG(debug) << "Destroying local state"; + auto& lt = this->local_offload(); + CELER_VALIDATE(lt, + << "local thread " << G4Threading::G4GetThreadId() + 1 + << " cannot be finalized more than once"); + params_.timer()->RecordActionTime(lt.GetActionTime()); + lt.Finalize(); +} + +//---------------------------------------------------------------------------// +/*! + * Finalize shared params implementation. + */ +void IntegrationSingleton::finalize_shared_impl() +{ + CELER_LOG(status) << "Finalizing Celeritas"; + CELER_VALIDATE(params_, << "params cannot be finalized more than once"); + params_.timer()->RecordTotalTime(get_time_()); + params_.Finalize(); +} + //---------------------------------------------------------------------------// } // namespace detail } // namespace celeritas diff --git a/src/accel/detail/IntegrationSingleton.hh b/src/accel/detail/IntegrationSingleton.hh index 41e29f0a68..debda2ca1f 100644 --- a/src/accel/detail/IntegrationSingleton.hh +++ b/src/accel/detail/IntegrationSingleton.hh @@ -9,8 +9,10 @@ #include #include "corecel/sys/Stopwatch.hh" +#include "accel/LocalOpticalTrackOffload.hh" -#include "../LocalOpticalOffload.hh" +#include "../LocalOpticalGenOffload.hh" +#include "../LocalOpticalTrackOffload.hh" #include "../LocalTransporter.hh" #include "../SetupOptions.hh" #include "../SharedParams.hh" @@ -52,8 +54,16 @@ class IntegrationSingleton static LocalTransporter& local_transporter(); // Static THREAD-LOCAL Celeritas optical state data - static LocalOpticalOffload& local_optical_offload(); + static LocalOpticalGenOffload& local_optical_offload(); + // Static Thread-local Celeritas optical track offload + static LocalOpticalTrackOffload& local_optical_track_offload(); + + // Static Thread-local Celeritas optical track offload + static LocalOpticalTrackOffload& local_optical_track_offload(); + + // Access thread-local track offload interface + TrackOffloadInterface& local_track_offload(); //// ACCESSORS //// // Access the thread-local offload interface @@ -79,23 +89,11 @@ class IntegrationSingleton //// HELPERS //// - // Construct shared params on master (or single) thread - void initialize_shared_params(); - - // Construct thread-local transporter - bool initialize_local_transporter(); - - // Destroy local transporter - void finalize_local_transporter(); + // Initialize shared params and thread-local transporter + bool initialize_offload(); - // Destroy params - void finalize_shared_params(); - - // Start the transport timer [s] - void start_timer() { get_time_ = {}; } - - // Stop the timer and return the elapsed time [s] - real_type stop_timer() { return get_time_(); } + // Destroy local transporter and shared params + void finalize_offload(); private: //// TYPES //// @@ -110,6 +108,7 @@ class IntegrationSingleton std::unique_ptr messenger_; Stopwatch get_time_; bool have_created_logger_{false}; + bool failed_setup_{false}; //// PRIVATE MEMBER FUNCTIONS //// @@ -122,8 +121,23 @@ class IntegrationSingleton // Whether offloading optical distribution data is enabled bool optical_offload() const; + // Whether offloading optical track is enabled + bool optical_track_offload() const; + // Set up or update logging if the run manager is enabled void update_logger(); + + // Initialize shared params implementation + void initialize_master_impl(); + // Initialize worker thread implementation + void initialize_worker_impl(); + // Initialize local transporter implementation + void initialize_local_impl(); + + // Finalize local transporter implementation + void finalize_local_impl(); + // Finalize shared params implementation + void finalize_shared_impl(); }; //---------------------------------------------------------------------------// diff --git a/src/accel/detail/LoggerImpl.cc b/src/accel/detail/LoggerImpl.cc index 71dea46ab9..761c574be6 100644 --- a/src/accel/detail/LoggerImpl.cc +++ b/src/accel/detail/LoggerImpl.cc @@ -69,7 +69,7 @@ std::ostream& operator<<(std::ostream& os, CleanedProvenance ssd) */ std::ostream& operator<<(std::ostream& os, ColorfulLogMessage clm) { - os << to_color_code(clm.lev) << to_cstring(clm.lev); + os << to_ansi_color(clm.lev) << to_cstring(clm.lev); if (!clm.prov.file.empty()) { os << color_code('x') << "@" @@ -116,7 +116,7 @@ void MtSelfWriter::operator()(LogProvenance prov, } else { - // Logging "local" message from the master thread! + // Logging "local" message from the master thread cerr << color_code('W') << "[M!] "; } cerr << ColorfulLogMessage{prov, lev, msg} << std::endl; diff --git a/src/celeritas/CMakeLists.txt b/src/celeritas/CMakeLists.txt index 0003c831d5..b0c122e52d 100644 --- a/src/celeritas/CMakeLists.txt +++ b/src/celeritas/CMakeLists.txt @@ -46,6 +46,7 @@ list(APPEND SOURCES em/process/BremsstrahlungProcess.cc em/process/ComptonProcess.cc em/process/CoulombScatteringProcess.cc + em/process/ElectroNuclearProcess.cc em/process/EIonizationProcess.cc em/process/EPlusAnnihilationProcess.cc em/process/GammaConversionProcess.cc @@ -99,6 +100,8 @@ list(APPEND SOURCES mat/MaterialParams.cc mat/MaterialParamsOutput.cc mat/detail/Utils.cc + mucf/model/detail/MucfMaterialInserter.cc + mucf/process/MucfProcess.cc neutron/model/CascadeOptions.cc neutron/model/CascadeOptionsIO.json.cc neutron/process/NeutronElasticProcess.cc @@ -357,6 +360,7 @@ celeritas_polysource(alongstep/AlongStepCylMapFieldMscAction) celeritas_polysource(em/model/BetheHeitlerModel) celeritas_polysource(em/model/BetheBlochModel) celeritas_polysource(em/model/BraggModel) +celeritas_polysource(em/model/ElectroNuclearModel) celeritas_polysource(em/model/EPlusGGModel) celeritas_polysource(em/model/GammaNuclearModel) celeritas_polysource(em/model/ICRU73QOModel) @@ -373,6 +377,7 @@ celeritas_polysource(em/model/CoulombScatteringModel) celeritas_polysource(geo/detail/BoundaryAction) celeritas_polysource(global/detail/KillActive) celeritas_polysource(global/detail/TrackSlotUtils) +celeritas_polysource(mucf/model/DTMixMucfModel) celeritas_polysource(neutron/model/ChipsNeutronElasticModel) celeritas_polysource(neutron/model/NeutronInelasticModel) celeritas_polysource(optical/model/AbsorptionModel) @@ -392,8 +397,10 @@ celeritas_polysource(optical/gen/DirectGeneratorAction) celeritas_polysource(optical/gen/detail/GeneratorAlgorithms) celeritas_polysource(optical/gen/detail/OffloadAlgorithms) celeritas_polysource(optical/surface/BoundaryAction) -celeritas_polysource(optical/surface/model/PolishedRoughnessModel) celeritas_polysource(optical/surface/model/DielectricInteractionModel) +celeritas_polysource(optical/surface/model/GaussianRoughnessModel) +celeritas_polysource(optical/surface/model/PolishedRoughnessModel) +celeritas_polysource(optical/surface/model/SmearRoughnessModel) celeritas_polysource(optical/surface/model/TrivialInteractionModel) celeritas_polysource(phys/detail/DiscreteSelectAction) celeritas_polysource(phys/detail/PreStepAction) diff --git a/src/celeritas/Types.hh b/src/celeritas/Types.hh index c2bc81764a..c2cd0e9e63 100644 --- a/src/celeritas/Types.hh +++ b/src/celeritas/Types.hh @@ -227,25 +227,6 @@ enum class CylAxis size_ }; -//---------------------------------------------------------------------------// -//! Muon-catalyzed fusion atoms -enum class MucfMuonicAtom -{ - deuterium, - tritium, - size_ -}; - -//---------------------------------------------------------------------------// -//! Muon-catalyzed fusion molecules -enum class MucfMuonicMolecule -{ - deuterium_deuterium, - deuterium_tritium, - tritium_tritium, - size_ -}; - //---------------------------------------------------------------------------// // HELPER STRUCTS //---------------------------------------------------------------------------// diff --git a/src/celeritas/alongstep/AlongStep.hh b/src/celeritas/alongstep/AlongStep.hh deleted file mode 100644 index d64bd2f0aa..0000000000 --- a/src/celeritas/alongstep/AlongStep.hh +++ /dev/null @@ -1,60 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/AlongStep.hh -//! \brief Along-step function and helper classes -//---------------------------------------------------------------------------// -#pragma once - -#include "celeritas/global/CoreTrackView.hh" - -#include "detail/ElossApplier.hh" // IWYU pragma: associated -#include "detail/MscApplier.hh" // IWYU pragma: associated -#include "detail/MscStepLimitApplier.hh" // IWYU pragma: associated -#include "detail/PropagationApplier.hh" // IWYU pragma: associated -#include "detail/TimeUpdater.hh" // IWYU pragma: associated -#include "detail/TrackUpdater.hh" // IWYU pragma: associated - -namespace celeritas -{ -//---------------------------------------------------------------------------// -/*! - * Perform the along-step action using helper functions. - * - * \tparam MH MSC helper, e.g. \c detail::NoMsc - * \tparam MP Propagator factory, e.g. \c detail::LinearPropagatorFactory - * \tparam EH Energy loss helper, e.g. \c detail::TrackNoEloss - */ -template -struct AlongStep -{ - inline CELER_FUNCTION void operator()(CoreTrackView& track); - - MH msc; - MP make_propagator; - EH eloss; -}; - -//---------------------------------------------------------------------------// -// DEDUCTION GUIDES -//---------------------------------------------------------------------------// -template -CELER_FUNCTION AlongStep(MH&&, MP&&, EH&&) -> AlongStep; - -//---------------------------------------------------------------------------// -// INLINE DEFINITIONS -//---------------------------------------------------------------------------// -template -CELER_FUNCTION void AlongStep::operator()(CoreTrackView& track) -{ - detail::MscStepLimitApplier{msc}(track); - detail::PropagationApplier{make_propagator}(track); - detail::MscApplier{msc}(track); - detail::TimeUpdater{}(track); - detail::ElossApplier{eloss}(track); - detail::TrackUpdater{}(track); -} - -//---------------------------------------------------------------------------// -} // namespace celeritas diff --git a/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cc b/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cc index e9c7ef382b..598be0efac 100644 --- a/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cc +++ b/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cc @@ -6,26 +6,28 @@ //---------------------------------------------------------------------------// #include "AlongStepCartMapFieldMscAction.hh" -#include #include #include "corecel/Assert.hh" #include "celeritas/em/msc/UrbanMsc.hh" #include "celeritas/em/params/FluctuationParams.hh" // IWYU pragma: keep #include "celeritas/em/params/UrbanMscParams.hh" // IWYU pragma: keep +#include "celeritas/field/CartMapField.hh" // IWYU pragma: keep #include "celeritas/field/CartMapFieldInput.hh" -#include "celeritas/geo/GeoFwd.hh" #include "celeritas/global/ActionLauncher.hh" #include "celeritas/global/CoreParams.hh" #include "celeritas/global/CoreState.hh" #include "celeritas/global/TrackExecutor.hh" -#include "celeritas/phys/ParticleTrackView.hh" -#include "AlongStep.hh" - -#include "detail/CartMapFieldPropagatorFactory.hh" +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/FluctELoss.hh" #include "detail/MeanELoss.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" +#include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" namespace celeritas { @@ -97,7 +99,7 @@ void AlongStepCartMapFieldMscAction::step(CoreParams const& params, { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } - PropagationApplier{CartMapFieldPropagatorFactory{ + PropagationApplier{detail::FieldTrackPropagator{ field_->ref()}}(track); if (this->has_msc()) { diff --git a/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cu b/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cu index 446753bc09..85a08ce029 100644 --- a/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cu +++ b/src/celeritas/alongstep/AlongStepCartMapFieldMscAction.cu @@ -9,6 +9,7 @@ #include "corecel/sys/ScopedProfiling.hh" #include "celeritas/em/params/FluctuationParams.hh" #include "celeritas/em/params/UrbanMscParams.hh" +#include "celeritas/field/CartMapField.hh" #include "celeritas/field/CartMapFieldParams.hh" #include "celeritas/global/ActionLauncher.device.hh" #include "celeritas/global/CoreParams.hh" @@ -16,7 +17,7 @@ #include "celeritas/global/TrackExecutor.hh" #include "detail/AlongStepKernels.hh" -#include "detail/CartMapFieldPropagatorFactory.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/PropagationApplier.hh" namespace celeritas @@ -39,8 +40,9 @@ void AlongStepCartMapFieldMscAction::step(CoreParams const& params, params.ptr(), state.ptr(), this->action_id(), - detail::PropagationApplier{detail::CartMapFieldPropagatorFactory{ - field_->ref()}}); + detail::PropagationApplier{ + detail::FieldTrackPropagator{ + field_->ref()}}); static ActionLauncher const launch_kernel( *this, "propagate-cartmap"); launch_kernel(*this, params, state, execute_thread); diff --git a/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cc b/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cc index 38b36965fc..65622dd4d1 100644 --- a/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cc +++ b/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cc @@ -19,13 +19,19 @@ #include "celeritas/global/CoreParams.hh" #include "celeritas/global/CoreState.hh" #include "celeritas/global/TrackExecutor.hh" -#include "celeritas/phys/ParticleTrackView.hh" -#include "AlongStep.hh" - -#include "detail/CylMapFieldPropagatorFactory.hh" +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/FluctELoss.hh" #include "detail/MeanELoss.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" +#include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/CylMapField.hh" namespace celeritas { @@ -97,7 +103,7 @@ void AlongStepCylMapFieldMscAction::step(CoreParams const& params, { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } - PropagationApplier{CylMapFieldPropagatorFactory{ + PropagationApplier{FieldTrackPropagator{ field_->ref()}}(track); if (this->has_msc()) { diff --git a/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cu b/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cu index 69c566f3d0..03b7ebfbce 100644 --- a/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cu +++ b/src/celeritas/alongstep/AlongStepCylMapFieldMscAction.cu @@ -16,8 +16,18 @@ #include "celeritas/global/TrackExecutor.hh" #include "detail/AlongStepKernels.hh" -#include "detail/CylMapFieldPropagatorFactory.hh" +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" +#include "detail/FluctELoss.hh" +#include "detail/MeanELoss.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" #include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/CylMapField.hh" namespace celeritas { @@ -39,7 +49,7 @@ void AlongStepCylMapFieldMscAction::step(CoreParams const& params, params.ptr(), state.ptr(), this->action_id(), - detail::PropagationApplier{detail::CylMapFieldPropagatorFactory{ + detail::PropagationApplier{detail::FieldTrackPropagator{ field_->ref()}}); static ActionLauncher const launch_kernel( *this, "propagate-cylmap"); diff --git a/src/celeritas/alongstep/AlongStepGeneralLinearAction.cc b/src/celeritas/alongstep/AlongStepGeneralLinearAction.cc index 9ba47fd574..eb50a0f23e 100644 --- a/src/celeritas/alongstep/AlongStepGeneralLinearAction.cc +++ b/src/celeritas/alongstep/AlongStepGeneralLinearAction.cc @@ -23,7 +23,7 @@ #include "detail/ElossApplier.hh" #include "detail/FluctELoss.hh" // IWYU pragma: associated -#include "detail/LinearPropagatorFactory.hh" +#include "detail/LinearTrackPropagator.hh" #include "detail/MeanELoss.hh" // IWYU pragma: associated #include "detail/MscApplier.hh" #include "detail/MscStepLimitApplier.hh" @@ -95,7 +95,7 @@ void AlongStepGeneralLinearAction::step(CoreParams const& params, { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } - PropagationApplier{LinearPropagatorFactory{}}(track); + PropagationApplier{LinearTrackPropagator{}}(track); if (this->has_msc()) { MscApplier{UrbanMsc{msc_->ref()}}(track); diff --git a/src/celeritas/alongstep/AlongStepNeutralAction.cc b/src/celeritas/alongstep/AlongStepNeutralAction.cc index 2b7191c680..843ca8e792 100644 --- a/src/celeritas/alongstep/AlongStepNeutralAction.cc +++ b/src/celeritas/alongstep/AlongStepNeutralAction.cc @@ -11,12 +11,10 @@ #include "celeritas/global/ActionLauncher.hh" #include "celeritas/global/CoreParams.hh" #include "celeritas/global/CoreState.hh" +#include "celeritas/global/CoreTrackView.hh" #include "celeritas/global/TrackExecutor.hh" -#include "AlongStep.hh" // IWYU pragma: associated - #include "detail/AlongStepNeutralImpl.hh" // IWYU pragma: associated -#include "detail/LinearPropagatorFactory.hh" // IWYU pragma: associated namespace celeritas { @@ -36,14 +34,16 @@ AlongStepNeutralAction::AlongStepNeutralAction(ActionId id) : id_(id) void AlongStepNeutralAction::step(CoreParams const& params, CoreStateHost& state) const { - auto execute = make_along_step_track_executor( - params.ptr(), - state.ptr(), - this->action_id(), - AlongStep{detail::NoMsc{}, - detail::LinearPropagatorFactory{}, - detail::NoELoss{}}); - return launch_action(*this, params, state, execute); + using namespace ::celeritas::detail; + + return launch_action( + *this, + params, + state, + make_along_step_track_executor(params.ptr(), + state.ptr(), + this->action_id(), + AlongStepNeutralExecutor{})); } //---------------------------------------------------------------------------// diff --git a/src/celeritas/alongstep/AlongStepNeutralAction.cu b/src/celeritas/alongstep/AlongStepNeutralAction.cu index 47e542c860..3d544fba4c 100644 --- a/src/celeritas/alongstep/AlongStepNeutralAction.cu +++ b/src/celeritas/alongstep/AlongStepNeutralAction.cu @@ -12,7 +12,6 @@ #include "celeritas/global/TrackExecutor.hh" #include "detail/AlongStepNeutralImpl.hh" -#include "detail/LinearPropagatorFactory.hh" namespace celeritas { @@ -23,13 +22,11 @@ namespace celeritas void AlongStepNeutralAction::step(CoreParams const& params, CoreStateDevice& state) const { - auto execute = make_along_step_track_executor( - params.ptr(), - state.ptr(), - this->action_id(), - AlongStep{detail::NoMsc{}, - detail::LinearPropagatorFactory{}, - detail::NoELoss{}}); + auto execute + = make_along_step_track_executor(params.ptr(), + state.ptr(), + this->action_id(), + detail::AlongStepNeutralExecutor{}); static ActionLauncher const launch_kernel(*this); launch_kernel(*this, params, state, execute); } diff --git a/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cc b/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cc index ed83e04c34..407f3fc995 100644 --- a/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cc +++ b/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cc @@ -10,6 +10,7 @@ #include #include "corecel/Assert.hh" +#include "celeritas/alongstep/detail/PropagationApplier.hh" #include "celeritas/em/msc/UrbanMsc.hh" #include "celeritas/em/params/FluctuationParams.hh" // IWYU pragma: keep #include "celeritas/em/params/UrbanMscParams.hh" // IWYU pragma: keep @@ -21,11 +22,18 @@ #include "celeritas/global/TrackExecutor.hh" #include "celeritas/phys/ParticleTrackView.hh" -#include "AlongStep.hh" - +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/FluctELoss.hh" #include "detail/MeanELoss.hh" -#include "detail/RZMapFieldPropagatorFactory.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" +#include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/RZMapField.hh" namespace celeritas { @@ -97,7 +105,7 @@ void AlongStepRZMapFieldMscAction::step(CoreParams const& params, { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } - PropagationApplier{RZMapFieldPropagatorFactory{ + PropagationApplier{FieldTrackPropagator{ field_->ref()}}(track); if (this->has_msc()) { diff --git a/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cu b/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cu index 4d545a5258..1565d1da7a 100644 --- a/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cu +++ b/src/celeritas/alongstep/AlongStepRZMapFieldMscAction.cu @@ -16,8 +16,18 @@ #include "celeritas/global/TrackExecutor.hh" #include "detail/AlongStepKernels.hh" +#include "detail/ElossApplier.hh" +#include "detail/FieldTrackPropagator.hh" +#include "detail/FluctELoss.hh" +#include "detail/MeanELoss.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" #include "detail/PropagationApplier.hh" -#include "detail/RZMapFieldPropagatorFactory.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/RZMapField.hh" namespace celeritas { @@ -39,7 +49,7 @@ void AlongStepRZMapFieldMscAction::step(CoreParams const& params, params.ptr(), state.ptr(), this->action_id(), - detail::PropagationApplier{detail::RZMapFieldPropagatorFactory{ + detail::PropagationApplier{detail::FieldTrackPropagator{ field_->ref()}}); static ActionLauncher const launch_kernel( *this, "propagate-rzmap"); diff --git a/src/celeritas/alongstep/AlongStepUniformMscAction.cc b/src/celeritas/alongstep/AlongStepUniformMscAction.cc index da8d55ba2e..d53299f813 100644 --- a/src/celeritas/alongstep/AlongStepUniformMscAction.cc +++ b/src/celeritas/alongstep/AlongStepUniformMscAction.cc @@ -10,9 +10,6 @@ #include "corecel/Assert.hh" #include "corecel/Macros.hh" -#include "corecel/data/Ref.hh" -#include "corecel/io/Logger.hh" -#include "corecel/sys/Device.hh" #include "celeritas/em/msc/UrbanMsc.hh" #include "celeritas/em/params/FluctuationParams.hh" #include "celeritas/em/params/UrbanMscParams.hh" // IWYU pragma: keep @@ -20,15 +17,21 @@ #include "celeritas/global/CoreParams.hh" #include "celeritas/global/CoreState.hh" #include "celeritas/global/TrackExecutor.hh" -#include "celeritas/track/TrackFunctors.hh" - -#include "AlongStep.hh" +#include "detail/ElossApplier.hh" #include "detail/FieldFunctors.hh" +#include "detail/FieldTrackPropagator.hh" #include "detail/FluctELoss.hh" -#include "detail/LinearPropagatorFactory.hh" +#include "detail/LinearTrackPropagator.hh" #include "detail/MeanELoss.hh" -#include "detail/UniformFieldPropagatorFactory.hh" +#include "detail/MscApplier.hh" +#include "detail/MscStepLimitApplier.hh" +#include "detail/PropagationApplier.hh" +#include "detail/TimeUpdater.hh" +#include "detail/TrackUpdater.hh" + +// Field classes +#include "celeritas/field/UniformField.hh" namespace celeritas { @@ -86,32 +89,19 @@ void AlongStepUniformMscAction::step(CoreParams const& params, CoreStateHost& state) const { using namespace ::celeritas::detail; - - auto launch_impl = [&](auto&& execute_track) { - return launch_action( - *this, - params, - state, - make_along_step_track_executor( - params.ptr(), - state.ptr(), - this->action_id(), - std::forward(execute_track))); - }; - - launch_impl([&](CoreTrackView& track) { + auto execute_track = [&](CoreTrackView& track) { if (this->has_msc()) { MscStepLimitApplier{UrbanMsc{msc_->ref()}}(track); } if (IsInUniformField{field_->ref()}(track)) { - PropagationApplier{UniformFieldPropagatorFactory{ + PropagationApplier{FieldTrackPropagator{ field_->ref()}}(track); } else { - PropagationApplier{LinearPropagatorFactory{}}(track); + PropagationApplier{LinearTrackPropagator{}}(track); } if (this->has_msc()) { @@ -127,7 +117,16 @@ void AlongStepUniformMscAction::step(CoreParams const& params, ElossApplier{MeanELoss{}}(track); } TrackUpdater{}(track); - }); + }; + + return launch_action( + *this, + params, + state, + make_along_step_track_executor(params.ptr(), + state.ptr(), + this->action_id(), + execute_track)); } //---------------------------------------------------------------------------// diff --git a/src/celeritas/alongstep/AlongStepUniformMscAction.cu b/src/celeritas/alongstep/AlongStepUniformMscAction.cu index ba078dfd0c..b4a092de4c 100644 --- a/src/celeritas/alongstep/AlongStepUniformMscAction.cu +++ b/src/celeritas/alongstep/AlongStepUniformMscAction.cu @@ -20,9 +20,12 @@ #include "detail/AlongStepKernels.hh" #include "detail/FieldFunctors.hh" -#include "detail/LinearPropagatorFactory.hh" +#include "detail/FieldTrackPropagator.hh" +#include "detail/LinearTrackPropagator.hh" #include "detail/PropagationApplier.hh" -#include "detail/UniformFieldPropagatorFactory.hh" + +// Field classes +#include "celeritas/field/UniformField.hh" namespace celeritas { @@ -46,7 +49,7 @@ void AlongStepUniformMscAction::step(CoreParams const& params, state.ptr(), detail::IsAlongStepUniformField{this->action_id(), field}, detail::PropagationApplier{ - detail::UniformFieldPropagatorFactory{field}}}; + detail::FieldTrackPropagator{field}}}; static ActionLauncher const launch_kernel( *this, "propagate"); launch_kernel(*this, params, state, execute_thread); @@ -59,7 +62,7 @@ void AlongStepUniformMscAction::step(CoreParams const& params, params.ptr(), state.ptr(), detail::IsAlongStepLinear{this->action_id(), field}, - detail::PropagationApplier{detail::LinearPropagatorFactory{}}}; + detail::PropagationApplier{detail::LinearTrackPropagator{}}}; static ActionLauncher const launch_kernel( *this, "propagate-linear"); launch_kernel(*this, params, state, execute_thread); diff --git a/src/celeritas/alongstep/detail/AlongStepKernels.cu b/src/celeritas/alongstep/detail/AlongStepKernels.cu index 175945ba58..b74fd9ce75 100644 --- a/src/celeritas/alongstep/detail/AlongStepKernels.cu +++ b/src/celeritas/alongstep/detail/AlongStepKernels.cu @@ -17,7 +17,7 @@ #include "ElossApplier.hh" #include "FluctELoss.hh" -#include "LinearPropagatorFactory.hh" +#include "LinearTrackPropagator.hh" #include "MeanELoss.hh" #include "MscApplier.hh" #include "MscStepLimitApplier.hh" @@ -58,7 +58,7 @@ void launch_propagate(CoreStepActionInterface const& action, params.ptr(), state.ptr(), action.action_id(), - detail::PropagationApplier{detail::LinearPropagatorFactory{}}); + detail::PropagationApplier{detail::LinearTrackPropagator{}}); static ActionLauncher const launch_kernel( action, "propagate-linear"); launch_kernel(action, params, state, execute_thread); diff --git a/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh b/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh index 08ab2654d1..0314f7a006 100644 --- a/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh +++ b/src/celeritas/alongstep/detail/AlongStepNeutralImpl.hh @@ -6,18 +6,18 @@ //---------------------------------------------------------------------------// #pragma once -#include "corecel/Assert.hh" -#include "corecel/Macros.hh" -#include "corecel/Types.hh" -#include "corecel/math/Quantity.hh" +#include "celeritas/global/CoreTrackView.hh" -#include "../AlongStep.hh" +#include "ElossApplier.hh" // IWYU pragma: associated +#include "LinearTrackPropagator.hh" // IWYU pragma: associated +#include "MscApplier.hh" // IWYU pragma: associated +#include "MscStepLimitApplier.hh" // IWYU pragma: associated +#include "PropagationApplier.hh" // IWYU pragma: associated +#include "TimeUpdater.hh" // IWYU pragma: associated +#include "TrackUpdater.hh" // IWYU pragma: associated namespace celeritas { -//---------------------------------------------------------------------------// -class CoreTrackView; - namespace detail { //---------------------------------------------------------------------------// @@ -47,23 +47,39 @@ struct NoMsc */ struct NoELoss { - //! This calculator never returns energy loss - CELER_CONSTEXPR_FUNCTION bool is_applicable(CoreTrackView const&) - { - return false; - } - //! No energy loss - CELER_FUNCTION auto calc_eloss(CoreTrackView const&, real_type, bool) const - -> decltype(auto) + CELER_FUNCTION auto operator()(CoreTrackView const&) const -> decltype(auto) { return zero_quantity(); } +}; + +//---------------------------------------------------------------------------// +/*! + * Perform the along-step action using helper functions. + */ +struct AlongStepNeutralExecutor +{ + inline CELER_FUNCTION void operator()(CoreTrackView& track); - //! No slowing down - static CELER_CONSTEXPR_FUNCTION bool imprecise_range() { return false; } + NoMsc msc; + LinearTrackPropagator propagate_track; + NoELoss eloss; }; +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +CELER_FUNCTION void AlongStepNeutralExecutor::operator()(CoreTrackView& track) +{ + MscStepLimitApplier{msc}(track); + PropagationApplier{propagate_track}(track); + MscApplier{msc}(track); + TimeUpdater{}(track); + ElossApplier{eloss}(track); + TrackUpdater{}(track); +} + //---------------------------------------------------------------------------// } // namespace detail } // namespace celeritas diff --git a/src/celeritas/alongstep/detail/CartMapFieldPropagatorFactory.hh b/src/celeritas/alongstep/detail/CartMapFieldPropagatorFactory.hh deleted file mode 100644 index 792bf44db7..0000000000 --- a/src/celeritas/alongstep/detail/CartMapFieldPropagatorFactory.hh +++ /dev/null @@ -1,43 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/CartMapFieldPropagatorFactory.hh -//---------------------------------------------------------------------------// -#pragma once - -#include "celeritas/field/CartMapField.hh" // IWYU pragma: associated -#include "celeritas/field/CartMapFieldData.hh" // IWYU pragma: associated -#include "celeritas/field/DormandPrinceIntegrator.hh" -#include "celeritas/field/MakeMagFieldPropagator.hh" -#include "celeritas/global/CoreTrackView.hh" - -namespace celeritas -{ -namespace detail -{ -//---------------------------------------------------------------------------// -/*! - * Propagate a track in a cartesian map magnetic field. - */ -struct CartMapFieldPropagatorFactory -{ - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const - { - return make_mag_field_propagator( - CartMapField{field}, - field.options, - track.particle(), - track.geometry()); - } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - - //// DATA //// - - NativeCRef field; -}; - -//---------------------------------------------------------------------------// -} // namespace detail -} // namespace celeritas diff --git a/src/celeritas/alongstep/detail/CylMapFieldPropagatorFactory.hh b/src/celeritas/alongstep/detail/CylMapFieldPropagatorFactory.hh deleted file mode 100644 index d83e78750b..0000000000 --- a/src/celeritas/alongstep/detail/CylMapFieldPropagatorFactory.hh +++ /dev/null @@ -1,43 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/CylMapFieldPropagatorFactory.hh -//---------------------------------------------------------------------------// -#pragma once - -#include "celeritas/field/CylMapField.hh" // IWYU pragma: associated -#include "celeritas/field/CylMapFieldData.hh" // IWYU pragma: associated -#include "celeritas/field/DormandPrinceIntegrator.hh" -#include "celeritas/field/MakeMagFieldPropagator.hh" -#include "celeritas/global/CoreTrackView.hh" - -namespace celeritas -{ -namespace detail -{ -//---------------------------------------------------------------------------// -/*! - * Propagate a track in an Cyl map magnetic field. - */ -struct CylMapFieldPropagatorFactory -{ - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const - { - return make_mag_field_propagator( - CylMapField{field}, - field.options, - track.particle(), - track.geometry()); - } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - - //// DATA //// - - NativeCRef field; -}; - -//---------------------------------------------------------------------------// -} // namespace detail -} // namespace celeritas diff --git a/src/celeritas/alongstep/detail/ElossApplier.hh b/src/celeritas/alongstep/detail/ElossApplier.hh index a6e64416bf..37fe3030c6 100644 --- a/src/celeritas/alongstep/detail/ElossApplier.hh +++ b/src/celeritas/alongstep/detail/ElossApplier.hh @@ -6,7 +6,12 @@ //---------------------------------------------------------------------------// #pragma once +#include "corecel/Assert.hh" +#include "corecel/math/Quantity.hh" +#include "corecel/math/detail/QuantityImpl.hh" #include "celeritas/global/CoreTrackView.hh" +#include "celeritas/phys/ParticleTrackView.hh" +#include "celeritas/phys/PhysicsStepUtils.hh" namespace celeritas { @@ -14,65 +19,112 @@ namespace detail { //---------------------------------------------------------------------------// /*! - * Apply energy loss using the EnergyLossHandler interface. - * - * TODO: move apply-cut out of mean/fluct eloss to this function to reduce - * duplicate code? + * Whether the given track can lose energy along its step. */ -template -struct ElossApplier +inline CELER_FUNCTION bool is_eloss_applicable(CoreTrackView const& track) { - inline CELER_FUNCTION void operator()(CoreTrackView const& track); + auto sim = track.sim(); + if (sim.status() != TrackStatus::alive) + { + // Errored during propagation + return false; + } - EH eloss; -}; + if (track.particle().is_stopped()) + { + // No energy to lose + return false; + } -//---------------------------------------------------------------------------// -// DEDUCTION GUIDES -//---------------------------------------------------------------------------// -template -CELER_FUNCTION ElossApplier(EH&&) -> ElossApplier; + if (!track.physics().energy_loss_grid()) + { + // No energy loss for this particle/material + return false; + } + + return true; +} //---------------------------------------------------------------------------// -// INLINE DEFINITIONS -//---------------------------------------------------------------------------// -template -CELER_FUNCTION void ElossApplier::operator()(CoreTrackView const& track) +/*! + * Whether the given track should lose all energy over the step. + * + * Tracks should theoretically only slow to zero via the range limiter (and its + * associated post-step action), but spline interpolation and energy + * fluctuations are inconsistent and may lead to incorrectly long steps. + */ +inline CELER_FUNCTION bool lost_all_energy(CoreTrackView const& track) { - auto particle = track.particle(); - if (!eloss.is_applicable(track) || particle.is_stopped()) + bool const on_boundary = track.geometry().is_on_boundary(); + CELER_ASSERT( + on_boundary + == (track.sim().post_step_action() == track.boundary_action())); + if (on_boundary) { - return; + // Avoid stopping particles unphysically on the boundary + return false; } + auto phys = track.physics(); + if (track.sim().step_length() == phys.dedx_range()) + { + // Range limited step: particle has deposited all remaining energy by + // slowing down + return true; + } + + if (track.particle().energy() < phys.particle_scalars().lowest_energy) + { + // Particle *started* below the tracking cut: deposit all remaining + // energy along the step + return true; + } + return false; +} + +//---------------------------------------------------------------------------// +/*! + * Deposit energy along the particle's step and update the particle state. + * + * - Particles that end below the tracking cut distribute their remaining + * energy along the step, unless they end on the boundary + * - Energy loss is removed to the particle and added to the physics step + * - Stopped tracks are killed if they have no at-rest process + * - Stopped tracks with at-rest processes are forced to undergo an interaction + */ +inline CELER_FUNCTION void +apply_slowing_down(CoreTrackView const& track, ParticleTrackView::Energy eloss) +{ + auto particle = track.particle(); + auto phys = track.physics(); auto sim = track.sim(); - auto step = sim.step_length(); - auto post_step_action = sim.post_step_action(); + bool on_boundary = track.geometry().is_on_boundary(); - // Calculate energy loss, possibly applying tracking cuts - bool apply_cut = (post_step_action != track.boundary_action()); - auto deposited = eloss.calc_eloss(track, step, apply_cut); - CELER_ASSERT(deposited <= particle.energy()); - CELER_ASSERT(apply_cut || deposited != particle.energy()); + CELER_EXPECT(eloss > zero_quantity()); + CELER_EXPECT(eloss <= particle.energy()); + CELER_EXPECT(eloss != particle.energy() || !on_boundary + || sim.post_step_action() == phys.scalars().range_action()); - if (deposited > zero_quantity()) + if (!on_boundary + && (particle.energy() - eloss <= phys.particle_scalars().lowest_energy)) + { + // Particle *ended* below the tracking cut: deposit all its energy + // (aka adjusting dE/dx upward a bit) + eloss = particle.energy(); + } + if (eloss > zero_quantity()) { // Deposit energy loss - auto phys_step = track.physics_step(); - phys_step.deposit_energy(deposited); - particle.subtract_energy(deposited); + track.physics_step().deposit_energy_from(eloss, particle); } - // Energy loss helper *must* apply the tracking cutoff - CELER_ASSERT(particle.energy() - >= track.physics().particle_scalars().lowest_energy - || !apply_cut || particle.is_stopped()); + // At this point, we shouldn't have any low-energy tracks *except* on the + // boundary + CELER_ASSERT(particle.energy() >= phys.particle_scalars().lowest_energy + || on_boundary || particle.is_stopped()); if (particle.is_stopped()) { - // Particle lost all energy over the step - CELER_ASSERT(post_step_action != track.boundary_action()); - auto const phys = track.physics(); if (!phys.at_rest_process()) { // Immediately kill stopped particles with no at rest processes @@ -87,6 +139,55 @@ CELER_FUNCTION void ElossApplier::operator()(CoreTrackView const& track) } } +//---------------------------------------------------------------------------// +/*! + * Apply energy loss using an energy loss calculator class. + * + * \todo Rename to \c ElossExecutor. + */ +template +struct ElossApplier +{ + inline CELER_FUNCTION void operator()(CoreTrackView const& track); + + EC calc_eloss; +}; + +//---------------------------------------------------------------------------// +// DEDUCTION GUIDES +//---------------------------------------------------------------------------// + +// Note: needed for C++17 support +template +CELER_FUNCTION ElossApplier(EC&&) -> ElossApplier; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +template +CELER_FUNCTION void ElossApplier::operator()(CoreTrackView const& track) +{ + CELER_EXPECT(track.sim().step_length() > 0); + if (!is_eloss_applicable(track)) + return; + + auto deposited = [&] { + if (lost_all_energy(track)) + { + // Particle was low energy or range-limited + return track.particle().energy(); + } + + // Calculate energy loss along the step + return ParticleTrackView::Energy{this->calc_eloss(track)}; + }(); + + if (deposited > zero_quantity()) + { + apply_slowing_down(track, deposited); + } +} + //---------------------------------------------------------------------------// } // namespace detail } // namespace celeritas diff --git a/src/celeritas/alongstep/detail/FieldFunctors.hh b/src/celeritas/alongstep/detail/FieldFunctors.hh index c12dfc7392..37ca3df993 100644 --- a/src/celeritas/alongstep/detail/FieldFunctors.hh +++ b/src/celeritas/alongstep/detail/FieldFunctors.hh @@ -10,6 +10,7 @@ #include "corecel/sys/ThreadId.hh" #include "celeritas/Types.hh" #include "celeritas/field/UniformFieldData.hh" +#include "celeritas/track/TrackFunctors.hh" namespace celeritas { diff --git a/src/celeritas/alongstep/detail/FieldTrackPropagator.hh b/src/celeritas/alongstep/detail/FieldTrackPropagator.hh new file mode 100644 index 0000000000..82e26aefb1 --- /dev/null +++ b/src/celeritas/alongstep/detail/FieldTrackPropagator.hh @@ -0,0 +1,91 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/alongstep/detail/FieldTrackPropagator.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "celeritas/field/DormandPrinceIntegrator.hh" +#include "celeritas/field/MakeMagFieldPropagator.hh" +#include "celeritas/global/CoreTrackView.hh" + +#if !CELER_DEVICE_COMPILE +# include "corecel/io/Logger.hh" +#endif + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Propagate a track in a mapped magnetic field. + * + * This class moves the track a single step based on the current sim step + * length. It updates the particle with looping behavior if necessary. + * + * \tparam Field The field type (e.g., RZMapField, CylMapField, CartMapField) + */ +template +struct FieldTrackPropagator +{ + //!@{ + //! \name Type aliases + using ParamsRef = typename Field::ParamsRef; + //!@} + + //! Create propagator, execute propagation, and return result + [[nodiscard]] CELER_FUNCTION Propagation + operator()(CoreTrackView& track) const; + + //// DATA //// + + ParamsRef field; +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Create propagator, execute propagation, and return result. + */ +template +inline CELER_FUNCTION Propagation +FieldTrackPropagator::operator()(CoreTrackView& track) const +{ + auto sim = track.sim(); + auto propagator = make_mag_field_propagator( + Field{field}, field.options, track.particle(), track.geometry()); + + Propagation p = propagator(sim.step_length()); + + sim.update_looping(p.looping); + if (p.looping) + { + sim.step_length(p.distance); + sim.post_step_action([&track, &sim] { + auto particle = track.particle(); + if (particle.is_stable() + && sim.is_looping(particle.particle_id(), particle.energy())) + { +#if !CELER_DEVICE_COMPILE + CELER_LOG_LOCAL(debug) + << "Track (pid=" << particle.particle_id().get() + << ", E=" << particle.energy().value() << ' ' + << ParticleTrackView::Energy::unit_type::label() + << ") is looping after " << sim.num_looping_steps() + << " steps"; +#endif + return track.tracking_cut_action(); + } + return track.propagation_limit_action(); + }()); + } + return p; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/alongstep/detail/FluctELoss.hh b/src/celeritas/alongstep/detail/FluctELoss.hh index 9692cbb8b2..f99123fe17 100644 --- a/src/celeritas/alongstep/detail/FluctELoss.hh +++ b/src/celeritas/alongstep/detail/FluctELoss.hh @@ -19,6 +19,11 @@ namespace detail //---------------------------------------------------------------------------// /*! * Apply energy loss (with fluctuations) to a track. + * + * \warning Because particle range is the integral of the \em mean energy loss, + * and this samples from a distribution, the sampled energy loss may be more + * than the particle's energy! We take care not to end a particle's life on a + * boundary, which is a nonphysical bias. */ class FluctELoss { @@ -33,16 +38,8 @@ class FluctELoss // Construct with fluctuation data inline explicit CELER_FUNCTION FluctELoss(ParamsRef const& params); - // Whether energy loss can be used for this track - inline CELER_FUNCTION bool is_applicable(CoreTrackView const&) const; - // Apply to the track - inline CELER_FUNCTION Energy calc_eloss(CoreTrackView const& track, - real_type step, - bool apply_cut); - - //! Indicate that we can lose all energy before hitting the dE/dx range - static CELER_CONSTEXPR_FUNCTION bool imprecise_range() { return true; } + inline CELER_FUNCTION Energy operator()(CoreTrackView const& track); private: //// DATA //// @@ -69,21 +66,6 @@ CELER_FUNCTION FluctELoss::FluctELoss(ParamsRef const& params) CELER_EXPECT(fluct_params_); } -//---------------------------------------------------------------------------// -/*! - * Whether energy loss is used for this track. - */ -CELER_FUNCTION bool FluctELoss::is_applicable(CoreTrackView const& track) const -{ - // The track can be marked as `errored` *within* the along-step kernel, - // during propagation - if (track.sim().status() == TrackStatus::errored) - return false; - - // Energy loss grid ID is 'false' - return static_cast(track.physics().energy_loss_grid()); -} - //---------------------------------------------------------------------------// /*! * Apply energy loss to the given track. @@ -96,81 +78,65 @@ CELER_FUNCTION bool FluctELoss::is_applicable(CoreTrackView const& track) const * energy, we reduce it to the particle energy (if energy cuts are to be * applied) or to the mean energy loss (if cuts are prohibited due to this * being a non-physics-based step). + * + * \todo The gamma and gaussian energy loss models are never called by + * positrons/electrons, only by muons */ -CELER_FUNCTION auto FluctELoss::calc_eloss(CoreTrackView const& track, - real_type step, - bool apply_cut) -> Energy +CELER_FUNCTION auto FluctELoss::operator()(CoreTrackView const& track) -> Energy { - CELER_EXPECT(step > 0); - auto particle = track.particle(); auto phys = track.physics(); + auto sim = track.sim(); - if (apply_cut && particle.energy() < phys.particle_scalars().lowest_energy) + // Calculate mean energy loss + auto eloss = calc_mean_energy_loss(particle, phys, sim.step_length()); + if (eloss == zero_quantity()) { - // Deposit all energy immediately when we start below the tracking cut - return particle.energy(); + return eloss; } - // Calculate mean energy loss - auto eloss = calc_mean_energy_loss(particle, phys, step); + // Apply energy loss fluctuations + auto cutoffs = track.cutoff(); + auto material = track.material(); + EnergyLossHelper loss_helper( + fluct_params_, cutoffs, material, particle, eloss, sim.step_length()); - if (eloss < particle.energy() && eloss > zero_quantity()) + auto rng = track.rng(); + switch (loss_helper.model()) { - // Apply energy loss fluctuations - auto cutoffs = track.cutoff(); - auto material = track.material(); - - EnergyLossHelper loss_helper( - fluct_params_, cutoffs, material, particle, eloss, step); - - auto rng = track.rng(); - switch (loss_helper.model()) - { #define ASU_SAMPLE_ELOSS(MODEL) \ case EnergyLossFluctuationModel::MODEL: \ eloss = this->sample_energy_loss( \ loss_helper, rng); \ break - ASU_SAMPLE_ELOSS(none); - ASU_SAMPLE_ELOSS(gamma); - ASU_SAMPLE_ELOSS(gaussian); - ASU_SAMPLE_ELOSS(urban); + ASU_SAMPLE_ELOSS(none); + ASU_SAMPLE_ELOSS(gamma); + ASU_SAMPLE_ELOSS(gaussian); + ASU_SAMPLE_ELOSS(urban); #undef ASU_SAMPLE_ELOSS - } - - if (eloss >= particle.energy()) - { - // Sampled energy loss can be greater than actual remaining energy - // because the range calculation is based on the *mean* energy - // loss. - if (apply_cut) - { - // Clamp to actual particle energy so that it stops - eloss = particle.energy(); - } - else - { - // Don't go to zero energy at geometry boundaries: just use the - // mean loss which should be positive because this isn't a - // range-limited step. - eloss = loss_helper.mean_loss(); - CELER_ASSERT(eloss < particle.energy()); - } - } } - if (apply_cut - && (particle.energy() - eloss <= phys.particle_scalars().lowest_energy)) + if (eloss >= particle.energy()) { - // Deposit all energy when we end below the tracking cut - return particle.energy(); + // Sampled energy loss can be greater than actual remaining energy + // because the range calculation is based on the *mean* energy + // loss. To fix this, we would need to sample the range from a + // distribution as well. + if (track.geometry().is_on_boundary()) + { + // Don't stop particles on geometry boundaries: just use the + // mean loss which should be positive because this isn't a + // range-limited step. + eloss = loss_helper.mean_loss(); + CELER_ASSERT(eloss < particle.energy()); + } + else + { + // Clamp to actual particle energy so that it stops + eloss = particle.energy(); + } } - CELER_ASSERT(eloss <= particle.energy()); - CELER_ENSURE(eloss != particle.energy() || apply_cut - || track.sim().post_step_action() - == phys.scalars().range_action()); return eloss; } diff --git a/src/celeritas/alongstep/detail/LinearPropagatorFactory.hh b/src/celeritas/alongstep/detail/LinearTrackPropagator.hh similarity index 65% rename from src/celeritas/alongstep/detail/LinearPropagatorFactory.hh rename to src/celeritas/alongstep/detail/LinearTrackPropagator.hh index 2a7baf763d..f438ecc565 100644 --- a/src/celeritas/alongstep/detail/LinearPropagatorFactory.hh +++ b/src/celeritas/alongstep/detail/LinearTrackPropagator.hh @@ -2,14 +2,12 @@ // Copyright Celeritas contributors: see top-level COPYRIGHT file for details // SPDX-License-Identifier: (Apache-2.0 OR MIT) //---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/LinearPropagatorFactory.hh +//! \file celeritas/alongstep/detail/LinearTrackPropagator.hh //---------------------------------------------------------------------------// #pragma once -#include "corecel/Macros.hh" -#include "celeritas/Types.hh" #include "celeritas/field/LinearPropagator.hh" -#include "celeritas/geo/GeoTrackView.hh" +#include "celeritas/global/CoreTrackView.hh" namespace celeritas { @@ -19,14 +17,12 @@ namespace detail /*! * Create a propagator for neutral particles or no fields. */ -struct LinearPropagatorFactory +struct LinearTrackPropagator { - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const + CELER_FUNCTION Propagation operator()(CoreTrackView const& track) const { - return LinearPropagator{track.geometry()}; + return LinearPropagator{track.geometry()}(track.sim().step_length()); } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return false; } }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/alongstep/detail/MeanELoss.hh b/src/celeritas/alongstep/detail/MeanELoss.hh index e0b42fea38..3087665ef2 100644 --- a/src/celeritas/alongstep/detail/MeanELoss.hh +++ b/src/celeritas/alongstep/detail/MeanELoss.hh @@ -27,69 +27,25 @@ class MeanELoss //!@} public: - // Whether energy loss is used for this track - inline CELER_FUNCTION bool is_applicable(CoreTrackView const&) const; - // Apply to the track - inline CELER_FUNCTION Energy calc_eloss(CoreTrackView const& track, - real_type step, - bool apply_cut); - - //! Particle will slow down to zero only if range limited - static CELER_CONSTEXPR_FUNCTION bool imprecise_range() { return false; } + inline CELER_FUNCTION Energy operator()(CoreTrackView const& track); }; //---------------------------------------------------------------------------// // INLINE DEFINITIONS -//---------------------------------------------------------------------------// -/*! - * Whether energy loss is used for this track. - */ -CELER_FUNCTION bool MeanELoss::is_applicable(CoreTrackView const& track) const -{ - // The track can be marked as `errored` *within* the along-step kernel, - // during propagation - if (track.sim().status() == TrackStatus::errored) - return false; - - // Energy loss grid ID is 'false' - return static_cast(track.physics().energy_loss_grid()); -} - //---------------------------------------------------------------------------// /*! * Apply energy loss to the given track. */ -CELER_FUNCTION auto MeanELoss::calc_eloss(CoreTrackView const& track, - real_type step, - bool apply_cut) -> Energy +CELER_FUNCTION auto MeanELoss::operator()(CoreTrackView const& track) -> Energy { - CELER_EXPECT(step > 0); - auto particle = track.particle(); auto phys = track.physics(); - - if (apply_cut && particle.energy() < phys.particle_scalars().lowest_energy) - { - // Deposit all energy when we start below the tracking cut - return particle.energy(); - } + auto sim = track.sim(); + CELER_EXPECT(!particle.is_stopped() && sim.step_length() > 0); // Calculate the mean energy loss - Energy eloss = calc_mean_energy_loss(particle, phys, step); - - if (apply_cut - && (particle.energy() - eloss <= phys.particle_scalars().lowest_energy)) - { - // Deposit all energy when we end below the tracking cut - return particle.energy(); - } - - CELER_ENSURE(eloss <= particle.energy()); - CELER_ENSURE(eloss != particle.energy() - || track.sim().post_step_action() - == phys.scalars().range_action()); - return eloss; + return calc_mean_energy_loss(particle, phys, sim.step_length()); } //---------------------------------------------------------------------------// diff --git a/src/celeritas/alongstep/detail/PropagationApplier.hh b/src/celeritas/alongstep/detail/PropagationApplier.hh index c0bccb4a93..89024e7c04 100644 --- a/src/celeritas/alongstep/detail/PropagationApplier.hh +++ b/src/celeritas/alongstep/detail/PropagationApplier.hh @@ -6,10 +6,7 @@ //---------------------------------------------------------------------------// #pragma once -#include - -#include "corecel/math/Algorithms.hh" -#include "corecel/sys/KernelTraits.hh" +#include "corecel/Assert.hh" #include "celeritas/global/CoreTrackView.hh" #if !CELER_DEVICE_COMPILE @@ -21,100 +18,44 @@ namespace celeritas { namespace detail { -//---------------------------------------------------------------------------// -/*! - * Apply propagation over the step (implementation). - */ -template -struct PropagationApplierBaseImpl -{ - inline CELER_FUNCTION void operator()(CoreTrackView& track); - - MP make_propagator; -}; - //---------------------------------------------------------------------------// /*! * Apply propagation over the step. * - * \tparam MP Propagator factory + * \tparam TP Track propagator * - * MP should be a function-like object: - * \code Propagator(*)(CoreTrackView const&) \endcode - * - * This class is partially specialized with a second template argument to - * extract any launch bounds from the MP class. TODO: we could probably inherit - * from a helper class to pull in those constants (if available). + * TP should be a function-like object: + * \code Propagation (*)(CoreTrackView const&) \endcode */ -template -struct PropagationApplier : public PropagationApplierBaseImpl -{ - CELER_FUNCTION PropagationApplier(MP&& mp) - : PropagationApplierBaseImpl{celeritas::forward(mp)} - { - } -}; - -template -struct PropagationApplier>> - : public PropagationApplierBaseImpl -{ - static constexpr int max_block_size = MP::max_block_size; - static constexpr int min_warps_per_eu = MP::min_warps_per_eu; - - CELER_FUNCTION PropagationApplier(MP&& mp) - : PropagationApplierBaseImpl{celeritas::forward(mp)} - { - } -}; - -template -struct PropagationApplier>> - : public PropagationApplierBaseImpl +template +struct PropagationApplier { - static constexpr int max_block_size = MP::max_block_size; + inline CELER_FUNCTION void operator()(CoreTrackView& track); - CELER_FUNCTION PropagationApplier(MP&& mp) - : PropagationApplierBaseImpl{celeritas::forward(mp)} - { - } + TP propagate; }; //---------------------------------------------------------------------------// // DEDUCTION GUIDES //---------------------------------------------------------------------------// -template -CELER_FUNCTION PropagationApplier(MP&&) -> PropagationApplier; +template +CELER_FUNCTION PropagationApplier(TP&&) -> PropagationApplier; //---------------------------------------------------------------------------// // INLINE DEFINITIONS //---------------------------------------------------------------------------// -template -CELER_FUNCTION void -PropagationApplierBaseImpl::operator()(CoreTrackView& track) +template +CELER_FUNCTION void PropagationApplier::operator()(CoreTrackView& track) { auto sim = track.sim(); - if (sim.step_length() == 0) - { - // Track is stopped: no movement or energy loss will happen - // (could be a stopped positron waiting for annihilation, or a - // particle waiting to decay?) - CELER_ASSERT(track.particle().is_stopped()); - CELER_ASSERT(sim.post_step_action() - == track.physics().scalars().discrete_action()); - CELER_ASSERT(track.physics().at_rest_process()); - return; - } + CELER_EXPECT(sim.step_length() > 0); - bool tracks_can_loop; Propagation p; { #if CELERITAS_DEBUG Real3 const orig_pos = track.geometry().pos(); #endif - auto propagate = make_propagator(track); - p = propagate(sim.step_length()); - tracks_can_loop = propagate.tracks_can_loop(); + p = this->propagate(track); CELER_ASSERT(p.distance > 0); #if CELERITAS_DEBUG if (CELER_UNLIKELY(track.geometry().pos() == orig_pos)) @@ -136,50 +77,18 @@ PropagationApplierBaseImpl::operator()(CoreTrackView& track) << " failed to change position"; # endif track.apply_errored(); - return; } #endif } - if (tracks_can_loop) - { - sim.update_looping(p.looping); - } - if (tracks_can_loop && p.looping) - { - // The track is looping, i.e. progressing little over many - // integration steps in the field propagator (likely a low energy - // particle in a low density material/strong magnetic field). - sim.step_length(p.distance); - - // Kill the track if it's stable and below the threshold energy or - // above the threshold number of steps allowed while looping. - sim.post_step_action([&track, &sim] { - auto particle = track.particle(); - if (particle.is_stable() - && sim.is_looping(particle.particle_id(), particle.energy())) - { -#if !CELER_DEVICE_COMPILE - CELER_LOG_LOCAL(debug) - << "Track (pid=" << particle.particle_id().get() - << ", E=" << particle.energy().value() << ' ' - << ParticleTrackView::Energy::unit_type::label() - << ") is looping after " << sim.num_looping_steps() - << " steps"; -#endif - return track.tracking_cut_action(); - } - return track.propagation_limit_action(); - }()); - } - else if (p.boundary) + if (p.boundary) { // Stopped at a geometry boundary: this is the new step action. CELER_ASSERT(p.distance <= sim.step_length()); sim.step_length(p.distance); sim.post_step_action(track.boundary_action()); } - else if (p.distance < sim.step_length()) + else if (!p.looping && p.distance < sim.step_length()) { // Some tracks may get stuck on a boundary and fail to move at // all in the field propagator, and will get bumped a small diff --git a/src/celeritas/alongstep/detail/RZMapFieldPropagatorFactory.hh b/src/celeritas/alongstep/detail/RZMapFieldPropagatorFactory.hh deleted file mode 100644 index 76f93058c7..0000000000 --- a/src/celeritas/alongstep/detail/RZMapFieldPropagatorFactory.hh +++ /dev/null @@ -1,43 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/RZMapFieldPropagatorFactory.hh -//---------------------------------------------------------------------------// -#pragma once - -#include "celeritas/field/DormandPrinceIntegrator.hh" -#include "celeritas/field/MakeMagFieldPropagator.hh" -#include "celeritas/field/RZMapField.hh" // IWYU pragma: associated -#include "celeritas/field/RZMapFieldData.hh" // IWYU pragma: associated -#include "celeritas/global/CoreTrackView.hh" - -namespace celeritas -{ -namespace detail -{ -//---------------------------------------------------------------------------// -/*! - * Propagate a track in an RZ map magnetic field. - */ -struct RZMapFieldPropagatorFactory -{ - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const - { - return make_mag_field_propagator( - RZMapField{field}, - field.options, - track.particle(), - track.geometry()); - } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - - //// DATA //// - - NativeCRef field; -}; - -//---------------------------------------------------------------------------// -} // namespace detail -} // namespace celeritas diff --git a/src/celeritas/alongstep/detail/UniformFieldPropagatorFactory.hh b/src/celeritas/alongstep/detail/UniformFieldPropagatorFactory.hh deleted file mode 100644 index 40ea543d09..0000000000 --- a/src/celeritas/alongstep/detail/UniformFieldPropagatorFactory.hh +++ /dev/null @@ -1,47 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/alongstep/detail/UniformFieldPropagatorFactory.hh -//---------------------------------------------------------------------------// -#pragma once - -#include "corecel/Macros.hh" -#include "celeritas/field/DormandPrinceIntegrator.hh" // IWYU pragma: associated -#include "celeritas/field/MakeMagFieldPropagator.hh" // IWYU pragma: associated -#include "celeritas/field/UniformField.hh" // IWYU pragma: associated -#include "celeritas/field/UniformFieldData.hh" // IWYU pragma: associated - -namespace celeritas -{ -namespace detail -{ -//---------------------------------------------------------------------------// -/*! - * Propagate a track in a uniform magnetic field. - */ -struct UniformFieldPropagatorFactory -{ -#if CELER_USE_DEVICE - inline static constexpr int max_block_size = 256; -#endif - - CELER_FUNCTION decltype(auto) operator()(CoreTrackView const& track) const - { - return make_mag_field_propagator( - UniformField(field.field), - field.options, - track.particle(), - track.geometry()); - } - - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - - //// DATA //// - - NativeCRef field; -}; - -//---------------------------------------------------------------------------// -} // namespace detail -} // namespace celeritas diff --git a/src/celeritas/em/data/ElectroNuclearData.hh b/src/celeritas/em/data/ElectroNuclearData.hh new file mode 100644 index 0000000000..dd9593f917 --- /dev/null +++ b/src/celeritas/em/data/ElectroNuclearData.hh @@ -0,0 +1,89 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/data/ElectroNuclearData.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/Types.hh" +#include "corecel/data/Collection.hh" +#include "corecel/grid/NonuniformGridData.hh" +#include "corecel/math/Quantity.hh" +#include "celeritas/Quantities.hh" +#include "celeritas/Types.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Scalar data for the electro-nuclear model. + */ +struct ElectroNuclearScalars +{ + // Particle IDs + ParticleId electron_id; + ParticleId positron_id; + + //! Model's minimum energy limit [MeV] + static CELER_CONSTEXPR_FUNCTION units::MevEnergy min_valid_energy() + { + return units::MevEnergy{1e+2}; + } + + //! Model's maximum energy limit [MeV] + static CELER_CONSTEXPR_FUNCTION units::MevEnergy max_valid_energy() + { + return units::MevEnergy{1e+8}; + } + + //! Whether data are assigned + explicit CELER_FUNCTION operator bool() const + { + return electron_id && positron_id; + } +}; + +//---------------------------------------------------------------------------// +/*! + * Device data for calculating micro (element) cross sections. + */ +template +struct ElectroNuclearData +{ + template + using Items = Collection; + template + using ElementItems = Collection; + + //// MEMBER DATA //// + + // Scalar data + ElectroNuclearScalars scalars; + + // Microscopic cross sections using parameterized data + ElementItems micro_xs; + Items reals; + + //! Whether the data are assigned + explicit CELER_FUNCTION operator bool() const + { + return scalars && !micro_xs.empty() && !reals.empty(); + } + + //! Assign from another set of data + template + ElectroNuclearData& operator=(ElectroNuclearData const& other) + { + CELER_EXPECT(other); + scalars = other.scalars; + micro_xs = other.micro_xs; + reals = other.reals; + + return *this; + } +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/executor/ElectroNuclearExecutor.hh b/src/celeritas/em/executor/ElectroNuclearExecutor.hh new file mode 100644 index 0000000000..ee74b01c70 --- /dev/null +++ b/src/celeritas/em/executor/ElectroNuclearExecutor.hh @@ -0,0 +1,61 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/executor/ElectroNuclearExecutor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Assert.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" +#include "celeritas/em/interactor/ElectroNuclearInteractor.hh" +#include "celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh" +#include "celeritas/global/CoreTrackView.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/random/ElementSelector.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +struct ElectroNuclearExecutor +{ + inline CELER_FUNCTION Interaction + operator()(celeritas::CoreTrackView const& track); + + ElectroNuclearRef params; +}; + +//---------------------------------------------------------------------------// +/*! + * Apply the ElectroNuclearInteractor to the current track. + */ +CELER_FUNCTION Interaction +ElectroNuclearExecutor::operator()(CoreTrackView const& track) +{ + auto particle = track.particle(); + + // Select a target element + auto material = track.material().material_record(); + auto elcomp_id = track.physics_step().element(); + if (!elcomp_id) + { + // Sample an element (based on element cross sections on the fly) + ElementSelector select_el( + material, + ElectroNuclearMicroXsCalculator{params, particle.energy()}, + track.material().element_scratch()); + elcomp_id = select_el(rng); + CELER_ASSERT(elcomp_id); + track.physics_step().element(elcomp_id); + } + + // Construct the interactor + ElectroNuclearInteractor interact(params, particle); + + // Execute the interactor + return interact(); +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/interactor/ElectroNuclearInteractor.hh b/src/celeritas/em/interactor/ElectroNuclearInteractor.hh new file mode 100644 index 0000000000..3224f1560b --- /dev/null +++ b/src/celeritas/em/interactor/ElectroNuclearInteractor.hh @@ -0,0 +1,65 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/interactor/ElectroNuclearInteractor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/phys/ParticleTrackView.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Handle the electro-nuclear interaction using G4ElectroVDNuclearModel. + * + * The electro-nuclear interaction requires hadronic models for the final + * state generation, as described in section 45.2 of the Geant4 physics manual. + * When the electro-nuclear process is selected, the electromagnetic vertex + * of the electro-nucleus reaction is computed and the virtual photon is + * generated. The status is set to onload::electro_nuclear and the post step + * action of the converted real photon will be handled by Geant4, while the + * primary electron or position continues to be tracked. + */ +class ElectroNuclearInteractor +{ + public: + // Construct from shared and state data + inline CELER_FUNCTION + ElectroNuclearInteractor(NativeCRef const& shared, + ParticleTrackView const& particle); + + // Sample an interaction + inline CELER_FUNCTION Interaction operator()(); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data, and a target nucleus. + */ +CELER_FUNCTION +ElectroNuclearInteractor::ElectroNuclearInteractor( + NativeCRef const& shared, + ParticleTrackView const& particle) +{ + CELER_EXPECT(particle.particle_id() == shared.scalars.electron_id + || particle.particle_id() == shared.scalars.positron_id); +} + +//---------------------------------------------------------------------------// +/*! + * Onload the electro-nuclear interaction. + */ +CELER_FUNCTION Interaction ElectroNuclearInteractor::operator()() +{ + return Interaction::from_onloaded(); +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/interactor/GammaNuclearInteractor.hh b/src/celeritas/em/interactor/GammaNuclearInteractor.hh index 81354de514..46eda0f66d 100644 --- a/src/celeritas/em/interactor/GammaNuclearInteractor.hh +++ b/src/celeritas/em/interactor/GammaNuclearInteractor.hh @@ -36,6 +36,7 @@ class GammaNuclearInteractor //---------------------------------------------------------------------------// // INLINE DEFINITIONS +//---------------------------------------------------------------------------// /*! * Construct with shared and state data, and a target nucleus. */ diff --git a/src/celeritas/em/model/ElectroNuclearModel.cc b/src/celeritas/em/model/ElectroNuclearModel.cc new file mode 100644 index 0000000000..a77c24d399 --- /dev/null +++ b/src/celeritas/em/model/ElectroNuclearModel.cc @@ -0,0 +1,154 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/model/ElectroNuclearModel.cc +//---------------------------------------------------------------------------// +#include "ElectroNuclearModel.hh" + +#include "corecel/Types.hh" +#include "corecel/grid/VectorUtils.hh" +#include "corecel/math/Quantity.hh" +#include "celeritas/g4/EmExtraPhysicsHelper.hh" +#include "celeritas/global/CoreParams.hh" +#include "celeritas/global/CoreState.hh" +#include "celeritas/grid/NonuniformGridInserter.hh" +#include "celeritas/mat/MaterialParams.hh" +#include "celeritas/phys/InteractionApplier.hh" +#include "celeritas/phys/PDGNumber.hh" +#include "celeritas/phys/ParticleParams.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Construct from model ID and other necessary data. + */ +ElectroNuclearModel::ElectroNuclearModel(ActionId id, + ParticleParams const& particles, + MaterialParams const& materials) + : StaticConcreteAction(id, "electro-nuclear", "interact by electro-nuclear") +{ + CELER_EXPECT(id); + + HostVal data; + + helper_ = std::make_shared(); + + // Save IDs + data.scalars.electron_id = particles.find(pdg::electron()); + data.scalars.positron_id = particles.find(pdg::positron()); + + CELER_VALIDATE(data.scalars.electron_id && data.scalars.positron_id, + << "missing particles (required for " << this->description() + << ")"); + + // Electro-nuclear element cross section data + NonuniformGridInserter insert_micro_xs{&data.reals, &data.micro_xs}; + + double const emin = data.scalars.min_valid_energy().value(); + double const emax = data.scalars.max_valid_energy().value(); + for (auto el_id : range(ElementId{materials.num_elements()})) + { + AtomicNumber z = materials.get(el_id).atomic_number(); + + // Build element cross sections + insert_micro_xs(this->calc_micro_xs(z, emin, emax)); + } + CELER_ASSERT(data.micro_xs.size() == materials.num_elements()); + + // Move to mirrored data, copying to device + data_ = CollectionMirror{std::move(data)}; + CELER_ENSURE(this->data_); +} + +//---------------------------------------------------------------------------// +/*! + * Particle types and energy ranges that this model applies to. + */ +auto ElectroNuclearModel::applicability() const -> SetApplicability +{ + Applicability electron_nuclear_appli; + electron_nuclear_appli.particle = this->host_ref().scalars.electron_id; + electron_nuclear_appli.lower = this->host_ref().scalars.min_valid_energy(); + electron_nuclear_appli.upper = this->host_ref().scalars.max_valid_energy(); + + Applicability positron_nuclear_appli = electron_nuclear_appli; + positron_nuclear_appli.particle = this->host_ref().scalars.positron_id; + + return {electron_nuclear_appli, positron_nuclear_appli}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the microscopic cross sections for the given particle and material. + */ +auto ElectroNuclearModel::micro_xs(Applicability) const -> XsTable +{ + // Cross sections are calculated on the fly + return {}; +} + +//---------------------------------------------------------------------------// +//!@{ +/*! + * Apply the interaction kernel. + */ +void ElectroNuclearModel::step(CoreParams const&, CoreStateHost&) const +{ + CELER_NOT_IMPLEMENTED("Electro-nuclear inelastic interaction"); +} + +//---------------------------------------------------------------------------// +#if !CELER_USE_DEVICE +void ElectroNuclearModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_CONFIGURED("CUDA OR HIP"); +} +#endif + +//---------------------------------------------------------------------------// +/*! + * Build electro-nuclear element cross sections using G4ElectroNuclearXS. + * + * Tabulate cross sections using separate parameterizations for the high energy + * region (emin < E < 50 GeV) and the ultra high energy region up to the + * maximum valid energy (emax). The numbers of bins are chosen to adequately + * capture both parameterized points (336 bins from 2.0612 MeV to 50 GeV) and + * calculations used in G4ElectroNuclearCrossSection, which can be made + * configurable if needed (TODO). + */ +inp::Grid ElectroNuclearModel::calc_micro_xs(AtomicNumber z, + double emin, + double emax) const +{ + CELER_EXPECT(z); + + inp::Grid result; + + // Upper limit of parameterizations of electro-nuclear cross section [Mev] + double const emid = 5e+4; + + size_type nbin_total = 300; + size_type nbin_ultra = 50; + result.y.resize(nbin_total); + + result.x = geomspace(emin, emid, nbin_total - nbin_ultra); + result.x.pop_back(); + auto ultra = geomspace(emid, emax, nbin_ultra + 1); + result.x.insert(result.x.end(), ultra.begin(), ultra.end()); + + // Tabulate the cross section from emin to emax + for (size_type i = 0; i < nbin_total; ++i) + { + auto xs = helper_->calc_electro_nuclear_xs( + z, MevEnergy{static_cast(result.x[i])}); + result.y[i] + = native_value_to(native_value_from(xs)).value(); + } + + return result; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/model/ElectroNuclearModel.cu b/src/celeritas/em/model/ElectroNuclearModel.cu new file mode 100644 index 0000000000..ff1820cfdc --- /dev/null +++ b/src/celeritas/em/model/ElectroNuclearModel.cu @@ -0,0 +1,24 @@ +//------------------------------ -*- cuda -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/model/ElectroNuclearModel.cu +//---------------------------------------------------------------------------// +#include "ElectroNuclearModel.hh" + +#include "celeritas/global/CoreParams.hh" +#include "celeritas/global/CoreState.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Interact with device data. + */ +void ElectroNuclearModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_IMPLEMENTED("Electro-nuclear interaction"); +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/model/ElectroNuclearModel.hh b/src/celeritas/em/model/ElectroNuclearModel.hh new file mode 100644 index 0000000000..c48e6eb31f --- /dev/null +++ b/src/celeritas/em/model/ElectroNuclearModel.hh @@ -0,0 +1,81 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/model/ElectroNuclearModel.hh +//---------------------------------------------------------------------------// +#pragma once + +#include + +#include "corecel/data/CollectionMirror.hh" +#include "corecel/inp/Grid.hh" +#include "celeritas/Quantities.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/phys/AtomicNumber.hh" +#include "celeritas/phys/Model.hh" + +namespace celeritas +{ +class MaterialParams; +class ParticleParams; + +class EmExtraPhysicsHelper; + +//---------------------------------------------------------------------------// +/*! + * Set up and launch the electro-nuclear model interaction. + */ +class ElectroNuclearModel final : public Model, public StaticConcreteAction +{ + public: + //!@{ + using MevEnergy = units::MevEnergy; + using HostRef = HostCRef; + using DeviceRef = DeviceCRef; + //!@} + + public: + // Construct from model ID and other necessary data + ElectroNuclearModel(ActionId id, + ParticleParams const& particles, + MaterialParams const& materials); + + // Particle types and energy ranges that this model applies to + SetApplicability applicability() const final; + + // Get the microscopic cross sections for the given particle and material + XsTable micro_xs(Applicability) const final; + + //! Apply the interaction kernel to host data + void step(CoreParams const&, CoreStateHost&) const final; + + // Apply the interaction kernel to device data + void step(CoreParams const&, CoreStateDevice&) const final; + + //!@{ + //! Access model data + HostRef const& host_ref() const { return data_.host_ref(); } + DeviceRef const& device_ref() const { return data_.device_ref(); } + //!@} + + private: + //// DATA //// + std::shared_ptr helper_; + + // Host/device storage and reference + CollectionMirror data_; + + //// TYPES //// + + using HostXsData = HostVal; + + //// HELPER FUNCTIONS //// + + inp::Grid + calc_micro_xs(AtomicNumber atomic_number, double emin, double emax) const; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/model/GammaNuclearModel.cc b/src/celeritas/em/model/GammaNuclearModel.cc index e4591a3615..1cff6e885b 100644 --- a/src/celeritas/em/model/GammaNuclearModel.cc +++ b/src/celeritas/em/model/GammaNuclearModel.cc @@ -44,9 +44,6 @@ GammaNuclearModel::GammaNuclearModel(ActionId id, << "missing gamma (required for " << this->description() << ")"); - // Save particle properties - CELER_EXPECT(data.scalars); - // Load gamma-nuclear element cross section data NonuniformGridInserter insert_xs_iaea{&data.reals, &data.xs_iaea}; NonuniformGridInserter insert_xs_chips{&data.reals, &data.xs_chips}; @@ -115,6 +112,15 @@ void GammaNuclearModel::step(CoreParams const&, CoreStateDevice&) const //---------------------------------------------------------------------------// /*! * Build CHIPS gamma-nuclear element cross sections using G4GammaNuclearXS. + * + * Tabulate cross sections using separate parameterizations for the high energy + * region (emin < E < 50 GeV) and the ultra high energy region up to the + * maximum valid energy (emax). The numbers of bins are chosen to adequately + * capture both the parameterized points (224 bins from 106 MeV to 50 GeV) and + * the calculations used in G4PhotoNuclearCrossSection, which can be made + * configurable if needed (TODO). Note that the linear interpolation between + * the upper limit of the IAEA cross-section data and 150 MeV, as used in + * G4GammaNuclearXS, is also included in the tabulation. */ inp::Grid GammaNuclearModel::calc_chips_xs(AtomicNumber z, double emin, double emax) const @@ -123,16 +129,6 @@ GammaNuclearModel::calc_chips_xs(AtomicNumber z, double emin, double emax) const inp::Grid result; - // Tabulate cross sections using separate parameterizations for the high - // energy region (emin < E < 50 GeV) and the ultra high energy region up - // to the maximum valid energy (emax). The numbers of bins are chosen to - // adequately capture both the parameterized points (224 bins from 106 MeV - // to 50 GeV) and the calculations used in G4PhotoNuclearCrossSection, - // which can be made configurable if needed (TODO). Note that the linear - // interpolation between the upper limit of the IAEA cross-section data - // and 150 MeV, as used in G4GammaNuclearXS, is also included in this - // tabulation. - // Upper limit of parameterizations for the high-energy region (50 GeV) double const emid = 5e+4; diff --git a/src/celeritas/em/process/ElectroNuclearProcess.cc b/src/celeritas/em/process/ElectroNuclearProcess.cc new file mode 100644 index 0000000000..7a652d4be9 --- /dev/null +++ b/src/celeritas/em/process/ElectroNuclearProcess.cc @@ -0,0 +1,65 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/process/ElectroNuclearProcess.cc +//---------------------------------------------------------------------------// +#include "ElectroNuclearProcess.hh" + +#include "corecel/Assert.hh" +#include "celeritas/em/model/ElectroNuclearModel.hh" +#include "celeritas/phys/PDGNumber.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Construct from host data. + */ +ElectroNuclearProcess::ElectroNuclearProcess(SPConstParticles particles, + SPConstMaterials materials) + : particles_(std::move(particles)), materials_(std::move(materials)) +{ + CELER_EXPECT(particles_); + CELER_EXPECT(materials_); +} + +//---------------------------------------------------------------------------// +/*! + * Construct the models associated with this process. + */ +auto ElectroNuclearProcess::build_models(ActionIdIter id) const -> VecModel +{ + return {std::make_shared( + *id++, *particles_, *materials_)}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the interaction cross sections for the given energy range. + */ +auto ElectroNuclearProcess::macro_xs(Applicability) const -> XsGrid +{ + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the energy loss for the given energy range. + */ +auto ElectroNuclearProcess::energy_loss(Applicability) const -> EnergyLossGrid +{ + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Name of the process. + */ +std::string_view ElectroNuclearProcess::label() const +{ + return "Electro nuclear"; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/process/ElectroNuclearProcess.hh b/src/celeritas/em/process/ElectroNuclearProcess.hh new file mode 100644 index 0000000000..34c74073e4 --- /dev/null +++ b/src/celeritas/em/process/ElectroNuclearProcess.hh @@ -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/em/process/ElectroNuclearProcess.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include + +#include "corecel/inp/Grid.hh" +#include "celeritas/mat/MaterialParams.hh" +#include "celeritas/phys/Applicability.hh" +#include "celeritas/phys/ParticleParams.hh" +#include "celeritas/phys/Process.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Electro-nuclear process. + */ +class ElectroNuclearProcess : public Process +{ + public: + //!@{ + //! \name Type aliases + using SPConstParticles = std::shared_ptr; + using SPConstMaterials = std::shared_ptr; + //!@} + + public: + // Construct from particle, material, and external cross section data + ElectroNuclearProcess(SPConstParticles particles, + SPConstMaterials materials); + + // Construct the models associated with this process + VecModel build_models(ActionIdIter start_id) const final; + + // Get the interaction cross sections for the given energy range + XsGrid macro_xs(Applicability range) const final; + + // Get the energy loss for the given energy range + EnergyLossGrid energy_loss(Applicability range) const final; + + //! Whether the integral method can be used to sample interaction length + bool supports_integral_xs() const final { return false; } + + //! Whether the process applies when the particle is stopped + bool applies_at_rest() const final { return false; } + + // Name of the process + std::string_view label() const final; + + private: + SPConstParticles particles_; + SPConstMaterials materials_; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh b/src/celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh new file mode 100644 index 0000000000..e707ca3aeb --- /dev/null +++ b/src/celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh @@ -0,0 +1,83 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/Types.hh" +#include "celeritas/Quantities.hh" +#include "celeritas/Types.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" +#include "celeritas/grid/NonuniformGridCalculator.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Calculate electro-nuclear cross sections. + */ +class ElectroNuclearMicroXsCalculator +{ + public: + //!@{ + //! \name Type aliases + using ParamsRef = NativeCRef; + using Energy = units::MevEnergy; + using BarnXs = units::BarnXs; + //!@} + + public: + // Construct with shared and state data + inline CELER_FUNCTION + ElectroNuclearMicroXsCalculator(ParamsRef const& shared, Energy energy); + + // Compute cross section + inline CELER_FUNCTION BarnXs operator()(ElementId el_id) const; + + private: + // Shared cross section data + ParamsRef const& data_; + // Incident particle energy + real_type const inc_energy_; +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data. + */ +CELER_FUNCTION +ElectroNuclearMicroXsCalculator::ElectroNuclearMicroXsCalculator( + ParamsRef const& data, Energy energy) + : data_(data), inc_energy_(energy.value()) +{ +} + +//---------------------------------------------------------------------------// +/*! + * Compute microscopic electro-nuclear cross section at the given particle + * energy. + */ +CELER_FUNCTION +auto ElectroNuclearMicroXsCalculator::operator()(ElementId el_id) const + -> BarnXs +{ + NonuniformGridRecord grid; + + // Use tabulated electro-nuclear micro cross sections + CELER_EXPECT(el_id < data_.micro_xs.size()); + grid = data_.micro_xs[el_id]; + + // Calculate micro cross section at the given energy + NonuniformGridCalculator calc_micro_xs(grid, data_.reals); + real_type result = calc_micro_xs(inc_energy_); + + return BarnXs{result}; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/ext/GeantImporter.cc b/src/celeritas/ext/GeantImporter.cc index 4ab4ef64e9..5b876d6cfa 100644 --- a/src/celeritas/ext/GeantImporter.cc +++ b/src/celeritas/ext/GeantImporter.cc @@ -7,7 +7,6 @@ #include "GeantImporter.hh" #include -#include #include #include #include @@ -185,13 +184,14 @@ struct ProcessFilter //! Map particles defined in \c G4MaterialConstPropertyIndex . auto& optical_particles_map() { - static std::unordered_map const map - = {{"PROTON", pdg::proton()}, - {"DEUTERON", pdg::deuteron()}, - {"TRITON", pdg::triton()}, - {"ALPHA", pdg::alpha()}, - {"ION", pdg::ion()}, - {"ELECTRON", pdg::electron()}}; + static std::unordered_map const map = { + {"PROTON", pdg::proton()}, + {"DEUTERON", pdg::deuteron()}, + {"TRITON", pdg::triton()}, + {"ALPHA", pdg::alpha()}, + {"ION", pdg::ion()}, + {"ELECTRON", pdg::electron()}, + }; return map; } @@ -690,7 +690,10 @@ import_optical_materials(detail::GeoOpticalIdMap const& geo_to_opt) get_property( &optical.mie.backward_g, "MIEHG_BACKWARD", ImportUnits::unitless); - CELER_ASSERT(optical); + CELER_VALIDATE(optical, + << "failed to load valid optical material data for " + "OptMatId{" + << opt_mat_id.get() << "} = " << material->GetName()); } CELER_LOG(debug) << "Loaded " << result.size() << " optical materials"; diff --git a/src/celeritas/ext/GeantSd.cc b/src/celeritas/ext/GeantSd.cc index b7ff792102..047e573896 100644 --- a/src/celeritas/ext/GeantSd.cc +++ b/src/celeritas/ext/GeantSd.cc @@ -196,7 +196,7 @@ void GeantSd::setup_volumes(inp::GeantSd const& setup) { if (G4VSensitiveDetector* sd = lv->GetSensitiveDetector()) { - // Sensitive detector is attached to the master thread + // Sensitive detector is attached to the main thread insert_volume(lv, sd); } } diff --git a/src/celeritas/ext/GeantSetup.cc b/src/celeritas/ext/GeantSetup.cc index 56381b97d6..28f071b74e 100644 --- a/src/celeritas/ext/GeantSetup.cc +++ b/src/celeritas/ext/GeantSetup.cc @@ -18,9 +18,6 @@ #include #include #include -#if G4VERSION_NUMBER >= 1070 -# include -#endif #if G4VERSION_NUMBER >= 1100 # include #endif @@ -91,9 +88,6 @@ GeantSetup::GeantSetup(std::string const& gdml_filename, Options options) "execution"); ++geant_launch_count; - // Disable signal handling - disable_geant_signal_handler(); - #if G4VERSION_NUMBER >= 1100 run_manager_.reset( G4RunManagerFactory::CreateRunManager(G4RunManagerType::Serial)); diff --git a/src/celeritas/ext/RootInterfaceLinkDef.h b/src/celeritas/ext/RootInterfaceLinkDef.h index 3e1ac8f498..0425bc3110 100644 --- a/src/celeritas/ext/RootInterfaceLinkDef.h +++ b/src/celeritas/ext/RootInterfaceLinkDef.h @@ -51,10 +51,11 @@ #pragma link C++ class celeritas::inp::Grid+; #pragma link C++ class celeritas::inp::GridReflection+; #pragma link C++ class celeritas::inp::Interpolation+; -#pragma link C++ class celeritas::inp::MucfPhysics+; -#pragma link C++ class celeritas::inp::MucfCycleRate+; #pragma link C++ class celeritas::inp::MucfAtomTransferRate+; #pragma link C++ class celeritas::inp::MucfAtomSpinFlipRate+; +#pragma link C++ class celeritas::inp::MucfCycleRate+; +#pragma link C++ class celeritas::inp::MucfPhysics+; +#pragma link C++ class celeritas::inp::MucfScalars+; #pragma link C++ class celeritas::inp::MuPairProductionEnergyTransferTable+; #pragma link C++ class celeritas::inp::NoRoughness+; #pragma link C++ class celeritas::inp::OpticalPhysics+; @@ -82,6 +83,9 @@ // Quantities #pragma link C++ class celeritas::Quantity+; #pragma link C++ class celeritas::Quantity+; +#pragma link C++ class celeritas::Quantity+; +#pragma link C++ class celeritas::Quantity+; + // Event data used by Geant4/Celeritas offloading applications #pragma link C++ class celeritas::EventData+; diff --git a/src/celeritas/field/CartMapField.covfie.hh b/src/celeritas/field/CartMapField.covfie.hh index c3af1c3825..9649f3a77f 100644 --- a/src/celeritas/field/CartMapField.covfie.hh +++ b/src/celeritas/field/CartMapField.covfie.hh @@ -6,14 +6,12 @@ //---------------------------------------------------------------------------// #pragma once -#include - #include "corecel/Macros.hh" #include "corecel/Types.hh" #include "corecel/cont/Array.hh" -#include "corecel/cont/Range.hh" #include "celeritas/Types.hh" -#include "celeritas/field/CartMapFieldData.hh" + +#include "CartMapFieldData.hh" // IWYU pragma: keep #include "detail/CovfieFieldTraits.hh" @@ -30,19 +28,19 @@ class CartMapField //! \name Type aliases using real_type = float; using Real3 = Array; - using FieldParamsRef = NativeCRef; + using ParamsRef = NativeCRef; //!@} public: // Construct with the shared map data - inline CELER_FUNCTION explicit CartMapField(FieldParamsRef const& shared); + inline CELER_FUNCTION explicit CartMapField(ParamsRef const& shared); // Evaluate the magnetic field value for the given position CELER_FUNCTION inline Real3 operator()(Real3 const& pos) const; private: - using field_view_t = FieldParamsRef::view_t; + using field_view_t = ParamsRef::view_t; field_view_t const& field_; }; @@ -53,8 +51,7 @@ class CartMapField * Construct with the shared magnetic field map data. */ CELER_FUNCTION -CartMapField::CartMapField(FieldParamsRef const& shared) - : field_{shared.get_view()} +CartMapField::CartMapField(ParamsRef const& shared) : field_{shared.get_view()} { } diff --git a/src/celeritas/field/CylMapField.hh b/src/celeritas/field/CylMapField.hh index eccbee69ce..e9491022d5 100644 --- a/src/celeritas/field/CylMapField.hh +++ b/src/celeritas/field/CylMapField.hh @@ -39,12 +39,12 @@ class CylMapField //! \name Type aliases using real_type = cylmap_real_type; using Real3 = Array; - using FieldParamsRef = NativeCRef; + using ParamsRef = NativeCRef; //!@} public: // Construct with the shared map data - inline CELER_FUNCTION explicit CylMapField(FieldParamsRef const& shared); + inline CELER_FUNCTION explicit CylMapField(ParamsRef const& shared); // Evaluate the magnetic field value for the given position CELER_FUNCTION @@ -52,7 +52,7 @@ class CylMapField private: // Shared constant field map - FieldParamsRef const& params_; + ParamsRef const& params_; NonuniformGrid const grid_r_; NonuniformGrid const grid_phi_; @@ -66,7 +66,7 @@ class CylMapField * Construct with the shared magnetic field map data. */ CELER_FUNCTION -CylMapField::CylMapField(FieldParamsRef const& params) +CylMapField::CylMapField(ParamsRef const& params) : params_{params} , grid_r_{params_.grids.axes[CylAxis::r], params_.grids.storage} , grid_phi_{params_.grids.axes[CylAxis::phi], params_.grids.storage} diff --git a/src/celeritas/field/DormandPrinceIntegrator.hh b/src/celeritas/field/DormandPrinceIntegrator.hh index 0508669577..47c927b92d 100644 --- a/src/celeritas/field/DormandPrinceIntegrator.hh +++ b/src/celeritas/field/DormandPrinceIntegrator.hh @@ -78,7 +78,7 @@ class DormandPrinceIntegrator { } - // Adaptive step size control + // Perform numerical integration over a step given an initial state CELER_FUNCTION result_type operator()(real_type step, OdeState const& beg_state) const; diff --git a/src/celeritas/field/FieldPropagator.hh b/src/celeritas/field/FieldPropagator.hh index 6ccece3eaf..ac7a1abe3f 100644 --- a/src/celeritas/field/FieldPropagator.hh +++ b/src/celeritas/field/FieldPropagator.hh @@ -54,15 +54,10 @@ class FieldPropagator ParticleTrackView const& particle, GTV&& geo); - // Move track to next volume boundary. - inline CELER_FUNCTION result_type operator()(); - // Move track up to a user-provided distance, or to the next boundary inline CELER_FUNCTION result_type operator()(real_type dist); - //! Whether it's possible to have tracks that are looping - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return true; } - + private: //! Limit on substeps inline CELER_FUNCTION short int max_substeps() const; @@ -108,17 +103,6 @@ CELER_FUNCTION FieldPropagator::FieldPropagator( state_.mom = value_as(particle.momentum()) * geo_.dir(); } -//---------------------------------------------------------------------------// -/*! - * Propagate a charged particle until it hits a boundary. - */ -template -CELER_FUNCTION auto FieldPropagator::operator()() - -> result_type -{ - return (*this)(numeric_limits::infinity()); -} - //---------------------------------------------------------------------------// /*! * Propagate a charged particle in a field. diff --git a/src/celeritas/field/LinearPropagator.hh b/src/celeritas/field/LinearPropagator.hh index 4285ad1b93..7463144570 100644 --- a/src/celeritas/field/LinearPropagator.hh +++ b/src/celeritas/field/LinearPropagator.hh @@ -31,15 +31,9 @@ class LinearPropagator { } - // Move track to next volume boundary. - inline CELER_FUNCTION result_type operator()(); - // Move track up to a user-provided distance, up to the next boundary inline CELER_FUNCTION result_type operator()(real_type dist); - //! Whether it's possible to have tracks that are looping - static CELER_CONSTEXPR_FUNCTION bool tracks_can_loop() { return false; } - private: GTV geo_; }; @@ -50,22 +44,6 @@ class LinearPropagator template CELER_FUNCTION LinearPropagator(GTV&&) -> LinearPropagator; -//---------------------------------------------------------------------------// -/*! - * Move track to next volume boundary. - */ -template -CELER_FUNCTION auto LinearPropagator::operator()() -> result_type -{ - CELER_EXPECT(!geo_.is_outside()); - - result_type result = geo_.find_next_step(); - CELER_ASSERT(result.boundary); - geo_.move_to_boundary(); - - return result; -} - //---------------------------------------------------------------------------// /*! * Move track by a user-provided distance up to the next boundary. diff --git a/src/celeritas/field/RZMapField.hh b/src/celeritas/field/RZMapField.hh index e2563919d1..ce3dee6dec 100644 --- a/src/celeritas/field/RZMapField.hh +++ b/src/celeritas/field/RZMapField.hh @@ -30,12 +30,12 @@ class RZMapField //!@{ //! \name Type aliases using Real3 = Array; - using FieldParamsRef = NativeCRef; + using ParamsRef = NativeCRef; //!@} public: // Construct with the shared map data - inline CELER_FUNCTION explicit RZMapField(FieldParamsRef const& shared); + inline CELER_FUNCTION explicit RZMapField(ParamsRef const& shared); // Evaluate the magnetic field value for the given position CELER_FUNCTION @@ -43,7 +43,7 @@ class RZMapField private: // Shared constant field map - FieldParamsRef const& params_; + ParamsRef const& params_; UniformGrid const grid_r_; UniformGrid const grid_z_; @@ -56,7 +56,7 @@ class RZMapField * Construct with the shared magnetic field map data. */ CELER_FUNCTION -RZMapField::RZMapField(FieldParamsRef const& params) +RZMapField::RZMapField(ParamsRef const& params) : params_(params) , grid_r_(params_.grids.data_r) , grid_z_(params_.grids.data_z) diff --git a/src/celeritas/field/UniformField.hh b/src/celeritas/field/UniformField.hh index 11c22960eb..8863264d0d 100644 --- a/src/celeritas/field/UniformField.hh +++ b/src/celeritas/field/UniformField.hh @@ -8,6 +8,7 @@ #include "corecel/cont/Array.hh" #include "celeritas/Types.hh" +#include "celeritas/field/UniformFieldData.hh" namespace celeritas { @@ -21,9 +22,17 @@ namespace celeritas class UniformField { public: + using ParamsRef = NativeCRef; + //! Construct with a field vector explicit CELER_FUNCTION UniformField(Real3 const& value) : value_(value) {} + //! Construct with field params + explicit CELER_FUNCTION UniformField(ParamsRef const& params) + : value_(params.field) + { + } + //! Return the field at the given position CELER_FUNCTION Real3 const& operator()(Real3 const&) const { diff --git a/src/celeritas/field/detail/NotImplementedField.hh b/src/celeritas/field/detail/NotImplementedField.hh index 39e0af598f..4ed24ad881 100644 --- a/src/celeritas/field/detail/NotImplementedField.hh +++ b/src/celeritas/field/detail/NotImplementedField.hh @@ -27,12 +27,12 @@ class NotImplementedField //! \name Type aliases using real_type = float; using Real3 = Array; - using FieldParamsRef = NativeCRef; + using ParamsRef = NativeCRef; //!@} public: // Construct with the shared map data - inline CELER_FUNCTION explicit NotImplementedField(FieldParamsRef const&); + inline CELER_FUNCTION explicit NotImplementedField(ParamsRef const&); // Evaluate the magnetic field value for the given position CELER_FUNCTION @@ -40,7 +40,7 @@ class NotImplementedField }; CELER_FUNCTION -NotImplementedField::NotImplementedField(FieldParamsRef const&) +NotImplementedField::NotImplementedField(ParamsRef const&) { CELER_NOT_CONFIGURED("covfie"); } diff --git a/src/celeritas/g4/DetectorConstruction.cc b/src/celeritas/g4/DetectorConstruction.cc index 78f79a2dde..8928f6c9c4 100644 --- a/src/celeritas/g4/DetectorConstruction.cc +++ b/src/celeritas/g4/DetectorConstruction.cc @@ -37,7 +37,7 @@ DetectorConstruction::DetectorConstruction(std::string const& filename, /*! * Load geometry and sensitive detector volumes. * - * This should only be called once from the master thread, toward the very + * This should only be called once from the main thread, toward the very * beginning of the program. */ G4VPhysicalVolume* DetectorConstruction::Construct() diff --git a/src/celeritas/g4/EmExtraPhysicsHelper.cc b/src/celeritas/g4/EmExtraPhysicsHelper.cc index a087a5ef5d..45884359bb 100644 --- a/src/celeritas/g4/EmExtraPhysicsHelper.cc +++ b/src/celeritas/g4/EmExtraPhysicsHelper.cc @@ -6,6 +6,8 @@ //---------------------------------------------------------------------------// #include "EmExtraPhysicsHelper.hh" +#include +#include #include #include #include @@ -22,6 +24,8 @@ EmExtraPhysicsHelper::EmExtraPhysicsHelper() << "compiled version of Geant4 (" << G4VERSION_NUMBER << ") is too old for gamma-nuclear cross section " "calculation"); + particle_ = std::make_shared(); + en_xs_ = std::make_shared(); gn_xs_ = std::make_shared(); if constexpr (G4VERSION_NUMBER < 1120) @@ -30,6 +34,20 @@ EmExtraPhysicsHelper::EmExtraPhysicsHelper() } } +//---------------------------------------------------------------------------// +/*! + * Calculate the electro-nuclear element cross section using + * G4ElectroNuclearCrossSection. + */ +auto EmExtraPhysicsHelper::calc_electro_nuclear_xs(AtomicNumber z, + MevEnergy energy) const + -> MmSqXs +{ + particle_->SetKineticEnergy(energy.value()); + return MmSqXs{ + en_xs_->GetElementCrossSection(particle_.get(), z.get(), nullptr)}; +} + //---------------------------------------------------------------------------// /*! * Calculate the gamma-nuclear element cross section using G4GammaNuclearXS. diff --git a/src/celeritas/g4/EmExtraPhysicsHelper.hh b/src/celeritas/g4/EmExtraPhysicsHelper.hh index 8770addca3..d8a6e171b9 100644 --- a/src/celeritas/g4/EmExtraPhysicsHelper.hh +++ b/src/celeritas/g4/EmExtraPhysicsHelper.hh @@ -16,13 +16,15 @@ #include "celeritas/UnitTypes.hh" #include "celeritas/phys/AtomicNumber.hh" +class G4DynamicParticle; +class G4ElectroNuclearCrossSection; class G4GammaNuclearXS; namespace celeritas { //---------------------------------------------------------------------------// /*! - * Calculate Geant4 gamma-nuclear cross sections. + * Calculate Geant4 gamma-nuclear and electro-nuclear cross sections. * * This class primarily severs as a wrapper around Geant4 cross section * calculation methods, which are not directly accessible from Celeritas EM @@ -41,11 +43,16 @@ class EmExtraPhysicsHelper // Construct EM extra physics helper EmExtraPhysicsHelper(); + // Calculate electro-nuclear element cross section + MmSqXs calc_electro_nuclear_xs(AtomicNumber z, MevEnergy energy) const; + // Calculate gamma-nuclear element cross section MmSqXs calc_gamma_nuclear_xs(AtomicNumber z, MevEnergy energy) const; private: //// DATA //// + std::shared_ptr particle_; + std::shared_ptr en_xs_; std::shared_ptr gn_xs_; }; @@ -66,6 +73,12 @@ inline EmExtraPhysicsHelper::EmExtraPhysicsHelper() # endif } +inline EmExtraPhysicsHelper::MmSqXs +EmExtraPhysicsHelper::calc_electro_nuclear_xs(AtomicNumber, MevEnergy) const +{ + CELER_ASSERT_UNREACHABLE(); +} + inline EmExtraPhysicsHelper::MmSqXs EmExtraPhysicsHelper::calc_gamma_nuclear_xs(AtomicNumber, MevEnergy) const { diff --git a/src/celeritas/g4/SupportedOpticalPhysics.hh b/src/celeritas/g4/SupportedOpticalPhysics.hh index befc992924..ae874d9bc5 100644 --- a/src/celeritas/g4/SupportedOpticalPhysics.hh +++ b/src/celeritas/g4/SupportedOpticalPhysics.hh @@ -14,7 +14,7 @@ namespace celeritas { //---------------------------------------------------------------------------// /*! - * Construct Celeritas-supported optical physics. + * Construct Celeritas-supported optical physics processes. */ class SupportedOpticalPhysics : public G4VPhysicsConstructor { diff --git a/src/celeritas/global/CoreParams.cc b/src/celeritas/global/CoreParams.cc index 19a91ee519..96615f3ac3 100644 --- a/src/celeritas/global/CoreParams.cc +++ b/src/celeritas/global/CoreParams.cc @@ -15,21 +15,12 @@ #include "corecel/Assert.hh" #include "corecel/data/AuxParamsRegistry.hh" // IWYU pragma: keep #include "corecel/data/Ref.hh" -#include "corecel/io/BuildOutput.hh" #include "corecel/io/Logger.hh" #include "corecel/io/OutputInterfaceAdapter.hh" #include "corecel/io/OutputRegistry.hh" // IWYU pragma: keep #include "corecel/random/params/RngParams.hh" // IWYU pragma: keep #include "corecel/sys/ActionRegistry.hh" // IWYU pragma: keep #include "corecel/sys/ActionRegistryOutput.hh" -#include "corecel/sys/Device.hh" -#include "corecel/sys/DeviceIO.json.hh" -#include "corecel/sys/Environment.hh" -#include "corecel/sys/EnvironmentIO.json.hh" -#include "corecel/sys/KernelRegistry.hh" -#include "corecel/sys/KernelRegistryIO.json.hh" -#include "corecel/sys/MemRegistry.hh" -#include "corecel/sys/MemRegistryIO.json.hh" #include "corecel/sys/MpiCommunicator.hh" #include "corecel/sys/ScopedMem.hh" #include "geocel/GeoParamsOutput.hh" @@ -332,18 +323,9 @@ CoreParams::CoreParams(Input input) : input_(std::move(input)) } // Save system diagnostic information - input_.output_reg->insert(OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, "device", celeritas::device())); - input_.output_reg->insert( - OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, - "kernels", - celeritas::kernel_registry())); - input_.output_reg->insert(OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, "memory", celeritas::mem_registry())); - input_.output_reg->insert(OutputInterfaceAdapter::from_const_ref( - OutputInterface::Category::system, "environ", celeritas::environment())); - input_.output_reg->insert(std::make_shared()); + insert_system_diagnostics(*input_.output_reg); + + // Save core sizes input_.output_reg->insert( OutputInterfaceAdapter::from_rvalue_ref( OutputInterface::Category::internal, diff --git a/src/celeritas/global/CoreState.cc b/src/celeritas/global/CoreState.cc index f83378725b..4322ea9a62 100644 --- a/src/celeritas/global/CoreState.cc +++ b/src/celeritas/global/CoreState.cc @@ -52,7 +52,9 @@ CoreState::CoreState(CoreParams const& params, states_ = CollectionStateStore( params.host_ref(), stream_id, num_track_slots); - counters_.num_vacancies = num_track_slots; + auto counters = CoreStateCounters{}; + counters.num_vacancies = num_track_slots; + this->sync_put_counters(counters); if constexpr (M == MemSpace::device) { @@ -114,7 +116,7 @@ CoreState::~CoreState() template void CoreState::warming_up(bool new_state) { - CELER_EXPECT(!new_state || counters_.num_active == 0); + CELER_EXPECT(!new_state || this->sync_get_counters().num_active == 0); warming_up_ = new_state; } @@ -133,19 +135,64 @@ Range CoreState::get_action_range(ActionId action_id) const return {thread_offsets[action_id], thread_offsets[action_id + 1]}; } +//---------------------------------------------------------------------------// +/*! + * Copy the core state counters from the device to the host. For host-only + * code, the counters reside on the host, so this just returns a + * CoreStateCounters object. Note that it does not return a reference, so + * sync_put_counters() must be used if any counters change. + */ +template +CoreStateCounters CoreState::sync_get_counters() const +{ + auto* counters + = static_cast(this->ref().init.counters.data()); + CELER_ASSERT(counters); + if constexpr (M == MemSpace::device) + { + auto result + = ItemCopier{this->stream_id()}(counters); + device().stream(this->stream_id()).sync(); + return result; + } + return *counters; +} + +//---------------------------------------------------------------------------// +/*! + * Copy the core state counters from the host to the device. For host-only + * code, this function copies a CoreStateCounter object into the CoreState + * object, which is needed when any of the counters change, because + * sync_get_counters() doesn't return a reference. + */ +template +void CoreState::sync_put_counters(CoreStateCounters const& host_counters) +{ + auto* counters + = static_cast(this->ref().init.counters.data()); + CELER_ASSERT(counters); + Copier copy{{counters, 1}, this->stream_id()}; + copy(MemSpace::host, {&host_counters, 1}); + if constexpr (M == MemSpace::device) + { + device().stream(this->stream_id()).sync(); + } +} + //---------------------------------------------------------------------------// /*! * Reset the state data. * * This clears the state counters and initializes the necessary state data so - * the state can be reused for a new event. This should only be necessary if + * the state can be reused for a new event. This should be necessary only if * the previous event aborted early. */ template void CoreState::reset() { - counters_ = CoreStateCounters{}; - counters_.num_vacancies = this->size(); + auto counters = CoreStateCounters{}; + counters.num_vacancies = this->size(); + sync_put_counters(counters); // Reset all the track slots to inactive fill(TrackStatus::inactive, &this->ref().sim.status); diff --git a/src/celeritas/global/CoreState.hh b/src/celeritas/global/CoreState.hh index f0c1d5ee80..5c18aba534 100644 --- a/src/celeritas/global/CoreState.hh +++ b/src/celeritas/global/CoreState.hh @@ -48,9 +48,14 @@ class CoreStateInterface //! Number of track slots virtual size_type size() const = 0; - //! Access track initialization counters - virtual CoreStateCounters const& counters() const = 0; - + //! Synchronize and copy track initialization counters from device to host + //! For host-only code, this replaces the old counters() function + [[nodiscard]] virtual CoreStateCounters sync_get_counters() const = 0; + + //! Synchronize and copy track initialization counters from host to device + //! For host-only code, this replaces the old counters() function + //! since we return a CoreStateCounters object instead of a reference + virtual void sync_put_counters(CoreStateCounters const&) = 0; //! Access auxiliary state data virtual AuxStateVec const& aux() const = 0; @@ -130,11 +135,13 @@ class CoreState final : public CoreStateInterface //// COUNTERS //// - //! Track initialization counters - CoreStateCounters& counters() { return counters_; } + //! Synchronize and copy track initialization counters from device to host + [[nodiscard]] CoreStateCounters sync_get_counters() const final; - //! Track initialization counters - CoreStateCounters const& counters() const final { return counters_; } + //! Synchronize and copy track initialization counters from host to device + //! For host-only code, this copies the local CoreStateCounters back to the + //! class, since sync_get_counters() doesn't return a reference + void sync_put_counters(CoreStateCounters const&) final; //// AUXILIARY DATA //// @@ -178,9 +185,6 @@ class CoreState final : public CoreStateInterface // Native pointer to ref data Ptr ptr_; - // Counters for track initialization and activity - CoreStateCounters counters_; - // User-added data associated with params SPAuxStateVec aux_state_; diff --git a/src/celeritas/global/Stepper.cc b/src/celeritas/global/Stepper.cc index fc991afeb5..97215967de 100644 --- a/src/celeritas/global/Stepper.cc +++ b/src/celeritas/global/Stepper.cc @@ -107,14 +107,14 @@ Stepper::~Stepper() = default; template void Stepper::warm_up() { - CELER_VALIDATE(state_->counters().num_active == 0, + CELER_VALIDATE(state_->sync_get_counters().num_active == 0, << "cannot warm up when state has active tracks"); ScopedProfiling profile_this{"warmup"}; state_->warming_up(true); ScopeExit on_exit_{[this] { state_->warming_up(false); }}; actions_->step(*params_, *state_); - CELER_ENSURE(state_->counters().num_active == 0); + CELER_ENSURE(state_->sync_get_counters().num_active == 0); } //---------------------------------------------------------------------------// @@ -128,9 +128,11 @@ template auto Stepper::operator()() -> result_type { ScopedProfiling profile_this{"step"}; - auto& counters = state_->counters(); + auto counters = state_->sync_get_counters(); counters.num_generated = 0; + state_->sync_put_counters(counters); actions_->step(*params_, *state_); + counters = state_->sync_get_counters(); // Get the number of track initializers and active tracks result_type result; @@ -164,7 +166,9 @@ auto Stepper::operator()(SpanConstPrimary primaries) -> result_type << "event number " << max_id->event_id.unchecked_get() << " exceeds max_events=" << params_->init()->max_events()); - state_->counters().num_pending = primaries.size(); + auto counters = state_->sync_get_counters(); + counters.num_pending = primaries.size(); + state_->sync_put_counters(counters); primaries_action_->insert(*params_, *state_, primaries); return (*this)(); @@ -180,7 +184,8 @@ auto Stepper::operator()(SpanConstPrimary primaries) -> result_type template void Stepper::kill_active() { - CELER_LOG_LOCAL(error) << "Killing " << state_->counters().num_active + CELER_LOG_LOCAL(error) << "Killing " + << state_->sync_get_counters().num_active << " active tracks"; detail::kill_active(*params_, *state_); } diff --git a/src/celeritas/global/TrackExecutor.hh b/src/celeritas/global/TrackExecutor.hh index 041558cb31..8ee00589a7 100644 --- a/src/celeritas/global/TrackExecutor.hh +++ b/src/celeritas/global/TrackExecutor.hh @@ -127,6 +127,8 @@ class ConditionalTrackExecutor return; } + // NOTE: "return value type" error means the executor function is + // incorrectly returning a value return execute_track_(track); } diff --git a/src/celeritas/grid/RangeCalculator.hh b/src/celeritas/grid/RangeCalculator.hh index 90fe2ec0f4..0635fb4e76 100644 --- a/src/celeritas/grid/RangeCalculator.hh +++ b/src/celeritas/grid/RangeCalculator.hh @@ -31,8 +31,6 @@ namespace celeritas * \f[ r = r_\mathrm{min} \sqrt{\frac{E}{E_\mathrm{min}}} * \f] - * - * \todo Construct with \c UniformGridRecord */ class RangeCalculator { diff --git a/src/celeritas/grid/RangeGridCalculator.hh b/src/celeritas/grid/RangeGridCalculator.hh index 0fd8561ead..db8f8cfeaf 100644 --- a/src/celeritas/grid/RangeGridCalculator.hh +++ b/src/celeritas/grid/RangeGridCalculator.hh @@ -40,6 +40,10 @@ namespace celeritas * points is less than 5, linear interpolation will be used instead. * * \todo support polynomial interpolation as well? + * + * \todo The calculated range does not account for "tracking cuts" (hard limits + * below which tracks are killed). The range should be adjusted so that the + * lower integral limit is the energy cutoff \c T_c. */ class RangeGridCalculator { diff --git a/src/celeritas/inp/Control.hh b/src/celeritas/inp/Control.hh index a3312c7bd0..baa9bf408b 100644 --- a/src/celeritas/inp/Control.hh +++ b/src/celeritas/inp/Control.hh @@ -75,10 +75,19 @@ struct CoreStateCapacity : StateCapacity size_type initializers{}; //! Maximum number of secondaries created per step std::optional secondaries; - - //! Maximum number of simultaneous events (zero for doing one event at a - //! time) + //! Maximum number of simultaneous events (zero for one event at a time) std::optional events; + + //! Return default values + static CoreStateCapacity from_default(bool use_device) + { + CoreStateCapacity result; + result.tracks = use_device ? 1048576 : 4096; + result.primaries = result.tracks; + result.initializers = 8 * result.tracks; + result.secondaries = 2 * result.tracks; + return result; + } }; //---------------------------------------------------------------------------// @@ -91,6 +100,16 @@ struct OpticalStateCapacity : StateCapacity { //! Maximum number of queued photon-generating steps size_type generators{}; + + //! Return default values + static OpticalStateCapacity from_default(bool use_device) + { + OpticalStateCapacity result; + result.tracks = use_device ? 1048576 : 4096; + result.primaries = 128 * result.tracks; + result.generators = 2 * result.tracks; + return result; + } }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/inp/Events.hh b/src/celeritas/inp/Events.hh index 117774e32c..f295312e09 100644 --- a/src/celeritas/inp/Events.hh +++ b/src/celeritas/inp/Events.hh @@ -119,11 +119,37 @@ struct OpticalOffloadGenerator { }; +//---------------------------------------------------------------------------// +/*! + * Generate optical photons track. + */ +struct OpticalTrackOffload +{ +}; + +//---------------------------------------------------------------------------// +/*! + * Generate optical photons directly from optical track initializers. + */ +struct OpticalDirectGenerator +{ +}; + +//---------------------------------------------------------------------------// +/*! + * Offload optical photon tracks from Geant4 to Celeritas. + */ +struct OpticalTrackOffload +{ +}; + //---------------------------------------------------------------------------// //! Mechanism for generating optical photons using OpticalGenerator = std::variant; + OpticalPrimaryGenerator, + OpticalDirectGenerator, + OpticalTrackOffload>; //---------------------------------------------------------------------------// /*! diff --git a/src/celeritas/inp/FrameworkInput.hh b/src/celeritas/inp/FrameworkInput.hh index 08cd75e69c..3cb42ee1ae 100644 --- a/src/celeritas/inp/FrameworkInput.hh +++ b/src/celeritas/inp/FrameworkInput.hh @@ -17,6 +17,7 @@ namespace celeritas namespace inp { struct Problem; +struct OpticalProblem; //---------------------------------------------------------------------------// /*! @@ -40,6 +41,9 @@ struct FrameworkInput //! User application/framework-defined adjustments std::function adjust; + + //! User application/framework-defined optical adjustments + std::function adjust_optical; }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/inp/MucfPhysics.cc b/src/celeritas/inp/MucfPhysics.cc index 9724a6e0b0..9a298eaa7b 100644 --- a/src/celeritas/inp/MucfPhysics.cc +++ b/src/celeritas/inp/MucfPhysics.cc @@ -10,6 +10,117 @@ namespace celeritas { namespace inp { +namespace +{ +//---------------------------------------------------------------------------// +/*! + * Muon energy CDF data for muon-catalyzed fusion. + */ +Grid mucf_muon_energy_cdf() +{ + Grid cdf; + cdf.interpolation.type = InterpolationType::cubic_spline; + + // Cumulative distribution data [unitless] + cdf.x = { + 0, + 0.04169381107491854, + 0.08664495114006499, + 0.14332247557003264, + 0.20456026058631915, + 0.2723127035830618, + 0.34136807817589576, + 0.41563517915309456, + 0.48990228013029324, + 0.5667752442996744, + 0.6306188925081434, + 0.6866449511400652, + 0.7309446254071662, + 0.7778501628664496, + 0.8104234527687297, + 0.8403908794788275, + 0.8618892508143323, + 0.8814332247557004, + 0.8970684039087949, + 0.903583061889251, + 1.0, + }; + + // Energy [keV] + cdf.y = { + 0, + 0.48850540675768084, + 0.8390389347819425, + 1.2521213482687141, + 1.7153033196164724, + 2.253638712180777, + 2.854653691809707, + 3.606073540073316, + 4.470346052913727, + 5.560291219507215, + 6.700556502915258, + 7.953772477101693, + 9.194596305637525, + 10.849180562221111, + 12.353474314071864, + 14.045888515617822, + 15.650634617544647, + 17.38079707555165, + 19.111008546659452, + 19.976130619913615, + 80.0, + }; + + CELER_ENSURE(cdf); + return cdf; +} + +//---------------------------------------------------------------------------// +/*! + * Cycle rate data for muon-catalyzed fusion. + */ +std::vector mucf_cycle_rates() +{ + //! \todo Implement + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Spin-flip data for muon-catalyzed fusion. + */ +std::vector mucf_atom_spin_flip_rates() +{ + //! \todo Implement + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Atom transfer data for muon-catalyzed fusion. + */ +std::vector mucf_atom_transfer_rates() +{ + //! \todo Implement + return {}; +} +//---------------------------------------------------------------------------// +} // namespace + +//---------------------------------------------------------------------------// +/*! + * Initialize with hardcoded values. + */ +MucfScalars MucfScalars::from_default() +{ + MucfScalars result; + result.protium = AmuMass{1.007825031898}; + result.deuterium = AmuMass{2.014101777844}; + result.tritium = AmuMass{3.016049281320}; + result.liquid_hydrogen_density = InvCcDensity{4.25e22}; + return result; +} + //---------------------------------------------------------------------------// /*! * Construct hardcoded muon-catalyzed fusion physics data. @@ -21,16 +132,14 @@ namespace inp MucfPhysics MucfPhysics::from_default() { MucfPhysics result; - - //! \todo Initialize hardcoded CDF data - //! \todo Initialize hardcoded cycle rate data - //! \todo Initialize hardcoded atom transfer data - //! \todo Initialize hardcoded spin flip data + result.scalars = MucfScalars::from_default(); + result.muon_energy_cdf = mucf_muon_energy_cdf(); + result.cycle_rates = mucf_cycle_rates(); + result.atom_transfer = mucf_atom_transfer_rates(); + result.atom_spin_flip = mucf_atom_spin_flip_rates(); // Temporary test dummy data to verify correct import { - result.muon_energy_cdf = Grid::from_constant(1.0); - MucfCycleRate dt_cycle; dt_cycle.molecule = MucfMuonicMolecule::deuterium_tritium; diff --git a/src/celeritas/inp/MucfPhysics.hh b/src/celeritas/inp/MucfPhysics.hh index 88c67a90e4..401e58b525 100644 --- a/src/celeritas/inp/MucfPhysics.hh +++ b/src/celeritas/inp/MucfPhysics.hh @@ -10,12 +10,43 @@ #include #include "corecel/inp/Grid.hh" -#include "celeritas/Types.hh" +#include "corecel/math/Quantity.hh" +#include "celeritas/UnitTypes.hh" +#include "celeritas/mucf/Types.hh" namespace celeritas { namespace inp { +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion scalars. + * + * Default values are the same used by Acceleron. + */ +struct MucfScalars +{ + using AmuMass = Quantity; + using InvCcDensity = Quantity; + + // Atomic masses + AmuMass protium; //!< Protium atomic mass [AMU] + AmuMass deuterium; //!< Deuterium atomic mass [AMU] + AmuMass tritium; //!< Tritium atomic mass [AMU] + InvCcDensity liquid_hydrogen_density; //!< LHD unit [1/cm^3] + + //! Whether scalars have been defined + explicit operator bool() const + { + return protium > zero_quantity() && deuterium > zero_quantity() + && tritium > zero_quantity() + && liquid_hydrogen_density > zero_quantity(); + } + + // Initialize with hardcoded values + static MucfScalars from_default(); +}; + //---------------------------------------------------------------------------// /*! * Muon-catalyzed fusion mean cycle rate data. @@ -68,6 +99,9 @@ struct MucfCycleRate struct MucfAtomTransferRate { //! \todo Implement + + //! True if data is assigned \todo Implement + explicit operator bool() const { return true; } }; //---------------------------------------------------------------------------// @@ -91,6 +125,9 @@ struct MucfAtomTransferRate struct MucfAtomSpinFlipRate { //! \todo Implement + + //! True if data is assigned \todo Implement + explicit operator bool() const { return true; } }; //---------------------------------------------------------------------------// @@ -98,7 +135,8 @@ struct MucfAtomSpinFlipRate * Muon-catalyzed fusion physics options and data import. * * Minimum requirements for muon-catalyzed fusion: - * - Muon energy CDF data, required for sampling the outgoing muCF muon, and + * - Muon energy CDF data, required for sampling the outgoing muCF muon, + * and * - Mean cycle rate data for dd, dt, and tt muonic molecules. * * Muonic atom transfer and muonic atom spin flip are secondary effects and not @@ -109,15 +147,16 @@ struct MucfPhysics template using Vec = std::vector; - Grid muon_energy_cdf; //!< CDF for outgoing muCF muon + MucfScalars scalars; + Grid muon_energy_cdf; //!< CDF for sampling the outgoing muCF muon Vec cycle_rates; //!< Mean cycle rates for muonic molecules - Vec atom_transfer; //!< Muon atom transfer rates - Vec atom_spin_flip; //!< Muon atom spin flip rates + Vec atom_transfer; //!< Muonic atom transfer rates + Vec atom_spin_flip; //!< Muonic atom spin flip rates //! Whether muon-catalyzed fusion physics is enabled explicit operator bool() const { - return muon_energy_cdf && !cycle_rates.empty(); + return scalars && muon_energy_cdf && !cycle_rates.empty(); } //! Construct hardcoded muon-catalyzed fusion physics data diff --git a/src/celeritas/inp/Physics.hh b/src/celeritas/inp/Physics.hh index d5efebe191..918efbb316 100644 --- a/src/celeritas/inp/Physics.hh +++ b/src/celeritas/inp/Physics.hh @@ -57,6 +57,8 @@ struct EmPhysics /*! * Optical physics processes, options, and surface definitions. * + * If scintillation or Cherenkov is enabled, optical photons will be generated. + * * \todo Move cherenkov/scintillation to a OpticalGenPhysics class. */ struct OpticalPhysics @@ -110,8 +112,6 @@ struct Physics //! Physics for optical photons OpticalPhysics optical; - //! Optical photon generation mechanism - OpticalGenerator optical_generator; }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/inp/Problem.hh b/src/celeritas/inp/Problem.hh index 7243ce398d..387443d4b1 100644 --- a/src/celeritas/inp/Problem.hh +++ b/src/celeritas/inp/Problem.hh @@ -76,6 +76,32 @@ struct Problem Diagnostics diagnostics; }; +//---------------------------------------------------------------------------// +/*! + * Celeritas optical-only problem input definition. + */ +struct OpticalProblem +{ + //! Geometry, material, and region definitions + Model model; + //! Physics models and options + OpticalPhysics physics; + //! Optical photon generation mechanism + OpticalGenerator generator; + //! Hard cutoffs for counters + OpticalTrackingLimits limits; + //! Per-process state sizes for optical tracking loop + OpticalStateCapacity capacity; + //! Number of streams + size_type num_streams{}; + //! Random number generator seed + unsigned int seed{}; + //! Set up step or action timers + Timers timers; + //! Write Celeritas diagnostics to this file ("-" is stdout) + std::string output_file{"-"}; +}; + //---------------------------------------------------------------------------// } // namespace inp } // namespace celeritas diff --git a/src/celeritas/io/ImportProcess.cc b/src/celeritas/io/ImportProcess.cc index fa5e036b76..beb2ab3cb8 100644 --- a/src/celeritas/io/ImportProcess.cc +++ b/src/celeritas/io/ImportProcess.cc @@ -61,6 +61,7 @@ char const* to_cstring(ImportProcessClass value) "mu_ioni", "mu_brems", "mu_pair_prod", + "electro_nuclear", "gamma_general", "gamma_nuclear", "neutron_elastic", @@ -92,6 +93,7 @@ char const* to_geant_name(ImportProcessClass value) "muIoni", // mu_ioni, "muBrems", // mu_brems, "muPairProd", // mu_pair_prod, + "ElectroNuclearProc", // electro_nuclear, "GammaGeneralProc", // gamma_general, "GammaNuclearProc", // gamma_nuclear, "neutronElasticProc", // neutron_elastic, diff --git a/src/celeritas/io/ImportProcess.hh b/src/celeritas/io/ImportProcess.hh index 1415f0d5f9..0c535ca830 100644 --- a/src/celeritas/io/ImportProcess.hh +++ b/src/celeritas/io/ImportProcess.hh @@ -68,6 +68,7 @@ enum class ImportProcessClass mu_ioni, mu_brems, mu_pair_prod, + electro_nuclear, gamma_general, // Will be decomposed into other processes gamma_nuclear, // Neutron diff --git a/src/accel/detail/OffloadWriter.hh b/src/celeritas/io/OffloadWriter.hh similarity index 93% rename from src/accel/detail/OffloadWriter.hh rename to src/celeritas/io/OffloadWriter.hh index 64d2c56a8a..2f60fe543f 100644 --- a/src/accel/detail/OffloadWriter.hh +++ b/src/celeritas/io/OffloadWriter.hh @@ -2,19 +2,17 @@ // Copyright Celeritas contributors: see top-level COPYRIGHT file for details // SPDX-License-Identifier: (Apache-2.0 OR MIT) //---------------------------------------------------------------------------// -//! \file accel/detail/OffloadWriter.hh +//! \file celeritas/io/OffloadWriter.hh //---------------------------------------------------------------------------// #pragma once #include #include -#include "celeritas/io/EventIOInterface.hh" +#include "EventIOInterface.hh" namespace celeritas { -namespace detail -{ //---------------------------------------------------------------------------// /*! * Dump primaries to a shared file, one event per flush. @@ -64,5 +62,4 @@ void OffloadWriter::operator()(argument_type primaries) } //---------------------------------------------------------------------------// -} // namespace detail } // namespace celeritas diff --git a/src/celeritas/mucf/Types.hh b/src/celeritas/mucf/Types.hh new file mode 100644 index 0000000000..0a7c410dda --- /dev/null +++ b/src/celeritas/mucf/Types.hh @@ -0,0 +1,71 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/Types.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/OpaqueId.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +// ENUMERATIONS +//---------------------------------------------------------------------------// +/*! + * Muonic atom selection from material data. This is *not* intended to be used + * by the transport loop. + */ +enum class MucfMuonicAtom +{ + deuterium, + tritium, + size_ +}; + +//---------------------------------------------------------------------------// +/*! + * Muonic molecule selection from material data. This is *not* intended to be + * used by the transport loop. + */ +enum class MucfMuonicMolecule +{ + deuterium_deuterium, + deuterium_tritium, + tritium_tritium, + size_ +}; + +//---------------------------------------------------------------------------// +/*! + * Enum for safely accessing hydrogen isoprotologues. + * + * Hydrogen isoprotologue molecules are: + * - Homonuclear: \f$ ^2H \f$, \f$ ^2d \f$, and \f$ ^2t \f$ + * - Heteronuclear: hd, ht, and dt. + * + * \note Muon-catalyzed fusion data is only applicable to a material with + * concentrations in thermodynamic equilibrium. This equilibrium is calculated + * at model construction from the material temperature and its h, d, and t + * fractions. + */ +enum class MucfIsoprotologueMolecule +{ + protium_protium, + protium_deuterium, + protium_tritium, + deuterium_tritium, + tritium_tritium, + size_ +}; + +//---------------------------------------------------------------------------// +// TYPE ALIASES +//---------------------------------------------------------------------------// + +//! Opaque index of a muCF material component +using MuCfMatId = OpaqueId; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/data/DTMixMucfData.hh b/src/celeritas/mucf/data/DTMixMucfData.hh new file mode 100644 index 0000000000..63affe3867 --- /dev/null +++ b/src/celeritas/mucf/data/DTMixMucfData.hh @@ -0,0 +1,113 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/data/DTMixMucfData.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include + +#include "corecel/Macros.hh" +#include "corecel/cont/EnumArray.hh" +#include "corecel/grid/NonuniformGridData.hh" +#include "corecel/io/Join.hh" +#include "celeritas/Types.hh" +#include "celeritas/mucf/Types.hh" +#include "celeritas/phys/ParticleParams.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * ParticleIds used by the \c DTMixMucfModel . + */ +struct MucfParticleIds +{ + //! Primary + ParticleId mu_minus; + + //!@{ + //! Elementary particles and nuclei + ParticleId neutron; + ParticleId proton; + ParticleId alpha; + ParticleId he3; + //!@} + + //!@{ + //! Muonic atoms + ParticleId muonic_hydrogen; + ParticleId muonic_deuteron; + ParticleId muonic_triton; + ParticleId muonic_alpha; + ParticleId muonic_he3; + //!@} + + //! Check whether all particles are assigned + CELER_FUNCTION explicit operator bool() const + { + return mu_minus && neutron && proton && alpha && he3 && muonic_hydrogen + && muonic_alpha && muonic_triton && muonic_he3; + } +}; + +//---------------------------------------------------------------------------// +/*! + * Data for for the \c DTMixMucfModel . + */ +template +struct DTMixMucfData +{ + template + using Items = Collection; + template + using MaterialItems = Collection; + using GridRecord = NonuniformGridRecord; + using CycleTimesArray = EnumArray>; + + //! Particle IDs + MucfParticleIds particles; + + //! Muon CDF energy grid for sampling outgoing muCF muons + GridRecord muon_energy_cdf; //! \todo Verify energy unit + Items reals; + + //!@{ + //! Material-dependent data calculated at model construction + //! \c PhysMatId indexed by \c MuCfMatId + MaterialItems mucfmatid_to_matid; + //! Cycle times per material: [mat_comp_id][muonic_molecule][spin_index] + MaterialItems cycle_times; //!< In [s] + //! \todo Add mean atom spin flip times + //! \todo Add mean atom transfer times + //!@} + + //! Check whether the data are assigned + explicit CELER_FUNCTION operator bool() const + { + return particles && muon_energy_cdf && !mucfmatid_to_matid.empty() + && !cycle_times.empty() + && (mucfmatid_to_matid.size() == cycle_times.size()); + } + + //! Assign from another set of data + template + DTMixMucfData& operator=(DTMixMucfData const& other) + { + CELER_EXPECT(other); + + //! \todo Finish implementation + this->particles = other.particles; + this->reals = other.reals; + this->muon_energy_cdf = other.muon_energy_cdf; + this->mucfmatid_to_matid = other.mucfmatid_to_matid; + this->cycle_times = other.cycle_times; + + return *this; + } +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/DTMixMucfExecutor.hh b/src/celeritas/mucf/executor/DTMixMucfExecutor.hh new file mode 100644 index 0000000000..030d2c2bf9 --- /dev/null +++ b/src/celeritas/mucf/executor/DTMixMucfExecutor.hh @@ -0,0 +1,148 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/DTMixMucfExecutor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Assert.hh" +#include "celeritas/global/CoreTrackView.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/mucf/interactor/DDMucfInteractor.hh" +#include "celeritas/mucf/interactor/DTMucfInteractor.hh" +#include "celeritas/mucf/interactor/TTMucfInteractor.hh" + +#include "detail/DDChannelSelector.hh" +#include "detail/DTChannelSelector.hh" +#include "detail/DTMixMuonicAtomSelector.hh" +#include "detail/DTMixMuonicMoleculeSelector.hh" +#include "detail/MuonicAtomSpinSelector.hh" +#include "detail/MuonicMoleculeSpinSelector.hh" +#include "detail/TTChannelSelector.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +struct DTMixMucfExecutor +{ + inline CELER_FUNCTION Interaction + operator()(celeritas::CoreTrackView const& track); + + NativeCRef data; +}; + +//---------------------------------------------------------------------------// +/*! + * Execute muon-catalyzed fusion for muonic dd, dt, or tt molecules. + */ +CELER_FUNCTION Interaction +DTMixMucfExecutor::operator()(celeritas::CoreTrackView const& track) +{ + auto phys_step_view = track.physics_step(); + auto elcomp_id = phys_step_view.element(); + CELER_ASSERT(elcomp_id); + + auto element = track.material().material_record().element_record(elcomp_id); + CELER_ASSERT(element.atomic_number() == AtomicNumber{1}); // Must be H + + auto rng = track.rng(); + + // Muon decay may compete against other "actions" in this executor + real_type const decay_len{}; //! \todo Set muon decay interaction length + + // Form d or t muonic atom + detail::DTMixMuonicAtomSelector form_atom; + auto muonic_atom = form_atom(rng); + + // Select atom spin via a helper class + detail::MuonicAtomSpinSelector select_atom_spin(muonic_atom); + + // { + // Competing at-rest processes which add to the total track time + //! \todo Muonic atom transfer + //! \todo Muonic atom spin flip + // } + + // Form dd, dt, or tt muonic molecule + detail::DTMixMuonicMoleculeSelector form_muonic_molecule; + auto muonic_molecule = form_muonic_molecule(rng); + + // Select molecule spin + detail::MuonicMoleculeSpinSelector select_molecule_spin(muonic_molecule); + auto const molecule_spin = select_molecule_spin(rng); + + // Find muCF material ID from PhysMatId + // Make this a View if ever used beyond this executor + auto find = [&](PhysMatId matid) -> MuCfMatId { + CELER_EXPECT(matid); + for (auto i : range(data.mucfmatid_to_matid.size())) + { + if (auto const comp_id = MuCfMatId{i}; + data.mucfmatid_to_matid[comp_id] == matid) + { + return comp_id; + } + } + // MuCF material ID not found + return MuCfMatId{}; + }; + + // Load cycle time for the selected molecule + auto const mucf_matid = find(track.material().material_id()); + CELER_ASSERT(mucf_matid); + auto const cycle_time + = data.cycle_times[mucf_matid][muonic_molecule][molecule_spin]; + CELER_ASSERT(cycle_time > 0); + + // Check if muon decays before fusion happens + real_type const mucf_len = cycle_time * track.sim().step_length(); + if (decay_len < mucf_len) + { + // Muon decays and halts the interaction + //! \todo Update track time and return muon decay interactor + } + + //! \todo Correct track time update? Or should be done in Interactors? + track.sim().add_time(cycle_time); + + // Fuse molecule and generate secondaries + //! \todo Maybe move the channel selectors into the interactors + auto allocate_secondaries = phys_step_view.make_secondary_allocator(); + Interaction result; + switch (muonic_molecule) + { + case MucfMuonicMolecule::deuterium_deuterium: { + // Return DD interaction + DDMucfInteractor interact( + data, detail::DDChannelSelector()(rng), allocate_secondaries); + result = interact(rng); + break; + } + case MucfMuonicMolecule::deuterium_tritium: { + // Return DT interaction + DTMucfInteractor interact( + data, detail::DTChannelSelector()(rng), allocate_secondaries); + result = interact(rng); + break; + } + case MucfMuonicMolecule::tritium_tritium: { + // Return TT interaction + TTMucfInteractor interact( + data, detail::TTChannelSelector()(rng), allocate_secondaries); + result = interact(rng); + break; + } + default: + CELER_ASSERT_UNREACHABLE(); + } + + //! \todo Muon stripping: strip muon from muonic atom secondaries + // May be added as a separate discrete process in the stepping loop + + return result; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/DDChannelSelector.hh b/src/celeritas/mucf/executor/detail/DDChannelSelector.hh new file mode 100644 index 0000000000..05394c8019 --- /dev/null +++ b/src/celeritas/mucf/executor/detail/DDChannelSelector.hh @@ -0,0 +1,72 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/DDChannelSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/mucf/interactor/DDMucfInteractor.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select final channel for muonic dd molecules. + * + * This selection already accounts for sticking, as that is one of the possible + * outcomes. + */ +class DDChannelSelector +{ + public: + //!@{ + //! \name Type aliases + using Channel = DDMucfInteractor::Channel; + //!@} + + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION DDChannelSelector(/* args */); + + // Select fusion channel to be used by the interactor + template + inline CELER_FUNCTION Channel operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + * + * \todo Update documentation + */ +CELER_FUNCTION DDChannelSelector::DDChannelSelector(/* args */) +{ + CELER_NOT_IMPLEMENTED("Mucf dd fusion channel selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return a sampled channel to be used as input in the dd muCF interactor. + * + * \sa celeritas::DDMucfInteractor + */ +template +CELER_FUNCTION DDChannelSelector::Channel DDChannelSelector::operator()(Engine&) +{ + Channel result{Channel::size_}; + + //! \todo Implement + // Final channel selection already takes into account sticking. + + CELER_ENSURE(result < Channel::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/DTChannelSelector.hh b/src/celeritas/mucf/executor/detail/DTChannelSelector.hh new file mode 100644 index 0000000000..47e097cdfd --- /dev/null +++ b/src/celeritas/mucf/executor/detail/DTChannelSelector.hh @@ -0,0 +1,72 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/DTChannelSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/mucf/interactor/DTMucfInteractor.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select final channel for muonic dt molecules. + * + * This selection already accounts for sticking, as that is one of the possible + * outcomes. + */ +class DTChannelSelector +{ + public: + //!@{ + //! \name Type aliases + using Channel = DTMucfInteractor::Channel; + //!@} + + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION DTChannelSelector(/* args */); + + // Select fusion channel to be used by the interactor + template + inline CELER_FUNCTION Channel operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + * + * \todo Update documentation + */ +CELER_FUNCTION DTChannelSelector::DTChannelSelector(/* args */) +{ + CELER_NOT_IMPLEMENTED("Mucf dt fusion channel selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return a sampled channel to be used as input in the dt muCF interactor. + * + * \sa celeritas::DTMucfInteractor + */ +template +CELER_FUNCTION DTChannelSelector::Channel DTChannelSelector::operator()(Engine&) +{ + Channel result{Channel::size_}; + + //! \todo Implement + // Final channel selection already takes into account sticking. + + CELER_ENSURE(result < Channel::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/DTMixMuonicAtomSelector.hh b/src/celeritas/mucf/executor/detail/DTMixMuonicAtomSelector.hh new file mode 100644 index 0000000000..f193109e0c --- /dev/null +++ b/src/celeritas/mucf/executor/detail/DTMixMuonicAtomSelector.hh @@ -0,0 +1,61 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/DTMixMuonicAtomSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/cont/EnumArray.hh" +#include "celeritas/Types.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select a muonic atom given the material information. + */ +class DTMixMuonicAtomSelector +{ + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION DTMixMuonicAtomSelector(/* args */); + + // Select muonic atom + template + inline CELER_FUNCTION MucfMuonicAtom operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + + * \todo Update documentation + */ +CELER_FUNCTION DTMixMuonicAtomSelector::DTMixMuonicAtomSelector(/* args */) +{ + CELER_NOT_IMPLEMENTED("Mucf muonic atom selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return selected muonic atom. + */ +template +CELER_FUNCTION MucfMuonicAtom DTMixMuonicAtomSelector::operator()(Engine&) +{ + MucfMuonicAtom result{MucfMuonicAtom::size_}; + + //! \todo Implement + + CELER_ENSURE(result < MucfMuonicAtom::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/DTMixMuonicMoleculeSelector.hh b/src/celeritas/mucf/executor/detail/DTMixMuonicMoleculeSelector.hh new file mode 100644 index 0000000000..cc4c7bd30b --- /dev/null +++ b/src/celeritas/mucf/executor/detail/DTMixMuonicMoleculeSelector.hh @@ -0,0 +1,67 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/DTMixMuonicMoleculeSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/Types.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select a muonic molecule by calculating the interaction lengths of the + * possible molecule formations. + * + * This is the equivalent of Geant4's + * \c G4VRestProcess::AtRestGetPhysicalInteractionLength + */ +class DTMixMuonicMoleculeSelector +{ + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION DTMixMuonicMoleculeSelector(/* args */); + + // Select muonic molecule + template + inline CELER_FUNCTION MucfMuonicMolecule operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + * + * \todo Update documentation + */ +CELER_FUNCTION +DTMixMuonicMoleculeSelector::DTMixMuonicMoleculeSelector(/* args */) +{ + //! \todo Implement + CELER_NOT_IMPLEMENTED("Mucf muonic molecule selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return selected muonic molecule. + */ +template +CELER_FUNCTION MucfMuonicMolecule +DTMixMuonicMoleculeSelector::operator()(Engine&) +{ + MucfMuonicMolecule result{MucfMuonicMolecule::size_}; + + //! \todo Implement + + CELER_ENSURE(result < MucfMuonicMolecule::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/MuonicAtomSpinSelector.hh b/src/celeritas/mucf/executor/detail/MuonicAtomSpinSelector.hh new file mode 100644 index 0000000000..6d22817345 --- /dev/null +++ b/src/celeritas/mucf/executor/detail/MuonicAtomSpinSelector.hh @@ -0,0 +1,64 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/MuonicAtomSpinSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Assert.hh" +#include "celeritas/Types.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select muonic atom spin, in units of \f$ \frac{\hbar}{2} \f$. + * + * Applicable to \f$ (p)_\mu \f$, \f$ (d)_\mu \f$, and \f$ (t)_\mu \f$. + */ +class MuonicAtomSpinSelector +{ + public: + // Construct with muonic atom type + inline CELER_FUNCTION MuonicAtomSpinSelector(MucfMuonicAtom atom); + + // Sample and return a spin value in units of hbar / 2 + template + inline CELER_FUNCTION size_type operator()(Engine&); + + private: + MucfMuonicAtom atom_; + //! \todo Add constant atom spin sampling rejection fractions +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with muonic atom. + */ +CELER_FUNCTION +MuonicAtomSpinSelector::MuonicAtomSpinSelector(MucfMuonicAtom atom) + : atom_(atom) +{ + CELER_EXPECT(atom_ < MucfMuonicAtom::size_); + CELER_NOT_IMPLEMENTED("Mucf muonic atom spin selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return selected spin, in units of \f$ \hbar / 2 \f$. + */ +template +CELER_FUNCTION size_type MuonicAtomSpinSelector::operator()(Engine&) +{ + //! \todo switch on atom_ + CELER_ASSERT_UNREACHABLE(); +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/MuonicMoleculeSpinSelector.hh b/src/celeritas/mucf/executor/detail/MuonicMoleculeSpinSelector.hh new file mode 100644 index 0000000000..67ae8d4e29 --- /dev/null +++ b/src/celeritas/mucf/executor/detail/MuonicMoleculeSpinSelector.hh @@ -0,0 +1,64 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/MuonicMoleculeSpinSelector.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Assert.hh" +#include "celeritas/Types.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select muonic molecule spin, in units of \f$ \frac{\hbar}{2} \f$. + * + * Applicable to \f$ (dd)_\mu \f$, * \f$ (dt)_\mu \f$ , and \f$ (tt)_\mu \f$. + */ +class MuonicMoleculeSpinSelector +{ + public: + // Construct with muonic molecule type + inline CELER_FUNCTION + MuonicMoleculeSpinSelector(MucfMuonicMolecule molecule); + + // Sample and return a spin value in units of hbar / 2 + template + inline CELER_FUNCTION size_type operator()(Engine&); + + private: + MucfMuonicMolecule molecule_; +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with muonic molecule. + */ +CELER_FUNCTION MuonicMoleculeSpinSelector::MuonicMoleculeSpinSelector( + MucfMuonicMolecule molecule) + : molecule_(molecule) +{ + CELER_EXPECT(molecule_ < MucfMuonicMolecule::size_); + CELER_NOT_IMPLEMENTED("Mucf muonic molecule spin selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return selected spin, in units of \f$ \hbar / 2 \f$. + */ +template +CELER_FUNCTION size_type MuonicMoleculeSpinSelector::operator()(Engine&) +{ + //! \todo switch on molecule_ + CELER_ASSERT_UNREACHABLE(); +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/executor/detail/TTChannelSelector.hh b/src/celeritas/mucf/executor/detail/TTChannelSelector.hh new file mode 100644 index 0000000000..1f7c0b40ea --- /dev/null +++ b/src/celeritas/mucf/executor/detail/TTChannelSelector.hh @@ -0,0 +1,73 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/executor/detail/TTChannelSelectionHelper.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/mucf/interactor/TTMucfInteractor.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Select final channel for muonic tt molecules. + * + * This selection already accounts for sticking, as that is one of the possible + * outcomes. + */ +class TTChannelSelector +{ + public: + //!@{ + //! \name Type aliases + using Channel = TTMucfInteractor::Channel; + //!@} + + public: + //! Construct with args; \todo Update documentation + inline CELER_FUNCTION TTChannelSelector(/* args */); + + // Select fusion channel to be used by the interactor + template + inline CELER_FUNCTION Channel operator()(Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with args. + * + * \todo Update documentation + */ +CELER_FUNCTION TTChannelSelector::TTChannelSelector(/* args */) +{ + //! \todo Implement + CELER_NOT_IMPLEMENTED("Mucf tt fusion channel selection"); +} + +//---------------------------------------------------------------------------// +/*! + * Return a sampled channel to be used as input in the tt muCF interactor. + * + * \sa celeritas::TTMucfInteractor + */ +template +CELER_FUNCTION TTChannelSelector::Channel TTChannelSelector::operator()(Engine&) +{ + Channel result{Channel::size_}; + + //! \todo Implement + // Final channel selection already takes into account sticking. + + CELER_ENSURE(result < Channel::size_); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/interactor/DDMucfInteractor.hh b/src/celeritas/mucf/interactor/DDMucfInteractor.hh new file mode 100644 index 0000000000..b75fa71c02 --- /dev/null +++ b/src/celeritas/mucf/interactor/DDMucfInteractor.hh @@ -0,0 +1,128 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/interactor/DDMucfInteractor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/StackAllocator.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/phys/ParticleTrackView.hh" +#include "celeritas/phys/Secondary.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion of \f$ (dd)_\mu \f$ molecules. + * + * Fusion channels: + * - \f$ ^3\text{He} + \mu + n \f$ + * - \f$ (^3\text{He})_\mu + n \f$ + * - \f$ ^3\text{H} + \mu + p \f$ + */ +class DDMucfInteractor +{ + public: + //! \todo Implement muonichydrogen3_proton (\f$ (^3\text{H})_\mu + p \f$) + enum class Channel + { + helium3_muon_neutron, //!< \f$ ^3\text{He} + \mu + n \f$ + muonichelium3_neutron, //!< \f$ (^3\text{He})_\mu + n \f$ + hydrogen3_muon_proton, //!< \f$ ^3\text{H} + \mu + p \f$ + size_ + }; + + // Construct from shared and state data + inline CELER_FUNCTION + DDMucfInteractor(NativeCRef const& data, + Channel channel, + StackAllocator& allocate); + + // Sample an interaction with the given RNG + template + inline CELER_FUNCTION Interaction operator()(Engine& rng); + + private: + // Shared constant physics properties + NativeCRef const& data_; + // Selected fusion channel + Channel channel_{Channel::size_}; + // Allocate space for secondary particles + StackAllocator& allocate_; + // Number of secondaries per channel + EnumArray num_secondaries_{ + 3, // helium3_muon_neutron + 2, // muonichelium3_neutron + 3 // hydrogen3_muon_proton + }; + + // Sample Interaction secondaries + template + inline CELER_FUNCTION Span + sample_secondaries(Secondary* secondaries /*, other args */, Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data. + */ +CELER_FUNCTION +DDMucfInteractor::DDMucfInteractor(NativeCRef const& data, + Channel const channel, + StackAllocator& allocate) + : data_(data), channel_(channel), allocate_(allocate) +{ + CELER_EXPECT(data_); + CELER_EXPECT(channel < Channel::size_); +} + +//---------------------------------------------------------------------------// +/*! + * Sample a dd muonic molecule fusion. + */ +template +CELER_FUNCTION Interaction DDMucfInteractor::operator()(Engine& rng) +{ + // Allocate space for the final fusion channel + Secondary* secondaries = allocate_(num_secondaries_[channel_]); + if (secondaries == nullptr) + { + // Failed to allocate space for secondaries + return Interaction::from_failure(); + } + + // Kill primary and generate secondaries + Interaction result = Interaction::from_absorption(); + result.secondaries = this->sample_secondaries(secondaries, rng); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Sample the secondaries of the selected channel. + * + * Since secondaries come from an at rest interaction, their final state is + * a simple combination of random direction + momentum conservation + */ +template +CELER_FUNCTION Span +DDMucfInteractor::sample_secondaries(Secondary* secondaries /*, other args */, + Engine&) +{ + // TODO: switch on channel_ + CELER_ASSERT_UNREACHABLE(); + + return Span{secondaries, num_secondaries_[channel_]}; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/interactor/DTMucfInteractor.hh b/src/celeritas/mucf/interactor/DTMucfInteractor.hh new file mode 100644 index 0000000000..568bf3ba0e --- /dev/null +++ b/src/celeritas/mucf/interactor/DTMucfInteractor.hh @@ -0,0 +1,123 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/interactor/DTMucfInteractor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/StackAllocator.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/phys/ParticleTrackView.hh" +#include "celeritas/phys/Secondary.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion of \f$ (dt)_\mu \f$ molecules. + * + * Fusion channels: + * - \f$ \alpha + \mu + n \f$ + * - \f$ (\alpha)_\mu + n \f$ + */ +class DTMucfInteractor +{ + public: + enum class Channel + { + alpha_muon_neutron, //!< \f$ \alpha + \mu + n \f$ + muonicalpha_neutron, //!< \f$ (\alpha)_\mu + n \f$ + size_ + }; + + // Construct from shared and state data + inline CELER_FUNCTION + DTMucfInteractor(NativeCRef const& data, + Channel channel, + StackAllocator& allocate); + + // Sample an interaction with the given RNG + template + inline CELER_FUNCTION Interaction operator()(Engine& rng); + + private: + // Shared constant physics properties + NativeCRef const& data_; + // Selected fusion channel + Channel channel_{Channel::size_}; + // Allocate space for secondary particles + StackAllocator& allocate_; + // Number of secondaries per channel + EnumArray num_secondaries_{ + 3, // alpha_muon_neutron + 2 // muonicalpha_neutron + }; + + // Sample Interaction secondaries + template + inline CELER_FUNCTION Span + sample_secondaries(Secondary* secondaries /*, other args */, Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data. + */ +CELER_FUNCTION +DTMucfInteractor::DTMucfInteractor(NativeCRef const& data, + Channel const channel, + StackAllocator& allocate) + : data_(data), channel_(channel), allocate_(allocate) +{ + CELER_EXPECT(data_); + CELER_EXPECT(channel_ < Channel::size_); +} + +//---------------------------------------------------------------------------// +/*! + * Sample a dt muonic molecule fusion. + */ +template +CELER_FUNCTION Interaction DTMucfInteractor::operator()(Engine& rng) +{ + // Allocate space for the final fusion channel + Secondary* secondaries = allocate_(num_secondaries_[channel_]); + if (secondaries == nullptr) + { + // Failed to allocate space for secondaries + return Interaction::from_failure(); + } + + // Kill primary and generate secondaries + Interaction result = Interaction::from_absorption(); + result.secondaries = this->sample_secondaries(secondaries, rng); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Sample the secondaries of the selected channel. + * + * Since secondaries come from an at rest interaction, their final state is + * a simple combination of random direction + momentum conservation + */ +template +CELER_FUNCTION Span +DTMucfInteractor::sample_secondaries(Secondary* secondaries /*, other args */, + Engine&) +{ + // TODO: switch on channel_ + CELER_ASSERT_UNREACHABLE(); + return Span{secondaries, num_secondaries_[channel_]}; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/interactor/TTMucfInteractor.hh b/src/celeritas/mucf/interactor/TTMucfInteractor.hh new file mode 100644 index 0000000000..d0239fc561 --- /dev/null +++ b/src/celeritas/mucf/interactor/TTMucfInteractor.hh @@ -0,0 +1,124 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/interactor/TTMucfInteractor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/StackAllocator.hh" +#include "celeritas/mat/ElementView.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/phys/Interaction.hh" +#include "celeritas/phys/ParticleTrackView.hh" +#include "celeritas/phys/Secondary.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion of \f$ (tt)_\mu \f$ molecules. + * + * Fusion channels: + * - \f$ \alpha + \mu + n + n \f$ + * - \f$ (\alpha)_\mu + n + n \f$ + */ +class TTMucfInteractor +{ + public: + enum class Channel + { + alpha_muon_neutron_neutron, //!< \f$ \alpha + \mu + n + n \f$ + muonicalpha_neutron_neutron, //!< \f$ (\alpha)_\mu + n + n \f$ + size_ + }; + + // Construct from shared and state data + inline CELER_FUNCTION + TTMucfInteractor(NativeCRef const& data, + Channel channel, + StackAllocator& allocate); + + // Sample an interaction with the given RNG + template + inline CELER_FUNCTION Interaction operator()(Engine& rng); + + private: + // Shared constant physics properties + NativeCRef const& data_; + // Selected fusion channel + Channel channel_{Channel::size_}; + // Allocate space for secondary particles + StackAllocator& allocate_; + // Number of secondaries per channel + EnumArray num_secondaries_{ + 4, // alpha_muon_neutron_neutron + 3 // muonicalpha_neutron_neutron + }; + + // Sample Interaction secondaries + template + inline CELER_FUNCTION Span + sample_secondaries(Secondary* secondaries /*, other args */, Engine&); +}; + +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +/*! + * Construct with shared and state data. + */ +CELER_FUNCTION +TTMucfInteractor::TTMucfInteractor(NativeCRef const& data, + Channel const channel, + StackAllocator& allocate) + : data_(data), channel_(channel), allocate_(allocate) +{ + CELER_EXPECT(data_); + CELER_EXPECT(channel_ < Channel::size_); +} + +//---------------------------------------------------------------------------// +/*! + * Sample a tt muonic molecule fusion. + */ +template +CELER_FUNCTION Interaction TTMucfInteractor::operator()(Engine& rng) +{ + // Allocate space for the final fusion channel + Secondary* secondaries = allocate_(num_secondaries_[channel_]); + if (secondaries == nullptr) + { + // Failed to allocate space for secondaries + return Interaction::from_failure(); + } + + // Kill primary and generate secondaries + Interaction result = Interaction::from_absorption(); + result.secondaries = this->sample_secondaries(secondaries, rng); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Sample the secondaries of the selected channel. + * + * Since secondaries come from an at rest interaction, their final state is + * a simple combination of random direction + momentum conservation + */ +template +CELER_FUNCTION Span +TTMucfInteractor::sample_secondaries(Secondary* secondaries /*, other args */, + Engine&) +{ + // TODO: switch on channel_ + CELER_ASSERT_UNREACHABLE(); + + return Span{secondaries, num_secondaries_[channel_]}; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/model/DTMixMucfModel.cc b/src/celeritas/mucf/model/DTMixMucfModel.cc new file mode 100644 index 0000000000..d4cee2333a --- /dev/null +++ b/src/celeritas/mucf/model/DTMixMucfModel.cc @@ -0,0 +1,179 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/DTMixMucfModel.cc +//---------------------------------------------------------------------------// +#include "DTMixMucfModel.hh" + +#include + +#include "corecel/OpaqueIdIO.hh" +#include "corecel/inp/Grid.hh" +#include "celeritas/global/ActionLauncher.hh" +#include "celeritas/global/TrackExecutor.hh" +#include "celeritas/grid/NonuniformGridBuilder.hh" +#include "celeritas/inp/MucfPhysics.hh" +#include "celeritas/mat/MaterialParams.hh" +#include "celeritas/mucf/executor/DTMixMucfExecutor.hh" // IWYU pragma: associated +#include "celeritas/phys/InteractionApplier.hh" // IWYU pragma: associated +#include "celeritas/phys/PDGNumber.hh" +#include "celeritas/phys/ParticleParams.hh" + +#include "detail/MucfMaterialInserter.hh" + +namespace celeritas +{ +namespace +{ +//---------------------------------------------------------------------------// +/*! + * Assign particle IDs from \c ParticleParams . + */ +static MucfParticleIds from_params(ParticleParams const& particles) +{ + using PairStrPdg = std::pair; + std::vector missing; + MucfParticleIds result; + +#define MP_ADD(MEMBER) \ + result.MEMBER = particles.find(pdg::MEMBER()); \ + if (!result.MEMBER) \ + { \ + missing.push_back({#MEMBER, pdg::MEMBER()}); \ + } + + MP_ADD(mu_minus); + MP_ADD(neutron); + MP_ADD(proton); + MP_ADD(alpha); + MP_ADD(he3); + MP_ADD(muonic_deuteron); + MP_ADD(muonic_triton); + + //! \todo Decide whether to implement these PDGs in PDGNumber.hh +#if 0 + MP_ADD(muonic_hydrogen); + MP_ADD(muonic_alpha); + MP_ADD(muonic_he3); +#endif + + CELER_VALIDATE(missing.empty(), + << "missing particles required for muon-catalyzed fusion: " + << join_stream(missing.begin(), + missing.end(), + ", ", + [](std::ostream& os, PairStrPdg const& p) { + os << p.first << " (PDG " + << p.second.unchecked_get() << ')'; + })); + return result; + +#undef MP_ADD +} +//---------------------------------------------------------------------------// +} // namespace + +//---------------------------------------------------------------------------// +/*! + * Construct from model ID and other necessary data. + * + * Most of the muon-catalyzed fusion data is static throughout the simulation, + * as it is only material-dependent (DT mixture and temperature). Therefore, + * most grids can be host-only and used to calculate final values, which are + * then cached and copied to device. The exception to this is the muon energy + * CDF grid, needed to sample the final state of the outgoing muon after a muCF + * interaction. + * + * \todo Correctly update \c ImportProcessClass and \c ImportModelClass . These + * operate under the assumption that there is a one-to-one equivalente between + * Geant4 and Celeritas. But for muCF, everything is done via one + * process/model/executor in Celeritas, whereas in Geant4 atom formation, spin + * flip, atom transfer, etc., are are all separate processes. + */ +DTMixMucfModel::DTMixMucfModel(ActionId id, + ParticleParams const& particles, + MaterialParams const& materials) + : StaticConcreteAction( + id, + "dt-mucf", + R"(interact by muon forming and fusing a dd, dt, or tt muonic molecule)") +{ + CELER_EXPECT(id); + + // Initialize muCF physics input data + inp::MucfPhysics inp_data = inp::MucfPhysics::from_default(); + CELER_EXPECT(inp_data); + + HostVal host_data; + host_data.particles = from_params(particles); + + // Copy muon energy CDF data using NonuniformGridBuilder + NonuniformGridBuilder build_grid_record{&host_data.reals}; + host_data.muon_energy_cdf = build_grid_record(inp_data.muon_energy_cdf); + + // Calculate and cache quantities for all materials with dt mixtures + detail::MucfMaterialInserter insert(&host_data); + for (auto const& matid : range(materials.num_materials())) + { + auto const& mat_view = materials.get(PhysMatId{matid}); + if (insert(mat_view)) + { + CELER_LOG(debug) << "Added material ID " << mat_view.material_id() + << " as a muCF d-t mixture"; + } + } + + // Copy to device + data_ = CollectionMirror{std::move(host_data)}; + CELER_ENSURE(this->data_); +} + +//---------------------------------------------------------------------------// +/*! + * Particle types and energy ranges that this model applies to. + */ +auto DTMixMucfModel::applicability() const -> SetApplicability +{ + Applicability applic; + applic.particle = this->host_ref().particles.mu_minus; + // At-rest model + applic.lower = zero_quantity(); + applic.upper = zero_quantity(); + + return {applic}; +} + +//---------------------------------------------------------------------------// +/*! + * At-rest model does not require microscopic cross sections. + */ +auto DTMixMucfModel::micro_xs(Applicability) const -> XsTable +{ + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Interact with host data. + */ +void DTMixMucfModel::step(CoreParams const& params, CoreStateHost& state) const +{ + auto execute = make_action_track_executor( + params.ptr(), + state.ptr(), + this->action_id(), + InteractionApplier{DTMixMucfExecutor{this->host_ref()}}); + return launch_action(*this, params, state, execute); +} + +//---------------------------------------------------------------------------// +#if !CELER_USE_DEVICE +void DTMixMucfModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_CONFIGURED("CUDA OR HIP"); +} +#endif + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/model/DTMixMucfModel.cu b/src/celeritas/mucf/model/DTMixMucfModel.cu new file mode 100644 index 0000000000..4ac694e935 --- /dev/null +++ b/src/celeritas/mucf/model/DTMixMucfModel.cu @@ -0,0 +1,33 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/DTMixMucfModel.cu +//---------------------------------------------------------------------------// +#include "DTMixMucfModel.hh" + +#include "celeritas/global/ActionLauncher.device.hh" +#include "celeritas/global/CoreParams.hh" +#include "celeritas/global/CoreState.hh" +#include "celeritas/global/TrackExecutor.hh" +#include "celeritas/mucf/executor/DTMixMucfExecutor.hh" +#include "celeritas/phys/InteractionApplier.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Interact with device data. + */ +void DTMixMucfModel::step(CoreParams const& params, CoreStateDevice& state) const +{ + auto execute = make_action_track_executor( + params.ptr(), + state.ptr(), + this->action_id(), + InteractionApplier{DTMixMucfExecutor{this->device_ref()}}); + static ActionLauncher const launch_kernel(*this); + launch_kernel(*this, params, state, execute); +} +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/model/DTMixMucfModel.hh b/src/celeritas/mucf/model/DTMixMucfModel.hh new file mode 100644 index 0000000000..893d6c2e5e --- /dev/null +++ b/src/celeritas/mucf/model/DTMixMucfModel.hh @@ -0,0 +1,74 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/DTMixMucfModel.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/mucf/data/DTMixMucfData.hh" +#include "celeritas/phys/Model.hh" + +namespace celeritas +{ +class MaterialParams; +class ParticleParams; + +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion model for dd, dt, and tt molecules. + * + * In this model the executor performs the full muon-catalyzed fusion workflow. + * It forms a muonic d or t atom, samples which muonic molecule will be + * produced, selects the channel, and calls the appropriate interactor. + * + * The full set of "actions" is as follows, and in this ordering: + * - Define muon decay time to compete with the rest of the execution + * - Form muonic atom and select its spin + * - May execute atom spin flip or atom transfer + * - Form muonic molecule and select its spin + * - Calculate mean cycle time (time it takes from atom formation to fusion) + * - Confirm if fusion happens or the if the muon should decay + * - Call appropriate Interactor: Muon decay, or one of the muCF interactors + * + * \note This is an at-rest model. + */ +class DTMixMucfModel final : public Model, public StaticConcreteAction +{ + public: + //!@{ + //! \name Type aliases + using HostRef = HostCRef; + using DeviceRef = DeviceCRef; + //!@} + + // Construct from model ID and other necessary data + DTMixMucfModel(ActionId id, + ParticleParams const& particles, + MaterialParams const& materials); + + // Particle types and energy ranges that this model applies to + SetApplicability applicability() const final; + + // Get the microscopic cross sections for the given particle and material + XsTable micro_xs(Applicability) const final; + + // Apply the interaction kernel on host + void step(CoreParams const&, CoreStateHost&) const final; + + // Apply the interaction kernel on device + void step(CoreParams const&, CoreStateDevice&) const final; + + //!@{ + //! Access model data + HostRef const& host_ref() const { return data_.host_ref(); } + DeviceRef const& device_ref() const { return data_.device_ref(); } + //!@} + + private: + // Host/device storage and reference + CollectionMirror data_; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/model/detail/MucfMaterialInserter.cc b/src/celeritas/mucf/model/detail/MucfMaterialInserter.cc new file mode 100644 index 0000000000..f859297ab9 --- /dev/null +++ b/src/celeritas/mucf/model/detail/MucfMaterialInserter.cc @@ -0,0 +1,243 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/detail/MucfMaterialInserter.cc +//---------------------------------------------------------------------------// +#include "MucfMaterialInserter.hh" + +#include "corecel/Assert.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Construct with \c DTMixMucfModel model data. + */ +MucfMaterialInserter::MucfMaterialInserter(HostVal* host_data) + : mucfmatid_to_matid_(&host_data->mucfmatid_to_matid) + , cycle_times_(&host_data->cycle_times) +{ + CELER_EXPECT(host_data); +} + +//---------------------------------------------------------------------------// +/*! + * Insert material information if applicable. + * + * Calculates and caches material-dependent properties needed by the + * \c DTMixMucfModel . If the material does not contain deuterium and/or + * tritium the operator will return false. + */ +bool MucfMaterialInserter::operator()(MaterialView const& material) +{ + for (auto elcompid : range(material.num_elements())) + { + auto const& element_view + = material.element_record(ElementComponentId{elcompid}); + if (element_view.atomic_number() != AtomicNumber{1}) + { + // Skip non-hydrogen elements + continue; + } + + // Found hydrogen; Check for d and t isotopes + IsotopeChecker has_isotope{false, false}; + for (auto el_comp : range(element_view.num_isotopes())) + { + auto iso_view + = element_view.isotope_record(IsotopeComponentId{el_comp}); + auto mass = iso_view.atomic_mass_number(); + if (mass == AtomicMassNumber{1}) + { + // Skip protium + continue; + } + + auto const atom = this->from_mass_number(mass); + if (atom < MucfMuonicAtom::size_) + { + // Mark d or t isotope as found + has_isotope[atom] = true; + } + } + + if (!has_isotope[MucfMuonicAtom::deuterium] + && !has_isotope[MucfMuonicAtom::tritium]) + { + // No deuterium or tritium found; skip material + return false; + } + + // Temporary data needed to calculate model data, such as cycle times + lhd_densities_ = this->calc_lhd_densities(element_view); + equilibrium_densities_ = this->calc_equilibrium_densities(element_view); + + // Calculate and insert muCF material data into model data + mucfmatid_to_matid_.push_back(material.material_id()); + cycle_times_.push_back( + this->calc_cycle_times(element_view, has_isotope)); + //! \todo Store mean atom spin flip and transfer times + } + return true; +} + +//---------------------------------------------------------------------------// +/*! + * Return \c MucfMuonicAtom from a given atomic mass number. + */ +MucfMuonicAtom MucfMaterialInserter::from_mass_number(AtomicMassNumber mass) +{ + if (mass == AtomicMassNumber{2}) + { + return MucfMuonicAtom::deuterium; + } + if (mass == AtomicMassNumber{3}) + { + return MucfMuonicAtom::tritium; + } + return MucfMuonicAtom::size_; +} + +//---------------------------------------------------------------------------// +/*! + * Convert dt mixture densities to units of liquid hydrogen density. + * + * Used during cycle time calculations. + */ +MucfMaterialInserter::LhdArray +MucfMaterialInserter::calc_lhd_densities(ElementView const&) +{ + LhdArray result; + + //! \todo Implement + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate dt mixture densities after reaching thermodynamical + * equilibrium. + * + * Used during cycle time calculations. + */ +MucfMaterialInserter::EquilibriumArray +MucfMaterialInserter::calc_equilibrium_densities(ElementView const&) +{ + EquilibriumArray result; + + //! \todo Implement + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate fusion mean cycle times. + * + * This is designed to work with the user's material definition being either: + * - Single element, multiple isotopes (H element, with H, d, and t isotopes); + * or + * - Multiple elements, single isotope each (separate H, d, and t elements). + */ +MucfMaterialInserter::CycleTimesArray +MucfMaterialInserter::calc_cycle_times(ElementView const& element, + IsotopeChecker const& has_isotope) +{ + CycleTimesArray result; + for (auto el_comp : range(element.num_isotopes())) + { + auto iso_view = element.isotope_record(IsotopeComponentId{el_comp}); + + // Select possible muonic atom based on the isotope/element mass number + auto atom = this->from_mass_number(iso_view.atomic_mass_number()); + switch (atom) + { + // Calculate cycle times for dd molecules + case MucfMuonicAtom::deuterium: { + result[MucfMuonicMolecule::deuterium_deuterium] + = this->calc_dd_cycle(element); + if (has_isotope[MucfMuonicAtom::tritium]) + { + // Calculate cycle times for dt molecules + result[MucfMuonicMolecule::deuterium_tritium] + = this->calc_dt_cycle(element); + } + break; + } + // Calculate cycle times for tt molecules + case MucfMuonicAtom::tritium: { + result[MucfMuonicMolecule::tritium_tritium] + = this->calc_tt_cycle(element); + break; + } + default: + CELER_ASSERT_UNREACHABLE(); + } + } + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate dd muonic molecules cycle times from material properties and grid + * data. + * + * Cycle times for dd molecules come from F = 0 and F = 1 spin states. + */ +MucfMaterialInserter::MoleculeCycles +MucfMaterialInserter::calc_dd_cycle(ElementView const&) +{ + MoleculeCycles result; + + //! \todo Implement + + // Reactive states are F = 0 and F = 1 + CELER_ENSURE(result[0] >= 0 && result[1] >= 0); + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate dt muonic molecules cycle times from material properties and grid + * data. + * + * Cycle times for dt molecules come from F = 1/2 and F = 3/2 spin states. + */ +MucfMaterialInserter::MoleculeCycles +MucfMaterialInserter::calc_dt_cycle(ElementView const&) +{ + MoleculeCycles result; + + //! \todo Implement + + // Reactive states are F = 1/2 and F = 3/2 + CELER_ENSURE(result[0] >= 0 && result[1] >= 0); + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Calculate tt muonic molecules cycle times from material properties and grid + * data. + * + * Cycle times for tt molecules come only from the F = 1/2 spin state. + */ +MucfMaterialInserter::MoleculeCycles +MucfMaterialInserter::calc_tt_cycle(ElementView const&) +{ + MoleculeCycles result; + + //! \todo Implement + + // Only F = 1/2 is reactive + CELER_ENSURE(result[0] >= 0 && result[1] == 0); + return result; +} + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/model/detail/MucfMaterialInserter.hh b/src/celeritas/mucf/model/detail/MucfMaterialInserter.hh new file mode 100644 index 0000000000..ad7c307b9b --- /dev/null +++ b/src/celeritas/mucf/model/detail/MucfMaterialInserter.hh @@ -0,0 +1,77 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/model/detail/MucfMaterialInserter.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/data/CollectionBuilder.hh" +#include "celeritas/mat/MaterialView.hh" +#include "celeritas/mucf/Types.hh" +#include "celeritas/mucf/data/DTMixMucfData.hh" + +namespace celeritas +{ +namespace detail +{ +//---------------------------------------------------------------------------// +/*! + * Helper class to calculate and insert muCF material-dependent data into + * \c DTMixMucfData . + */ +class MucfMaterialInserter +{ + public: + // Construct with muCF host data + explicit MucfMaterialInserter(HostVal* host_data); + + // Insert material if it is a valid d-t mixture + bool operator()(MaterialView const& material); + + private: + //// DATA //// + + using MoleculeCycles = Array; + using CycleTimesArray = EnumArray; + using LhdArray = EnumArray; + using EquilibriumArray = EnumArray; + using AtomicMassNumber = AtomicNumber; + using MucfIsotope = MucfMuonicAtom; + using IsotopeChecker = EnumArray; + + // DTMixMucfModel host data references populated by operator() + CollectionBuilder mucfmatid_to_matid_; + CollectionBuilder cycle_times_; + // Temporary quantities needed for calculating the model data + LhdArray lhd_densities_; + EquilibriumArray equilibrium_densities_; + + //// HELPER FUNCTIONS //// + + // Return muonic atom from given atomic mass number + MucfMuonicAtom from_mass_number(AtomicMassNumber mass); + + // Calculate dt mixture densities in units of liquid hydrogen density + LhdArray calc_lhd_densities(ElementView const&); + + // Calculate thermal equilibrium densities + EquilibriumArray calc_equilibrium_densities(ElementView const&); + + // Calculate mean fusion cycle times for all reactive muonic molecules + CycleTimesArray calc_cycle_times(ElementView const& element, + IsotopeChecker const& has_isotope); + + // Calculate mean fusion cycle times for dd muonic molecules + Array calc_dd_cycle(ElementView const&); + + // Calculate mean fusion cycle times for dt muonic molecules + Array calc_dt_cycle(ElementView const&); + + // Calculate mean fusion cycle times for tt muonic molecules + Array calc_tt_cycle(ElementView const&); +}; + +//---------------------------------------------------------------------------// +} // namespace detail +} // namespace celeritas diff --git a/src/celeritas/mucf/process/MucfProcess.cc b/src/celeritas/mucf/process/MucfProcess.cc new file mode 100644 index 0000000000..cb2947e0a6 --- /dev/null +++ b/src/celeritas/mucf/process/MucfProcess.cc @@ -0,0 +1,67 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/process/MucfProcess.cc +//---------------------------------------------------------------------------// +#include "MucfProcess.hh" + +#include + +#include "celeritas/mat/MaterialParams.hh" +#include "celeritas/mucf/model/DTMixMucfModel.hh" +#include "celeritas/phys/Model.hh" +#include "celeritas/phys/ParticleParams.hh" + +namespace celeritas +{ +//---------------------------------------------------------------------------// +/*! + * Construct from host data. + */ +MucfProcess::MucfProcess(SPConstParticles particles, SPConstMaterials materials) + : particles_(particles), materials_(materials) +{ + //! \todo Fix ImportProcessClass + CELER_EXPECT(particles_); + CELER_EXPECT(materials_); +} + +//---------------------------------------------------------------------------// +/*! + * Construct the models associated with this process. + */ +auto MucfProcess::build_models(ActionIdIter start_id) const -> VecModel +{ + return {std::make_shared( + *start_id++, *particles_, *materials_)}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the interaction cross sections for the given energy range. + */ +auto MucfProcess::macro_xs(Applicability) const -> XsGrid +{ + return {}; +} + +//---------------------------------------------------------------------------// +/*! + * Get the energy loss for the given energy range. + */ +auto MucfProcess::energy_loss(Applicability) const -> EnergyLossGrid +{ + return {}; +} +//---------------------------------------------------------------------------// +/*! + * Name of the process. + */ +std::string_view MucfProcess::label() const +{ + return "Muon-catalyzed fusion"; +} + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/mucf/process/MucfProcess.hh b/src/celeritas/mucf/process/MucfProcess.hh new file mode 100644 index 0000000000..23949c2acc --- /dev/null +++ b/src/celeritas/mucf/process/MucfProcess.hh @@ -0,0 +1,61 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/mucf/process/MucfProcess.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/Types.hh" +#include "celeritas/phys/Process.hh" + +namespace celeritas +{ +class MaterialParams; +class ParticleParams; + +//---------------------------------------------------------------------------// +/*! + * Muon-catalyzed fusion of muonic dd, dt, or tt molecules. + * + * \note This is an at-rest process. + */ +class MucfProcess final : public Process +{ + public: + //!@{ + //! \name Type aliases + using SPConstParticles = std::shared_ptr; + using SPConstMaterials = std::shared_ptr; + //!@} + + public: + // Construct from shared and imported data + explicit MucfProcess(SPConstParticles particles, + SPConstMaterials materials); + + // Construct the models associated with this process + VecModel build_models(ActionIdIter start_id) const final; + + // Get the interaction cross sections for the given energy range + XsGrid macro_xs(Applicability) const final; + + // Get the energy loss for the given energy range + EnergyLossGrid energy_loss(Applicability) const final; + + //! Whether the integral method can be used to sample interaction length + bool supports_integral_xs() const final { return false; } + + //! Whether the process applies when the particle is stopped + bool applies_at_rest() const final { return true; } + + // Name of the process + std::string_view label() const final; + + private: + SPConstParticles particles_; + SPConstMaterials materials_; +}; + +//---------------------------------------------------------------------------// +} // namespace celeritas diff --git a/src/celeritas/optical/CoreParams.cc b/src/celeritas/optical/CoreParams.cc index 3ea1f9bb36..c9554cc8de 100644 --- a/src/celeritas/optical/CoreParams.cc +++ b/src/celeritas/optical/CoreParams.cc @@ -6,15 +6,21 @@ //---------------------------------------------------------------------------// #include "CoreParams.hh" +#include "corecel/data/AuxParamsRegistry.hh" #include "corecel/io/Logger.hh" +#include "corecel/io/OutputInterfaceAdapter.hh" +#include "corecel/io/OutputRegistry.hh" #include "corecel/random/params/RngParams.hh" #include "corecel/sys/ActionRegistry.hh" +#include "corecel/sys/ActionRegistryOutput.hh" #include "corecel/sys/ScopedMem.hh" #include "geocel/SurfaceParams.hh" #include "celeritas/geo/CoreGeoParams.hh" #include "celeritas/mat/MaterialParams.hh" +#include "celeritas/optical/OpticalSizes.json.hh" #include "celeritas/phys/GeneratorRegistry.hh" #include "celeritas/track/SimParams.hh" +#include "celeritas/user/SDParams.hh" #include "CoreState.hh" #include "MaterialParams.hh" @@ -50,10 +56,10 @@ build_params_refs(CoreParams::Input const& p, CoreScalars const& scalars) ref.geometry = get_ref(*p.geometry); ref.material = get_ref(*p.material); ref.physics = get_ref(*p.physics); - ref.surface = get_ref(*p.surface); - ref.surface_physics = get_ref(*p.surface_physics); ref.rng = get_ref(*p.rng); ref.sim = get_ref(*p.sim); + ref.surface = get_ref(*p.surface); + ref.surface_physics = get_ref(*p.surface_physics); // TODO: Get detectors ref if (p.cherenkov) { @@ -126,12 +132,35 @@ CoreParams::CoreParams(Input&& input) : input_(std::move(input)) CELER_EXPECT(input_); - // TODO: provide detectors in input, passing from core params - detectors_ = input_.detectors; - if (!detectors_) + // TODO: require and validate detectors + if (!input_.detectors) { - detectors_ = std::make_shared(); + input_.detectors = std::make_shared(); } + if (!input_.aux_reg) + { + input_.aux_reg = std::make_shared(); + } + if (!input_.output_reg) + { + input_.output_reg = std::make_shared(); + insert_system_diagnostics(*input_.output_reg); + } + + // Save optical action diagnostic information + input_.output_reg->insert(std::make_shared( + input_.action_reg, "optical-actions")); + + // Add optical sizes + OpticalSizes sizes; + sizes.streams = this->max_streams(); + sizes.generators = input_.capacity.generators; + sizes.tracks = input_.capacity.tracks; + input_.output_reg->insert( + OutputInterfaceAdapter::from_rvalue_ref( + OutputInterface::Category::internal, + "optical-sizes", + std::move(sizes))); ScopedMem record_mem("optical::CoreParams.construct"); diff --git a/src/celeritas/optical/CoreParams.hh b/src/celeritas/optical/CoreParams.hh index 483b60655b..0c28fdf8e1 100644 --- a/src/celeritas/optical/CoreParams.hh +++ b/src/celeritas/optical/CoreParams.hh @@ -6,15 +6,13 @@ //---------------------------------------------------------------------------// #pragma once -#include - #include "corecel/Assert.hh" #include "corecel/data/DeviceVector.hh" #include "corecel/data/ObserverPtr.hh" #include "corecel/data/ParamsDataInterface.hh" #include "corecel/random/params/RngParamsFwd.hh" #include "celeritas/geo/GeoFwd.hh" -#include "celeritas/user/SDParams.hh" +#include "celeritas/inp/Control.hh" #include "CoreTrackData.hh" @@ -22,10 +20,13 @@ namespace celeritas { //---------------------------------------------------------------------------// class ActionRegistry; +class AuxParamsRegistry; class CherenkovParams; class GeneratorRegistry; +class OutputRegistry; class ScintillationParams; class SurfaceParams; +class SDParams; namespace optical { @@ -43,6 +44,11 @@ class CoreParams final : public ParamsDataInterface public: //!@{ //! \name Type aliases + using SPActionRegistry = std::shared_ptr; + using SPOutputRegistry = std::shared_ptr; + using SPGeneratorRegistry = std::shared_ptr; + using SPAuxRegistry = std::shared_ptr; + using SPConstCoreGeo = std::shared_ptr; using SPConstMaterial = std::shared_ptr; using SPConstPhysics = std::shared_ptr; @@ -50,9 +56,8 @@ class CoreParams final : public ParamsDataInterface using SPConstSim = std::shared_ptr; using SPConstSurface = std::shared_ptr; using SPConstSurfacePhysics = std::shared_ptr; - using SPActionRegistry = std::shared_ptr; - using SPGeneratorRegistry = std::shared_ptr; using SPConstDetectors = std::shared_ptr; + using SPConstCherenkov = std::shared_ptr; using SPConstScintillation = std::shared_ptr; @@ -64,6 +69,13 @@ class CoreParams final : public ParamsDataInterface struct Input { + // Registries + SPActionRegistry action_reg; + SPOutputRegistry output_reg; + SPGeneratorRegistry gen_reg; + SPAuxRegistry aux_reg; //!< Optional, empty default + + // Problem definition and state SPConstCoreGeo geometry; SPConstMaterial material; SPConstPhysics physics; @@ -72,20 +84,23 @@ class CoreParams final : public ParamsDataInterface SPConstSurface surface; SPConstSurfacePhysics surface_physics; SPConstDetectors detectors; + SPConstCherenkov cherenkov; //!< Optional SPConstScintillation scintillation; //!< Optional - SPActionRegistry action_reg; - SPGeneratorRegistry gen_reg; - //! Maximum number of simultaneous threads/tasks per process StreamId::size_type max_streams{1}; + //! Per-process state and buffer capacities + inp::OpticalStateCapacity capacity; + //! True if all params are assigned and valid explicit operator bool() const { return geometry && material && rng && sim && surface - && surface_physics && action_reg && gen_reg && max_streams; + && surface_physics && action_reg && gen_reg && max_streams + && capacity.generators > 0 && capacity.tracks > 0 + && capacity.primaries > 0; } }; @@ -115,8 +130,10 @@ class CoreParams final : public ParamsDataInterface return input_.surface_physics; } SPActionRegistry const& action_reg() const { return input_.action_reg; } + SPOutputRegistry const& output_reg() const { return input_.output_reg; } + SPAuxRegistry const& aux_reg() const { return input_.aux_reg; } SPGeneratorRegistry const& gen_reg() const { return input_.gen_reg; } - SPConstDetectors const& detectors() const { return detectors_; } + SPConstDetectors const& detectors() const { return input_.detectors; } SPConstCherenkov const& cherenkov() const { return input_.cherenkov; } SPConstScintillation const& scintillation() const { @@ -141,8 +158,6 @@ class CoreParams final : public ParamsDataInterface // Copy of DeviceRef in device memory DeviceVector device_ref_vec_; - - SPConstDetectors detectors_; }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/optical/CoreTrackData.hh b/src/celeritas/optical/CoreTrackData.hh index 91851ca09a..2b804a3442 100644 --- a/src/celeritas/optical/CoreTrackData.hh +++ b/src/celeritas/optical/CoreTrackData.hh @@ -19,7 +19,6 @@ #include "PhysicsData.hh" #include "SimData.hh" #include "TrackInitData.hh" -#include "Types.hh" #include "gen/CherenkovData.hh" #include "gen/ScintillationData.hh" #include "surface/SurfacePhysicsData.hh" @@ -80,9 +79,9 @@ struct CoreParamsData sim = other.sim; surface = other.surface; surface_physics = other.surface_physics; - scalars = other.scalars; cherenkov = other.cherenkov; scintillation = other.scintillation; + scalars = other.scalars; return *this; } }; @@ -98,7 +97,6 @@ struct CoreStateData using Items = StateCollection; GeoStateData geometry; - // TODO: should we cache the material ID? ParticleStateData particle; PhysicsStateData physics; RngStateData rng; diff --git a/src/celeritas/optical/MaterialParams.cc b/src/celeritas/optical/MaterialParams.cc index 0b2ad11622..87f24a6908 100644 --- a/src/celeritas/optical/MaterialParams.cc +++ b/src/celeritas/optical/MaterialParams.cc @@ -52,43 +52,32 @@ MaterialParams::from_import(ImportData const& data, inp.properties.push_back(opt_mat.properties); } - // Construct volume-to-optical mapping - inp.volume_to_mat.reserve(geo_mat.num_volumes()); - bool has_opt_mat{false}; - for (auto impl_id : range(ImplVolumeId{geo_mat.num_volumes()})) + // Construct impl-volume-to-optical mapping + inp.volume_to_mat.assign(geo_mat.num_volumes(), OptMatId{}); + inp.optical_to_core.assign(inp.properties.size(), PhysMatId{}); + + std::unordered_set all_optmat; + for (auto iv_id : range(ImplVolumeId{geo_mat.num_volumes()})) { - OptMatId optmat; - if (PhysMatId matid = geo_mat.material_id(impl_id)) + if (PhysMatId matid = geo_mat.material_id(iv_id)) { auto mat_view = mat.get(matid); - optmat = mat_view.optical_material_id(); + OptMatId optmat = mat_view.optical_material_id(); if (optmat) { - has_opt_mat = true; + CELER_ASSERT(optmat < inp.optical_to_core.size()); + all_optmat.insert(optmat); + inp.optical_to_core[optmat.get()] = matid; + inp.volume_to_mat[iv_id.get()] = optmat; } } - inp.volume_to_mat.push_back(optmat); } - - CELER_VALIDATE(has_opt_mat, + CELER_VALIDATE(!all_optmat.empty(), << "no volumes have associated optical materials"); - CELER_ENSURE(inp.volume_to_mat.size() == geo_mat.num_volumes()); - - // Construct optical to core material mapping - inp.optical_to_core - = std::vector(inp.properties.size(), PhysMatId{}); - for (auto core_id : range(PhysMatId{mat.num_materials()})) - { - if (auto opt_mat_id = mat.get(core_id).optical_material_id()) - { - CELER_EXPECT(opt_mat_id < inp.optical_to_core.size()); - CELER_EXPECT(!inp.optical_to_core[opt_mat_id.get()]); - inp.optical_to_core[opt_mat_id.get()] = core_id; - } - } - CELER_ENSURE(std::all_of( - inp.optical_to_core.begin(), inp.optical_to_core.end(), Identity{})); + CELER_LOG(info) << "Constructed " << inp.properties.size() + << " optical materials with " << all_optmat.size() + << " present in the geometry"; return std::make_shared(std::move(inp)); } diff --git a/src/celeritas/optical/OpticalCollector.cc b/src/celeritas/optical/OpticalCollector.cc index fbf6cb43b1..73ba44ca07 100644 --- a/src/celeritas/optical/OpticalCollector.cc +++ b/src/celeritas/optical/OpticalCollector.cc @@ -59,8 +59,8 @@ OpticalCollector::OpticalCollector(CoreParams const& core, Input&& inp) = OffloadGatherAction::make_and_insert(core); // Create optical action to generate Cherenkov or scintillation photons - generate_ = optical::GeneratorAction::make_and_insert( - core, *inp.optical_params, inp.buffer_capacity); + generate_ = optical::GeneratorAction::make_and_insert(*inp.optical_params, + inp.buffer_capacity); if (inp.optical_params->cherenkov()) { diff --git a/src/celeritas/optical/PhysicsParams.cc b/src/celeritas/optical/PhysicsParams.cc index c879cc5bd9..aa890833ab 100644 --- a/src/celeritas/optical/PhysicsParams.cc +++ b/src/celeritas/optical/PhysicsParams.cc @@ -7,6 +7,8 @@ #include "PhysicsParams.hh" #include "corecel/sys/ActionRegistry.hh" +#include "celeritas/io/ImportData.hh" +#include "celeritas/optical/ModelImporter.hh" #include "MaterialParams.hh" #include "MfpBuilder.hh" @@ -17,6 +19,30 @@ namespace celeritas { namespace optical { +//---------------------------------------------------------------------------// +/*! + * Construct with imported data. + */ +std::shared_ptr +PhysicsParams::from_import(ImportData const& data, + SPConstCoreMaterials core_materials, + SPConstMaterials materials, + SPActionRegistry action_reg) +{ + Input input; + input.materials = materials; + input.action_registry = action_reg.get(); + ModelImporter importer{data, materials, core_materials}; + for (auto const& model : data.optical_models) + { + if (auto builder = importer(model.model_class)) + { + input.model_builders.push_back(*builder); + } + } + return std::make_shared(std::move(input)); +} + //---------------------------------------------------------------------------// /*! * Construct from imported and shared data. diff --git a/src/celeritas/optical/PhysicsParams.hh b/src/celeritas/optical/PhysicsParams.hh index bcc1292098..1d857c8913 100644 --- a/src/celeritas/optical/PhysicsParams.hh +++ b/src/celeritas/optical/PhysicsParams.hh @@ -16,6 +16,8 @@ namespace celeritas { class ActionRegistry; +struct ImportData; +class MaterialParams; namespace optical { @@ -27,7 +29,10 @@ class PhysicsParams final : public ParamsDataInterface public: //!@{ //! \name Type aliases + using SPActionRegistry = std::shared_ptr; using SPConstModel = std::shared_ptr; + using SPConstCoreMaterials + = std::shared_ptr; using SPConstMaterials = std::shared_ptr; using VecModels = std::vector; @@ -44,6 +49,12 @@ class PhysicsParams final : public ParamsDataInterface }; public: + // Construct with imported data, material params, and action registry + static std::shared_ptr from_import(ImportData const&, + SPConstCoreMaterials, + SPConstMaterials, + SPActionRegistry); + // Construct from models explicit PhysicsParams(Input input); diff --git a/src/celeritas/optical/PhysicsStepUtils.hh b/src/celeritas/optical/PhysicsStepUtils.hh index 47807c6c84..732cb5fac3 100644 --- a/src/celeritas/optical/PhysicsStepUtils.hh +++ b/src/celeritas/optical/PhysicsStepUtils.hh @@ -33,13 +33,17 @@ inline CELER_FUNCTION StepLimit calc_physics_step_limit( total_xs += physics.calc_xs(model, particle.energy()); } physics.macro_xs(total_xs); - - CELER_ASSERT(physics.macro_xs() > 0); + CELER_ASSERT(physics.macro_xs() >= 0); StepLimit limit; limit.action = physics.discrete_action(); limit.step = physics.interaction_mfp() / total_xs; + if (CELER_UNLIKELY(total_xs == 0)) + { + limit.action = {}; + } + return limit; } diff --git a/src/celeritas/optical/detail/OpticalLaunchAction.cc b/src/celeritas/optical/detail/OpticalLaunchAction.cc index c080a805f8..1dcc73b86f 100644 --- a/src/celeritas/optical/detail/OpticalLaunchAction.cc +++ b/src/celeritas/optical/detail/OpticalLaunchAction.cc @@ -134,8 +134,8 @@ void OpticalLaunchAction::execute_impl(CoreParams const&, auto& state = get>(core_state.aux(), this->aux_id()); CELER_ASSERT(state.size() > 0); - auto const& core_counters = core_state.counters(); - auto& counters = state.counters(); + auto const core_counters = core_state.sync_get_counters(); + auto const& counters = state.counters(); if ((counters.num_pending < data_.auto_flush && (core_counters.num_alive > 0 || core_counters.num_initializers > 0)) diff --git a/src/celeritas/optical/gen/DirectGeneratorAction.cc b/src/celeritas/optical/gen/DirectGeneratorAction.cc index b6a174e0df..1137d0e70f 100644 --- a/src/celeritas/optical/gen/DirectGeneratorAction.cc +++ b/src/celeritas/optical/gen/DirectGeneratorAction.cc @@ -48,11 +48,11 @@ auto make_state(StreamId stream, size_type size) /*! * Construct and add to core params. */ -std::shared_ptr DirectGeneratorAction::make_and_insert( - ::celeritas::CoreParams const& core_params, CoreParams const& params) +std::shared_ptr +DirectGeneratorAction::make_and_insert(CoreParams const& params) { ActionRegistry& actions = *params.action_reg(); - AuxParamsRegistry& aux = *core_params.aux_reg(); + AuxParamsRegistry& aux = *params.aux_reg(); GeneratorRegistry& gen = *params.gen_reg(); auto result = std::make_shared( actions.next_id(), aux.next_id(), gen.next_id()); diff --git a/src/celeritas/optical/gen/DirectGeneratorAction.hh b/src/celeritas/optical/gen/DirectGeneratorAction.hh index b7c7ba1157..683ce84e1e 100644 --- a/src/celeritas/optical/gen/DirectGeneratorAction.hh +++ b/src/celeritas/optical/gen/DirectGeneratorAction.hh @@ -43,7 +43,7 @@ class DirectGeneratorAction final : public GeneratorBase public: // Construct and add to core params static std::shared_ptr - make_and_insert(::celeritas::CoreParams const&, CoreParams const&); + make_and_insert(CoreParams const&); // Construct with action ID and data IDs DirectGeneratorAction(ActionId, AuxId, GeneratorId); diff --git a/src/celeritas/optical/gen/GeneratorAction.cc b/src/celeritas/optical/gen/GeneratorAction.cc index 6e11eb67ee..1454fdc4d7 100644 --- a/src/celeritas/optical/gen/GeneratorAction.cc +++ b/src/celeritas/optical/gen/GeneratorAction.cc @@ -59,12 +59,10 @@ auto make_state(StreamId stream, size_type size) * Construct and add to core params. */ std::shared_ptr -GeneratorAction::make_and_insert(::celeritas::CoreParams const& core_params, - CoreParams const& params, - size_type capacity) +GeneratorAction::make_and_insert(CoreParams const& params, size_type capacity) { ActionRegistry& actions = *params.action_reg(); - AuxParamsRegistry& aux = *core_params.aux_reg(); + AuxParamsRegistry& aux = *params.aux_reg(); GeneratorRegistry& gen = *params.gen_reg(); auto result = std::make_shared( actions.next_id(), aux.next_id(), gen.next_id(), capacity); diff --git a/src/celeritas/optical/gen/GeneratorAction.hh b/src/celeritas/optical/gen/GeneratorAction.hh index 4c1ef6ffbe..5ab30b8c35 100644 --- a/src/celeritas/optical/gen/GeneratorAction.hh +++ b/src/celeritas/optical/gen/GeneratorAction.hh @@ -48,9 +48,7 @@ class GeneratorAction final : public GeneratorBase public: // Construct and add to core params static std::shared_ptr - make_and_insert(::celeritas::CoreParams const&, - CoreParams const&, - size_type capacity); + make_and_insert(CoreParams const&, size_type capacity); // Construct with action ID, data IDs, and optical properties GeneratorAction(ActionId, AuxId, GeneratorId, size_type capacity); diff --git a/src/celeritas/optical/gen/PrimaryGeneratorAction.cc b/src/celeritas/optical/gen/PrimaryGeneratorAction.cc index 8fceb1eb81..dafea685eb 100644 --- a/src/celeritas/optical/gen/PrimaryGeneratorAction.cc +++ b/src/celeritas/optical/gen/PrimaryGeneratorAction.cc @@ -33,14 +33,12 @@ namespace optical /*! * Construct and add to core params. */ -std::shared_ptr PrimaryGeneratorAction::make_and_insert( - ::celeritas::CoreParams const& core_params, - CoreParams const& params, - Input&& input) +std::shared_ptr +PrimaryGeneratorAction::make_and_insert(CoreParams const& params, Input&& input) { CELER_EXPECT(input); ActionRegistry& actions = *params.action_reg(); - AuxParamsRegistry& aux = *core_params.aux_reg(); + AuxParamsRegistry& aux = *params.aux_reg(); GeneratorRegistry& gen = *params.gen_reg(); auto result = std::make_shared( actions.next_id(), aux.next_id(), gen.next_id(), std::move(input)); diff --git a/src/celeritas/optical/gen/PrimaryGeneratorAction.hh b/src/celeritas/optical/gen/PrimaryGeneratorAction.hh index aa9cc2a76a..bee7dde715 100644 --- a/src/celeritas/optical/gen/PrimaryGeneratorAction.hh +++ b/src/celeritas/optical/gen/PrimaryGeneratorAction.hh @@ -47,7 +47,7 @@ class PrimaryGeneratorAction final : public GeneratorBase public: // Construct and add to core params static std::shared_ptr - make_and_insert(::celeritas::CoreParams const&, CoreParams const&, Input&&); + make_and_insert(CoreParams const&, Input&&); // Construct with IDs and distributions PrimaryGeneratorAction(ActionId, AuxId, GeneratorId, Input); diff --git a/src/celeritas/optical/surface/GaussianRoughnessSampler.hh b/src/celeritas/optical/surface/GaussianRoughnessSampler.hh index 2277101888..64e76b8a42 100644 --- a/src/celeritas/optical/surface/GaussianRoughnessSampler.hh +++ b/src/celeritas/optical/surface/GaussianRoughnessSampler.hh @@ -9,10 +9,8 @@ #include #include "corecel/math/Algorithms.hh" -#include "corecel/math/ArrayUtils.hh" #include "corecel/random/distribution/NormalDistribution.hh" #include "corecel/random/distribution/RejectionSampler.hh" -#include "celeritas/Constants.hh" #include "celeritas/phys/InteractionUtils.hh" namespace celeritas @@ -59,9 +57,8 @@ class GaussianRoughnessSampler inline CELER_FUNCTION Real3 operator()(Engine& rng); private: - Real3 const& normal_; + Real3 normal_; NormalDistribution sample_alpha_; - real_type alpha_max_; real_type f_max_; }; @@ -76,8 +73,7 @@ GaussianRoughnessSampler::GaussianRoughnessSampler(Real3 const& normal, real_type sigma_alpha) : normal_(normal) , sample_alpha_(0, sigma_alpha) - , alpha_max_(fmin(real_type(constants::pi / 2), 4 * sigma_alpha)) - , f_max_(std::sin(alpha_max_)) + , f_max_(fmin(real_type{1}, 4 * sigma_alpha)) { CELER_EXPECT(sigma_alpha > 0); CELER_EXPECT(is_soft_unit_vector(normal_)); @@ -102,13 +98,13 @@ CELER_FUNCTION Real3 GaussianRoughnessSampler::operator()(Engine& rng) // Sample positive angle according to gaussian (chances of having a // nonpositive slope are generally vanishingly small) alpha = std::fabs(sample_alpha_(rng)); - } while (alpha >= alpha_max_); + } while (alpha >= real_type(constants::pi / 2)); sincos(alpha, &sin_alpha, &cos_alpha); // Transform to polar angle using rejection - } while (RejectionSampler{sin_alpha, f_max_}(rng)); + } while (sin_alpha < f_max_ && RejectionSampler{sin_alpha, f_max_}(rng)); - // Rotate normal by alpha and then sample azimuth rotation uniformly + // Rotate normal by alpha and then sample azimuthal rotation uniformly return ExitingDirectionSampler{cos_alpha, normal_}(rng); } diff --git a/src/celeritas/optical/surface/SmearRoughnessSampler.hh b/src/celeritas/optical/surface/SmearRoughnessSampler.hh index 61f85be6cd..f0679edd99 100644 --- a/src/celeritas/optical/surface/SmearRoughnessSampler.hh +++ b/src/celeritas/optical/surface/SmearRoughnessSampler.hh @@ -40,7 +40,7 @@ class SmearRoughnessSampler inline CELER_FUNCTION Real3 operator()(Engine& rng) const; private: - Real3 const& normal_; + Real3 normal_; real_type roughness_; PowerDistribution<> sample_r_{2}; }; diff --git a/src/celeritas/optical/surface/SurfaceModel.hh b/src/celeritas/optical/surface/SurfaceModel.hh index 5f937fdfdc..3b34cd80cf 100644 --- a/src/celeritas/optical/surface/SurfaceModel.hh +++ b/src/celeritas/optical/surface/SurfaceModel.hh @@ -6,10 +6,6 @@ //---------------------------------------------------------------------------// #pragma once -#include -#include - -#include "celeritas/optical/Types.hh" #include "celeritas/optical/action/ActionInterface.hh" #include "celeritas/phys/SurfaceModel.hh" diff --git a/src/celeritas/optical/surface/SurfacePhysicsParams.cc b/src/celeritas/optical/surface/SurfacePhysicsParams.cc index 9e6da2bba2..191a3300d6 100644 --- a/src/celeritas/optical/surface/SurfacePhysicsParams.cc +++ b/src/celeritas/optical/surface/SurfacePhysicsParams.cc @@ -9,6 +9,8 @@ #include "corecel/data/CollectionBuilder.hh" #include "corecel/sys/ActionRegistry.hh" #include "celeritas/inp/SurfacePhysics.hh" +#include "celeritas/optical/surface/model/GaussianRoughnessModel.hh" +#include "celeritas/optical/surface/model/SmearRoughnessModel.hh" #include "celeritas/phys/SurfacePhysicsMapBuilder.hh" #include "model/DielectricInteractionModel.hh" @@ -140,8 +142,9 @@ auto SurfacePhysicsParams::build_models( case SurfacePhysicsOrder::roughness: build_model.build( input.roughness.polished); - build_model.build_fake("smear", input.roughness.smear); - build_model.build_fake("gaussian", input.roughness.gaussian); + build_model.build(input.roughness.smear); + build_model.build( + input.roughness.gaussian); break; case SurfacePhysicsOrder::reflectivity: build_model.build_fake("grid", input.reflectivity.grid); @@ -159,7 +162,7 @@ auto SurfacePhysicsParams::build_models( CELER_VALIDATE( build_model.num_surfaces() == num_phys_surfaces(input.materials), - << " same number of physics surfaces required for each " + << "same number of physics surfaces required for each " "surface physics step (" << num_phys_surfaces(input.materials) << " expected surfaces, " << build_model.num_surfaces() << " surfaces from " diff --git a/src/celeritas/optical/surface/SurfacePhysicsTrackView.hh b/src/celeritas/optical/surface/SurfacePhysicsTrackView.hh index 7838d9c571..d21c371f4d 100644 --- a/src/celeritas/optical/surface/SurfacePhysicsTrackView.hh +++ b/src/celeritas/optical/surface/SurfacePhysicsTrackView.hh @@ -6,7 +6,7 @@ //---------------------------------------------------------------------------// #pragma once -#include "corecel/math/ArrayUtils.hh" +#include "corecel/math/ArrayOperators.hh" #include "celeritas/optical/Types.hh" #include "celeritas/phys/SurfacePhysicsMapView.hh" @@ -237,6 +237,7 @@ CELER_FUNCTION void SurfacePhysicsTrackView::facet_normal(Real3 const& normal) { CELER_EXPECT(this->is_crossing_boundary()); CELER_EXPECT(is_soft_unit_vector(normal)); + CELER_EXPECT(dot_product(normal, this->global_normal()) >= 0); states_.facet_normal[track_id_] = normal; } diff --git a/src/celeritas/optical/surface/SurfacePhysicsUtils.hh b/src/celeritas/optical/surface/SurfacePhysicsUtils.hh index 8f8f66438f..f47d4356b7 100644 --- a/src/celeritas/optical/surface/SurfacePhysicsUtils.hh +++ b/src/celeritas/optical/surface/SurfacePhysicsUtils.hh @@ -6,12 +6,15 @@ //---------------------------------------------------------------------------// #pragma once -#include "corecel/data/Collection.hh" -#include "corecel/math/Algorithms.hh" -#include "corecel/math/ArrayOperators.hh" +#include "corecel/Macros.hh" #include "corecel/math/ArrayUtils.hh" #include "celeritas/optical/Types.hh" +#if !CELER_DEVICE_COMPILE +# include "corecel/io/Logger.hh" +# include "corecel/io/Repr.hh" +#endif + namespace celeritas { namespace optical @@ -96,8 +99,20 @@ class EnteringSurfaceNormalSampler CELER_FUNCTION Real3 operator()(Engine& rng) { Real3 local_normal; + int loop_guard{256}; do { + --loop_guard; +#if !CELER_DEVICE_COMPILE + CELER_VALIDATE( + loop_guard > 0, + << "failed to sample a microfacet direction into which " + << repr(dir_) << " is entering (last normal sampled: " + << repr(local_normal) << ")"); +#else + if (CELER_UNLIKELY(loop_guard == 0)) + return {0, 0, 0}; +#endif local_normal = sample_normal_(rng); } while (!is_entering_surface(local_normal, dir_)); return local_normal; diff --git a/src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh b/src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh index 0ea3b65950..773c4aa06e 100644 --- a/src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh +++ b/src/celeritas/optical/surface/detail/BuiltinSurfaceModelBuilder.hh @@ -6,19 +6,20 @@ //---------------------------------------------------------------------------// #pragma once +#include #include +#include +#include +#include "celeritas/Types.hh" #include "celeritas/optical/surface/SurfaceModel.hh" namespace celeritas { namespace optical { -//---------------------------------------------------------------------------// namespace detail { -namespace -{ //---------------------------------------------------------------------------// /*! * Fake model as a placeholder for surface models yet to be implemented. @@ -53,8 +54,6 @@ class FakeModel : public SurfaceModel VecSurfaceLayer layers_; }; -} // namespace - //---------------------------------------------------------------------------// /*! * Utility for building built-in surface models from input data. diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessData.hh b/src/celeritas/optical/surface/model/GaussianRoughnessData.hh new file mode 100644 index 0000000000..e0d2dd4196 --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessData.hh @@ -0,0 +1,58 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessData.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/Collection.hh" +#include "celeritas/Types.hh" +#include "celeritas/optical/surface/SurfaceModel.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Storage for Gaussian roughness model data. + */ +template +struct GaussianRoughnessData +{ + //!@{ + //! \name Type aliases + template + using SurfaceItems = Collection; + //!@} + + //// DATA ///// + + SurfaceItems sigma_alpha; + + //// METHODS //// + + //! True if assigned + explicit CELER_FUNCTION operator bool() const + { + return !sigma_alpha.empty(); + } + + //! Assign from another set of data + template + GaussianRoughnessData& + operator=(GaussianRoughnessData const& other) + { + CELER_EXPECT(other); + + sigma_alpha = other.sigma_alpha; + + return *this; + } +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessExecutor.hh b/src/celeritas/optical/surface/model/GaussianRoughnessExecutor.hh new file mode 100644 index 0000000000..f177d0b900 --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessExecutor.hh @@ -0,0 +1,44 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessExecutor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/optical/CoreTrackView.hh" +#include "celeritas/optical/surface/GaussianRoughnessSampler.hh" + +#include "GaussianRoughnessData.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Sample and save a facet normal for the Gaussian roughness model. + */ +struct GaussianRoughnessExecutor +{ + NativeCRef data; + + //! Apply Gaussian roughness executor + CELER_FUNCTION void operator()(CoreTrackView& track) const + { + auto s_phys = track.surface_physics(); + auto sub_model_id = s_phys.interface(SurfacePhysicsOrder::roughness) + .internal_surface_id(); + CELER_ASSERT(sub_model_id < data.sigma_alpha.size()); + EnteringSurfaceNormalSampler sample_facet{ + track.geometry().dir(), + s_phys.global_normal(), + data.sigma_alpha[sub_model_id]}; + auto rng = track.rng(); + s_phys.facet_normal(sample_facet(rng)); + } +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessModel.cc b/src/celeritas/optical/surface/model/GaussianRoughnessModel.cc new file mode 100644 index 0000000000..03d8bca97f --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessModel.cc @@ -0,0 +1,81 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessModel.cc +//---------------------------------------------------------------------------// +#include "GaussianRoughnessModel.hh" + +#include + +#include "corecel/data/CollectionBuilder.hh" +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/action/ActionLauncher.hh" +#include "celeritas/optical/action/TrackSlotExecutor.hh" + +#include "GaussianRoughnessExecutor.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Construct the model from an ID and a layer map. + */ +GaussianRoughnessModel::GaussianRoughnessModel( + SurfaceModelId id, std::map const& layer_map) + : SurfaceModel(id, "roughness-gaussian") +{ + surfaces_.reserve(layer_map.size()); + std::transform(layer_map.begin(), + layer_map.end(), + std::back_inserter(surfaces_), + [](auto const& layer) { return layer.first; }); + + HostVal data; + auto build_sigma_alpha = CollectionBuilder{&data.sigma_alpha}; + + for (auto const& [surface, gaussian] : layer_map) + { + CELER_ENSURE(gaussian); + build_sigma_alpha.push_back(gaussian.sigma_alpha); + } + + CELER_ENSURE(data); + CELER_ENSURE(data.sigma_alpha.size() == layer_map.size()); + + data_ = CollectionMirror{std::move(data)}; +} + +//---------------------------------------------------------------------------// +/*! + * Execute model with host data. + */ +void GaussianRoughnessModel::step(CoreParams const& params, + CoreStateHost& state) const +{ + launch_action(state, + make_surface_physics_executor( + params.ptr(), + state.ptr(), + SurfacePhysicsOrder::roughness, + this->surface_model_id(), + GaussianRoughnessExecutor{data_.host_ref()})); +} + +//---------------------------------------------------------------------------// +/*! + * Execute kernel with device data. + */ +#if !CELER_USE_DEVICE +void GaussianRoughnessModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_CONFIGURED("CUDA OR HIP"); +} +#endif + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessModel.cu b/src/celeritas/optical/surface/model/GaussianRoughnessModel.cu new file mode 100644 index 0000000000..a947ee39bc --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessModel.cu @@ -0,0 +1,40 @@ +//------------------------------ -*- cuda -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessModel.cu +//---------------------------------------------------------------------------// +#include "GaussianRoughnessModel.hh" + +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/action/ActionLauncher.device.hh" +#include "celeritas/optical/action/TrackSlotExecutor.hh" + +#include "GaussianRoughnessExecutor.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Launch kernel with device data. + */ +void GaussianRoughnessModel::step(CoreParams const& params, + CoreStateDevice& state) const +{ + auto execute = make_surface_physics_executor( + params.ptr(), + state.ptr(), + SurfacePhysicsOrder::roughness, + this->surface_model_id(), + GaussianRoughnessExecutor{data_.device_ref()}); + + static ActionLauncher const launch_kernel(*this); + launch_kernel(state, execute); +} + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/GaussianRoughnessModel.hh b/src/celeritas/optical/surface/model/GaussianRoughnessModel.hh new file mode 100644 index 0000000000..dd5e4ab055 --- /dev/null +++ b/src/celeritas/optical/surface/model/GaussianRoughnessModel.hh @@ -0,0 +1,60 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/GaussianRoughnessModel.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/data/CollectionMirror.hh" +#include "celeritas/inp/SurfacePhysics.hh" +#include "celeritas/optical/surface/SurfaceModel.hh" + +#include "GaussianRoughnessData.hh" + +namespace celeritas +{ +namespace inp +{ +struct GaussianRoughness; +} + +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Gaussian roughness surface model. + * + * Approximates the surface roughness of an optical surface with the UNIFIED + * Gaussian roughness model. + */ +class GaussianRoughnessModel : public SurfaceModel +{ + public: + //!@{ + //! \name Type aliases + using InputT = inp::GaussianRoughness; + //!@} + + public: + // Construct the model from an ID and layer map + GaussianRoughnessModel(SurfaceModelId, + std::map const&); + + //! Get the list of physical surfaces this model applies to + VecSurfaceLayer const& get_surfaces() const final { return surfaces_; } + + // Execute the model with host data + void step(CoreParams const&, CoreStateHost&) const final; + + // Execute the model with device data + void step(CoreParams const&, CoreStateDevice&) const final; + + private: + VecSurfaceLayer surfaces_; + CollectionMirror data_; +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessData.hh b/src/celeritas/optical/surface/model/SmearRoughnessData.hh new file mode 100644 index 0000000000..0b386737e5 --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessData.hh @@ -0,0 +1,57 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessData.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/Macros.hh" +#include "corecel/data/Collection.hh" +#include "celeritas/Types.hh" +#include "celeritas/optical/surface/SurfaceModel.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Storage for uniform smear roughness model data. + */ +template +struct SmearRoughnessData +{ + //!@{ + //! \name Type aliases + template + using SurfaceItems = Collection; + //!@} + + //// DATA ///// + + SurfaceItems roughness; + + //// METHODS //// + + //! True if assigned + explicit CELER_FUNCTION operator bool() const + { + return !roughness.empty(); + } + + //! Assign from another set of data + template + SmearRoughnessData& operator=(SmearRoughnessData const& other) + { + CELER_EXPECT(other); + + roughness = other.roughness; + + return *this; + } +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessExecutor.hh b/src/celeritas/optical/surface/model/SmearRoughnessExecutor.hh new file mode 100644 index 0000000000..414be17dd9 --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessExecutor.hh @@ -0,0 +1,47 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessExecutor.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "celeritas/optical/CoreTrackView.hh" +#include "celeritas/optical/surface/SmearRoughnessSampler.hh" +#include "celeritas/optical/surface/SurfacePhysicsUtils.hh" + +#include "SmearRoughnessData.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Sample and save a facet normal for the smear roughness model. + * + * TODO: refactor into roughness applier and SmearNormalCalculator + */ +struct SmearRoughnessExecutor +{ + NativeCRef data; + + //! Apply smear roughness executor + CELER_FUNCTION void operator()(CoreTrackView& track) const + { + auto s_phys = track.surface_physics(); + auto sub_model_id = s_phys.interface(SurfacePhysicsOrder::roughness) + .internal_surface_id(); + CELER_ASSERT(sub_model_id < data.roughness.size()); + EnteringSurfaceNormalSampler sample_facet{ + track.geometry().dir(), + s_phys.global_normal(), + data.roughness[sub_model_id]}; + auto rng = track.rng(); + s_phys.facet_normal(sample_facet(rng)); + } +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessModel.cc b/src/celeritas/optical/surface/model/SmearRoughnessModel.cc new file mode 100644 index 0000000000..017b653a56 --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessModel.cc @@ -0,0 +1,81 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessModel.cc +//---------------------------------------------------------------------------// +#include "SmearRoughnessModel.hh" + +#include + +#include "corecel/data/CollectionBuilder.hh" +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/action/ActionLauncher.hh" +#include "celeritas/optical/action/TrackSlotExecutor.hh" + +#include "SmearRoughnessExecutor.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Construct the model from an ID and a layer map. + */ +SmearRoughnessModel::SmearRoughnessModel( + SurfaceModelId id, std::map const& layer_map) + : SurfaceModel(id, "roughness-smear") +{ + surfaces_.reserve(layer_map.size()); + std::transform(layer_map.begin(), + layer_map.end(), + std::back_inserter(surfaces_), + [](auto const& layer) { return layer.first; }); + + HostVal data; + auto build_roughness = CollectionBuilder{&data.roughness}; + + for (auto const& [surface, smear] : layer_map) + { + CELER_ENSURE(smear); + build_roughness.push_back(smear.roughness); + } + + CELER_ENSURE(data); + CELER_ENSURE(data.roughness.size() == layer_map.size()); + + data_ = CollectionMirror{std::move(data)}; +} + +//---------------------------------------------------------------------------// +/*! + * Execute model with host data. + */ +void SmearRoughnessModel::step(CoreParams const& params, + CoreStateHost& state) const +{ + launch_action(state, + make_surface_physics_executor( + params.ptr(), + state.ptr(), + SurfacePhysicsOrder::roughness, + this->surface_model_id(), + SmearRoughnessExecutor{data_.host_ref()})); +} + +//---------------------------------------------------------------------------// +/*! + * Execute kernel with device data. + */ +#if !CELER_USE_DEVICE +void SmearRoughnessModel::step(CoreParams const&, CoreStateDevice&) const +{ + CELER_NOT_CONFIGURED("CUDA OR HIP"); +} +#endif + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessModel.cu b/src/celeritas/optical/surface/model/SmearRoughnessModel.cu new file mode 100644 index 0000000000..a82143c6aa --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessModel.cu @@ -0,0 +1,40 @@ +//------------------------------ -*- cuda -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessModel.cu +//---------------------------------------------------------------------------// +#include "SmearRoughnessModel.hh" + +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/action/ActionLauncher.device.hh" +#include "celeritas/optical/action/TrackSlotExecutor.hh" + +#include "SmearRoughnessExecutor.hh" + +namespace celeritas +{ +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Launch kernel with device data. + */ +void SmearRoughnessModel::step(CoreParams const& params, + CoreStateDevice& state) const +{ + auto execute = make_surface_physics_executor( + params.ptr(), + state.ptr(), + SurfacePhysicsOrder::roughness, + this->surface_model_id(), + SmearRoughnessExecutor{data_.device_ref()}); + + static ActionLauncher const launch_kernel(*this); + launch_kernel(state, execute); +} + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SmearRoughnessModel.hh b/src/celeritas/optical/surface/model/SmearRoughnessModel.hh new file mode 100644 index 0000000000..3019d9f306 --- /dev/null +++ b/src/celeritas/optical/surface/model/SmearRoughnessModel.hh @@ -0,0 +1,59 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/surface/model/SmearRoughnessModel.hh +//---------------------------------------------------------------------------// +#pragma once + +#include "corecel/data/CollectionMirror.hh" +#include "celeritas/inp/SurfacePhysics.hh" +#include "celeritas/optical/surface/SurfaceModel.hh" + +#include "SmearRoughnessData.hh" + +namespace celeritas +{ +namespace inp +{ +struct SmearRoughness; +} + +namespace optical +{ +//---------------------------------------------------------------------------// +/*! + * Smear roughness model. + * + * Approximates the surface roughness of an optical surface with the GliSur3 + * uniform smear roughness model. + */ +class SmearRoughnessModel : public SurfaceModel +{ + public: + //!@{ + //! \name Type aliases + using InputT = inp::SmearRoughness; + //!@} + + public: + // Construct the model from an ID and layer map + SmearRoughnessModel(SurfaceModelId, std::map const&); + + //! Get the list of physical surfaces this model applies to + VecSurfaceLayer const& get_surfaces() const final { return surfaces_; } + + // Execute the model with host data + void step(CoreParams const&, CoreStateHost&) const final; + + // Execute the model with device data + void step(CoreParams const&, CoreStateDevice&) const final; + + private: + VecSurfaceLayer surfaces_; + CollectionMirror data_; +}; + +//---------------------------------------------------------------------------// +} // namespace optical +} // namespace celeritas diff --git a/src/celeritas/optical/surface/model/SurfaceInteractionApplier.hh b/src/celeritas/optical/surface/model/SurfaceInteractionApplier.hh index c81ca30e63..4bccb8b477 100644 --- a/src/celeritas/optical/surface/model/SurfaceInteractionApplier.hh +++ b/src/celeritas/optical/surface/model/SurfaceInteractionApplier.hh @@ -18,7 +18,7 @@ namespace optical /*! * Wrap a surface interaction executor and apply it to a track. * - * The functor \c F must take a \c CoreTrackview and return a \c + * The functor \c F must take a \c CoreTrackView and return a \c * SurfaceInteraction. */ template diff --git a/src/celeritas/phys/Interaction.hh b/src/celeritas/phys/Interaction.hh index 2578787217..0c3d78e4dc 100644 --- a/src/celeritas/phys/Interaction.hh +++ b/src/celeritas/phys/Interaction.hh @@ -29,6 +29,8 @@ namespace celeritas */ struct Interaction { + using Energy = units::MevEnergy; + //! Interaction result category enum class Action { @@ -39,11 +41,10 @@ struct Interaction failed, //!< Ran out of memory during sampling }; - units::MevEnergy energy; //!< Post-interaction energy + Energy energy; //!< Post-interaction energy Real3 direction; //!< Post-interaction direction Span secondaries; //!< Emitted secondaries - units::MevEnergy energy_deposition{0}; //!< Energy loss locally to - //!< material + Energy energy_deposition{0}; //!< Energy loss locally to material Action action{Action::scattered}; //!< Flags for interaction result // Return an interaction representing a recoverable error @@ -101,6 +102,8 @@ struct MscStep * * These values are calculated at the first step in every msc tracking volume * and reused at subsequent steps within the same volume. + * + * \todo move to physics step data */ struct MscRange { diff --git a/src/celeritas/phys/InteractionApplier.hh b/src/celeritas/phys/InteractionApplier.hh index 7a59f272da..18722b40e8 100644 --- a/src/celeritas/phys/InteractionApplier.hh +++ b/src/celeritas/phys/InteractionApplier.hh @@ -132,8 +132,7 @@ InteractionApplierBaseImpl::operator()(celeritas::CoreTrackView const& track) if (result.action != Interaction::Action::absorbed) { // Update direction - auto geo = track.geometry(); - geo.set_dir(result.direction); + track.geometry().set_dir(result.direction); } else { @@ -141,11 +140,13 @@ InteractionApplierBaseImpl::operator()(celeritas::CoreTrackView const& track) sim.status(TrackStatus::killed); } - real_type deposition = result.energy_deposition.value(); + using Energy = units::MevEnergy; + using MassCSq = units::MevMass; + + real_type deposition = value_as(result.energy_deposition); auto cutoff = track.cutoff(); if (cutoff.apply_post_interaction()) { - // Kill secondaries with energies below the production cut for (auto& secondary : result.secondaries) { if (cutoff.apply(secondary)) @@ -153,20 +154,28 @@ InteractionApplierBaseImpl::operator()(celeritas::CoreTrackView const& track) // Secondary is an electron, positron or gamma with energy // below the production cut -- deposit the energy locally // and clear the secondary - deposition += secondary.energy.value() * sim.weight(); + deposition += value_as(secondary.energy) * sim.weight(); auto sec_par = track.particle_record(secondary.particle_id); if (sec_par.is_antiparticle()) { // Conservation of energy for positrons - deposition += 2 * sec_par.mass().value(); + deposition += 2 * value_as(sec_par.mass()); } secondary = {}; } } } + + // Deposit energy and save secondaries auto phys = track.physics_step(); - phys.deposit_energy(units::MevEnergy{deposition}); - phys.secondaries(result.secondaries); + if (deposition > 0) + { + phys.deposit_energy(Energy{deposition}); + } + if (!result.secondaries.empty()) + { + phys.secondaries(result.secondaries); + } } //---------------------------------------------------------------------------// diff --git a/src/celeritas/phys/PDGNumber.hh b/src/celeritas/phys/PDGNumber.hh index 44755468c1..15baf0edbf 100644 --- a/src/celeritas/phys/PDGNumber.hh +++ b/src/celeritas/phys/PDGNumber.hh @@ -146,6 +146,10 @@ CELER_DEFINE_PDGNUMBER(anti_he3, -1000020030) CELER_DEFINE_PDGNUMBER(alpha, 1000020040) CELER_DEFINE_PDGNUMBER(anti_alpha, -1000020040) +// Muonic nuclei +CELER_DEFINE_PDGNUMBER(muonic_deuteron, 2000010020) +CELER_DEFINE_PDGNUMBER(muonic_triton, 2000010030) + //!@} #undef CELER_DEFINE_PDGNUMBER diff --git a/src/celeritas/phys/ParticleTrackView.hh b/src/celeritas/phys/ParticleTrackView.hh index c360562fc0..5b570e9906 100644 --- a/src/celeritas/phys/ParticleTrackView.hh +++ b/src/celeritas/phys/ParticleTrackView.hh @@ -61,9 +61,6 @@ class ParticleTrackView // Change the particle's energy [MeV] inline CELER_FUNCTION void energy(Energy); - // Reduce the particle's energy [MeV] - inline CELER_FUNCTION void subtract_energy(Energy); - //// DYNAMIC PROPERTIES (pure accessors, free) //// // Unique particle type identifier @@ -167,18 +164,6 @@ void ParticleTrackView::energy(Energy quantity) states_.particle_energy[track_slot_] = quantity.value(); } -//---------------------------------------------------------------------------// -/*! - * Reduce the particle's energy without undergoing a collision [MeV]. - */ -CELER_FUNCTION void ParticleTrackView::subtract_energy(Energy eloss) -{ - CELER_EXPECT(eloss >= zero_quantity()); - CELER_EXPECT(eloss <= this->energy()); - // TODO: save a read/write by only saving if eloss is positive? - states_.particle_energy[track_slot_] -= eloss.value(); -} - //---------------------------------------------------------------------------// // DYNAMIC PROPERTIES //---------------------------------------------------------------------------// diff --git a/src/celeritas/phys/PhysicsData.hh b/src/celeritas/phys/PhysicsData.hh index 99200b3eaa..b631cd59ae 100644 --- a/src/celeritas/phys/PhysicsData.hh +++ b/src/celeritas/phys/PhysicsData.hh @@ -13,6 +13,7 @@ #include "celeritas/Types.hh" #include "celeritas/em/data/AtomicRelaxationData.hh" #include "celeritas/em/data/EPlusGGData.hh" +#include "celeritas/em/data/ElectroNuclearData.hh" #include "celeritas/em/data/GammaNuclearData.hh" #include "celeritas/em/data/LivermorePEData.hh" #include "celeritas/grid/XsGridData.hh" @@ -156,6 +157,9 @@ struct HardwiredIds ProcessId annihilation; ModelId eplusgg; + ProcessId electro_nuclear; + ModelId electro_vd; + ProcessId gamma_nuclear; ModelId bertini_qgs; @@ -178,6 +182,7 @@ struct HardwiredModels // Model data EPlusGGData eplusgg; + ElectroNuclearData electro_vd; GammaNuclearData bertini_qgs; LivermorePEData livermore_pe; AtomicRelaxParamsData relaxation; diff --git a/src/celeritas/phys/PhysicsParams.cc b/src/celeritas/phys/PhysicsParams.cc index 621a28c523..692558900a 100644 --- a/src/celeritas/phys/PhysicsParams.cc +++ b/src/celeritas/phys/PhysicsParams.cc @@ -29,6 +29,7 @@ #include "corecel/sys/ScopedMem.hh" #include "celeritas/Types.hh" #include "celeritas/em/model/EPlusGGModel.hh" +#include "celeritas/em/model/ElectroNuclearModel.hh" #include "celeritas/em/model/GammaNuclearModel.hh" #include "celeritas/em/model/LivermorePEModel.hh" #include "celeritas/em/params/AtomicRelaxationParams.hh" // IWYU pragma: keep @@ -445,6 +446,11 @@ void PhysicsParams::build_ids(ParticleParams const& particles, data->hardwired.ids.eplusgg = model_id; data->hardwired.eplusgg = m->host_ref(); } + else if (dynamic_cast(&model)) + { + data->hardwired.ids.electro_nuclear = process_id; + data->hardwired.ids.electro_vd = model_id; + } else if (dynamic_cast(&model)) { data->hardwired.ids.gamma_nuclear = process_id; @@ -481,6 +487,14 @@ void PhysicsParams::build_hardwired() host_ref_.hardwired.livermore_pe = model->host_ref(); device_ref_.hardwired.livermore_pe = model->device_ref(); } + if (auto model_id = host_ref_.hardwired.ids.electro_vd) + { + auto const* model = dynamic_cast( + models_[model_id.get()].first.get()); + CELER_ASSERT(model); + host_ref_.hardwired.electro_vd = model->host_ref(); + device_ref_.hardwired.electro_vd = model->device_ref(); + } if (auto model_id = host_ref_.hardwired.ids.bertini_qgs) { auto const* model = dynamic_cast( diff --git a/src/celeritas/phys/PhysicsStepUtils.hh b/src/celeritas/phys/PhysicsStepUtils.hh index 340b8544a6..1f976327b2 100644 --- a/src/celeritas/phys/PhysicsStepUtils.hh +++ b/src/celeritas/phys/PhysicsStepUtils.hh @@ -11,16 +11,15 @@ #include "corecel/Types.hh" #include "corecel/cont/Range.hh" #include "corecel/math/Algorithms.hh" -#include "corecel/math/NumericLimits.hh" +#include "corecel/math/Quantity.hh" #include "corecel/random/distribution/GenerateCanonical.hh" #include "corecel/random/distribution/Selector.hh" #include "celeritas/Types.hh" #include "celeritas/grid/EnergyLossCalculator.hh" #include "celeritas/grid/InverseRangeCalculator.hh" #include "celeritas/grid/RangeCalculator.hh" -#include "celeritas/grid/SplineCalculator.hh" -#include "celeritas/grid/XsCalculator.hh" #include "celeritas/mat/MaterialTrackView.hh" +#include "celeritas/track/SimTrackView.hh" #include "ParticleTrackView.hh" #include "PhysicsStepView.hh" @@ -206,13 +205,6 @@ calc_physics_step_limit(MaterialTrackView const& material, * \note The inverse range correction assumes range is always the integral of * the stopping power/energy loss. * - * \todo The GEANT3 manual \cite{geant3-1993} makes the point that linear - * interpolation of energy - * loss rate results in a piecewise constant energy deposition curve, which is - * why they use spline interpolation. Investigate higher-order reconstruction - * of energy loss curve, e.g. through spline-based interpolation or log-log - * interpolation. - * * \note See section 7.2.4 Run Time Energy Loss Computation of the Geant4 * physics manual \cite{g4prm}. See also the longer discussions in section 8 * of PHYS010 of the Geant3 manual. diff --git a/src/celeritas/phys/PhysicsStepView.hh b/src/celeritas/phys/PhysicsStepView.hh index c8a05dd502..97cf862f49 100644 --- a/src/celeritas/phys/PhysicsStepView.hh +++ b/src/celeritas/phys/PhysicsStepView.hh @@ -10,6 +10,7 @@ #include "corecel/Macros.hh" #include "corecel/data/StackAllocator.hh" #include "corecel/math/NumericLimits.hh" +#include "corecel/math/Quantity.hh" #include "corecel/sys/ThreadId.hh" #include "celeritas/em/interactor/AtomicRelaxationHelper.hh" @@ -68,6 +69,11 @@ class PhysicsStepView // Accumulate into local step's energy deposition inline CELER_FUNCTION void deposit_energy(Energy); + // Accumulate into local step's energy deposition from particle + template + inline CELER_FUNCTION void + deposit_energy_from(Energy deposited, PTV&& particle); + // Set secondaries during an interaction inline CELER_FUNCTION void secondaries(Span); @@ -184,11 +190,24 @@ CELER_FUNCTION void PhysicsStepView::reset_energy_deposition_debug() */ CELER_FUNCTION void PhysicsStepView::deposit_energy(Energy energy) { - CELER_EXPECT(energy >= zero_quantity()); - // TODO: save a memory read/write by skipping if energy is zero? + CELER_EXPECT(energy > zero_quantity()); this->state().energy_deposition += energy.value(); } +//---------------------------------------------------------------------------// +/*! + * Deposit energy from the particle. + */ +template +inline CELER_FUNCTION void +PhysicsStepView::deposit_energy_from(Energy deposited, PTV&& particle) +{ + CELER_EXPECT(deposited > zero_quantity()); + CELER_EXPECT(deposited <= particle.energy()); + this->deposit_energy(deposited); + particle.energy(particle.energy() - deposited); +} + //---------------------------------------------------------------------------// /*! * Set secondaries during an interaction, or clear them with an empty span. diff --git a/src/celeritas/phys/PhysicsTrackView.hh b/src/celeritas/phys/PhysicsTrackView.hh index 5c764fb6fa..413569d733 100644 --- a/src/celeritas/phys/PhysicsTrackView.hh +++ b/src/celeritas/phys/PhysicsTrackView.hh @@ -14,6 +14,7 @@ #include "celeritas/Quantities.hh" #include "celeritas/Types.hh" #include "celeritas/em/xs/EPlusGGMacroXsCalculator.hh" +#include "celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh" #include "celeritas/em/xs/GammaNuclearMicroXsCalculator.hh" #include "celeritas/em/xs/LivermorePEMicroXsCalculator.hh" #include "celeritas/grid/GridIdFinder.hh" @@ -458,11 +459,17 @@ CELER_FUNCTION real_type PhysicsTrackView::calc_xs(ParticleProcessId ppid, result = MacroXsCalculator( params_.hardwired.livermore_pe, material)(energy); } + else if (model_id == params_.hardwired.ids.eplusgg) { result = EPlusGGMacroXsCalculator(params_.hardwired.eplusgg, material)(energy); } + else if (model_id == params_.hardwired.ids.electro_vd) + { + result = MacroXsCalculator( + params_.hardwired.electro_vd, material)(energy); + } else if (model_id == params_.hardwired.ids.bertini_qgs) { result = MacroXsCalculator( @@ -671,6 +678,12 @@ CELER_FUNCTION ModelId PhysicsTrackView::model_id(ParticleModelId pmid) const * * Below \c min_range, no step scaling is applied, but the step can still * be arbitrarily small. + * + * \todo Rename \c calc_eloss_step_limit . This step limiter allows tuning the + * accuracy loss from approximating a constant cross section along the step. We + * should also split this into limiting the \em actual range (where the energy + * goes to zero or the minimum allowable tracking range) versus a dE/dx + * limiter. */ CELER_FUNCTION real_type PhysicsTrackView::range_to_step(real_type range) const { diff --git a/src/celeritas/phys/ProcessBuilder.cc b/src/celeritas/phys/ProcessBuilder.cc index 8175e6dc3a..2d3249fb2e 100644 --- a/src/celeritas/phys/ProcessBuilder.cc +++ b/src/celeritas/phys/ProcessBuilder.cc @@ -18,6 +18,7 @@ #include "celeritas/em/process/CoulombScatteringProcess.hh" #include "celeritas/em/process/EIonizationProcess.hh" #include "celeritas/em/process/EPlusAnnihilationProcess.hh" +#include "celeritas/em/process/ElectroNuclearProcess.hh" #include "celeritas/em/process/GammaConversionProcess.hh" #include "celeritas/em/process/GammaNuclearProcess.hh" #include "celeritas/em/process/MuBremsstrahlungProcess.hh" @@ -114,6 +115,7 @@ auto ProcessBuilder::operator()(IPC ipc) -> SPProcess {IPC::coulomb_scat, &ProcessBuilder::build_coulomb}, {IPC::e_brems, &ProcessBuilder::build_ebrems}, {IPC::e_ioni, &ProcessBuilder::build_eioni}, + {IPC::electro_nuclear, &ProcessBuilder::build_electro_nuclear}, {IPC::gamma_nuclear, &ProcessBuilder::build_gamma_nuclear}, {IPC::mu_brems, &ProcessBuilder::build_mubrems}, {IPC::mu_ioni, &ProcessBuilder::build_muioni}, @@ -158,6 +160,13 @@ auto ProcessBuilder::build_ebrems() -> SPProcess options); } +//---------------------------------------------------------------------------// +auto ProcessBuilder::build_electro_nuclear() -> SPProcess +{ + return std::make_shared(this->particle(), + this->material()); +} + //---------------------------------------------------------------------------// auto ProcessBuilder::build_gamma_nuclear() -> SPProcess { diff --git a/src/celeritas/phys/ProcessBuilder.hh b/src/celeritas/phys/ProcessBuilder.hh index adce498370..ceaebe1ab2 100644 --- a/src/celeritas/phys/ProcessBuilder.hh +++ b/src/celeritas/phys/ProcessBuilder.hh @@ -114,6 +114,7 @@ class ProcessBuilder auto build_coulomb() -> SPProcess; auto build_ebrems() -> SPProcess; auto build_eioni() -> SPProcess; + auto build_electro_nuclear() -> SPProcess; auto build_gamma_nuclear() -> SPProcess; auto build_mubrems() -> SPProcess; auto build_muioni() -> SPProcess; diff --git a/src/celeritas/phys/SurfaceModel.hh b/src/celeritas/phys/SurfaceModel.hh index 46ee8951a3..dc2df4c957 100644 --- a/src/celeritas/phys/SurfaceModel.hh +++ b/src/celeritas/phys/SurfaceModel.hh @@ -54,7 +54,7 @@ class SurfaceModel std::string_view label() const { return label_; } protected: - // Construct with label and model ID + // Construct with static label and model ID SurfaceModel(SurfaceModelId, std::string_view); //!@{ diff --git a/src/celeritas/phys/detail/PreStepExecutor.hh b/src/celeritas/phys/detail/PreStepExecutor.hh index d38d702a33..2443aae0e6 100644 --- a/src/celeritas/phys/detail/PreStepExecutor.hh +++ b/src/celeritas/phys/detail/PreStepExecutor.hh @@ -102,7 +102,11 @@ PreStepExecutor::operator()(celeritas::CoreTrackView const& track) // Initialize along-step action based on particle charge: // This should eventually be dependent on region, energy, etc. sim.along_step_action([&particle, &scalars = track.core_scalars()] { - if (particle.charge() == zero_quantity()) + if (particle.is_stopped()) + { + return ActionId{}; + } + else if (particle.charge() == zero_quantity()) { return scalars.along_step_neutral_action; } diff --git a/src/celeritas/phys/detail/TrackingCutExecutor.hh b/src/celeritas/phys/detail/TrackingCutExecutor.hh index eed10a67ad..2f31484520 100644 --- a/src/celeritas/phys/detail/TrackingCutExecutor.hh +++ b/src/celeritas/phys/detail/TrackingCutExecutor.hh @@ -7,6 +7,7 @@ #pragma once #include "corecel/Macros.hh" +#include "corecel/math/Quantity.hh" #include "celeritas/Types.hh" #include "celeritas/geo/GeoTrackView.hh" #include "celeritas/global/CoreTrackView.hh" @@ -77,7 +78,7 @@ TrackingCutExecutor::operator()(celeritas::CoreTrackView& track) #endif track.physics_step().deposit_energy(Energy{deposited}); - particle.subtract_energy(particle.energy()); + particle.energy(zero_quantity()); sim.status(TrackStatus::killed); } diff --git a/src/celeritas/setup/FrameworkInput.cc b/src/celeritas/setup/FrameworkInput.cc index 8085a75eb7..858e3a6082 100644 --- a/src/celeritas/setup/FrameworkInput.cc +++ b/src/celeritas/setup/FrameworkInput.cc @@ -33,13 +33,22 @@ FrameworkLoaded framework_input(inp::FrameworkInput& fi) CELER_LOG(info) << "Activating Celeritas version " << version_string << " on " << (Device::num_devices() > 0 ? "GPU" : "CPU"); + CELER_VALIDATE(!(fi.adjust && fi.adjust_optical), + << "cannot setup both a problem and an optical-only " + "problem"); + + // TODO: How to determine which problem to setup without requiring adjust? + CELER_EXPECT(fi.adjust || fi.adjust_optical); + + FrameworkLoaded result; + // Set up system setup::system(fi.system); // Load Geant4 geometry wrapper, which saves it as global CELER_ASSERT(celeritas::global_geant_geo().expired()); - auto geo = GeantGeoParams::from_tracking_manager(); - CELER_ASSERT(geo); + result.geo = GeantGeoParams::from_tracking_manager(); + CELER_ASSERT(result.geo); // Load Geant4 data from user setup ImportData imported; @@ -48,45 +57,63 @@ FrameworkLoaded framework_input(inp::FrameworkInput& fi) // Load physics from external Geant4 data files setup::physics_from(inp::PhysicsFromGeantFiles{}, imported); - // Set up problem - inp::Problem problem; + if (fi.adjust_optical) + { + // Set up optical-only problem + inp::OpticalProblem problem; - // Copy optical physics from import data - // (TODO: will be replaced) - problem.physics.optical = imported.optical_physics; + problem.physics = imported.optical_physics; + problem.model = result.geo->make_model_input(); - // Load geometry, surfaces, regions from Geant4 world pointer - problem.model = geo->make_model_input(); + // Adjust problem + fi.adjust_optical(problem); - // Load physics - for (std::string const& process_name : fi.physics_import.ignore_processes) + // Save filename for diagnostic output + result.output_file = problem.output_file; + + // Set up core params + result.problem = setup::problem(problem, imported); + } + else { - ImportProcessClass ipc; - try - { - ipc = geant_name_to_import_process_class(process_name); - } - catch (RuntimeError const&) + // Set up problem + inp::Problem problem; + + // Copy optical physics from import data + // (TODO: will be replaced) + problem.physics.optical = imported.optical_physics; + + // Load geometry, surfaces, regions from Geant4 world pointer + problem.model = result.geo->make_model_input(); + + // Load physics + for (std::string const& process_name : + fi.physics_import.ignore_processes) { - CELER_LOG(error) << "User-ignored process '" << process_name - << "' is unknown to Celeritas"; - continue; + ImportProcessClass ipc; + try + { + ipc = geant_name_to_import_process_class(process_name); + } + catch (RuntimeError const&) + { + CELER_LOG(error) << "User-ignored process '" << process_name + << "' is unknown to Celeritas"; + continue; + } + problem.physics.em.user_processes.emplace( + ipc, WarnAndIgnoreProcess{ipc}); } - problem.physics.em.user_processes.emplace(ipc, - WarnAndIgnoreProcess{ipc}); - } - // Adjust problem - if (fi.adjust) - { + // Adjust problem fi.adjust(problem); - } - FrameworkLoaded result; - result.geo = std::move(geo); + // Save filename for diagnostic output + result.output_file = problem.diagnostics.output_file; - // Set up core params - result.problem = setup::problem(problem, imported); + // Set up core params + result.problem = setup::problem(problem, imported); + } return result; } diff --git a/src/celeritas/setup/FrameworkInput.hh b/src/celeritas/setup/FrameworkInput.hh index 9486b5e57e..65c9b58087 100644 --- a/src/celeritas/setup/FrameworkInput.hh +++ b/src/celeritas/setup/FrameworkInput.hh @@ -7,6 +7,7 @@ #pragma once #include +#include #include "Problem.hh" @@ -26,9 +27,11 @@ namespace setup struct FrameworkLoaded { //! Loaded problem - ProblemLoaded problem; + std::variant problem; //! Geant4 geometry wrapper std::shared_ptr geo; + //! Write diagnostic output + std::string output_file; }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/setup/Problem.cc b/src/celeritas/setup/Problem.cc index 1de82d1ac2..1b2e5a0651 100644 --- a/src/celeritas/setup/Problem.cc +++ b/src/celeritas/setup/Problem.cc @@ -6,6 +6,7 @@ //---------------------------------------------------------------------------// #include "Problem.hh" +#include #include #include #include @@ -18,6 +19,7 @@ #include "corecel/io/Logger.hh" #include "corecel/io/OutputInterfaceAdapter.hh" #include "corecel/io/OutputRegistry.hh" +#include "corecel/io/StringUtils.hh" #include "corecel/math/Algorithms.hh" #include "corecel/random/params/RngParams.hh" #include "corecel/sys/ActionRegistry.hh" @@ -57,20 +59,24 @@ #include "celeritas/inp/Problem.hh" #include "celeritas/inp/Scoring.hh" #include "celeritas/inp/Tracking.hh" +#include "celeritas/io/EventWriter.hh" #include "celeritas/io/ImportData.hh" #include "celeritas/io/ImportProcess.hh" +#include "celeritas/io/JsonEventWriter.hh" +#include "celeritas/io/OffloadWriter.hh" #include "celeritas/io/RootCoreParamsOutput.hh" +#include "celeritas/io/RootEventWriter.hh" #include "celeritas/mat/MaterialParams.hh" #include "celeritas/optical/CoreParams.hh" #include "celeritas/optical/MaterialParams.hh" -#include "celeritas/optical/ModelImporter.hh" #include "celeritas/optical/OpticalCollector.hh" -#include "celeritas/optical/OpticalSizes.json.hh" #include "celeritas/optical/PhysicsParams.hh" #include "celeritas/optical/SimParams.hh" #include "celeritas/optical/Transporter.hh" #include "celeritas/optical/gen/CherenkovParams.hh" +#include "celeritas/optical/gen/DirectGeneratorAction.hh" #include "celeritas/optical/gen/GeneratorAction.hh" +#include "celeritas/optical/gen/PrimaryGeneratorAction.hh" #include "celeritas/optical/gen/ScintillationParams.hh" #include "celeritas/optical/surface/SurfacePhysicsParams.hh" #include "celeritas/phys/CutoffParams.hh" @@ -292,7 +298,9 @@ auto build_along_step(inp::Field const& var_field, //---------------------------------------------------------------------------// /*! - * Construct optical parameters. + * Construct optical parameters from EM parameters. + * + * \todo build unique RNG streams for optical loop */ auto build_optical_params(inp::Problem const& p, CoreParams const& core, @@ -301,58 +309,107 @@ auto build_optical_params(inp::Problem const& p, CELER_VALIDATE(!imported.optical_materials.empty(), << "an optical tracking loop was requested but no optical " "materials are present"); + CELER_EXPECT(p.physics.optical); + CELER_EXPECT(p.control.optical_capacity); - optical::CoreParams::Input params; - params.geometry = core.geometry(); - params.material = optical::MaterialParams::from_import( - imported, *core.geomaterial(), *core.material()); - // TODO: unique RNG streams for optical loop - params.rng = core.rng(); - params.sim - = std::make_shared(p.tracking.optical_limits); - params.surface = core.surface(); - params.action_reg = std::make_shared(); - params.gen_reg = std::make_shared(); - params.max_streams = core.max_streams(); - { - // Construct optical physics models - optical::PhysicsParams::Input pp_inp; - pp_inp.materials = params.material; - pp_inp.action_registry = params.action_reg.get(); - optical::ModelImporter importer{ - imported, params.material, core.material()}; - for (auto const& model : imported.optical_models) - { - if (auto builder = importer(model.model_class)) - { - pp_inp.model_builders.push_back(*builder); - } - } - params.physics - = std::make_shared(std::move(pp_inp)); - } + optical::CoreParams::Input pi; - // Construct optical surface physics models - CELER_ASSERT(p.physics.optical); - params.surface_physics = std::make_shared( - params.action_reg.get(), p.physics.optical.surfaces); + // Registries + pi.action_reg = std::make_shared(); + pi.output_reg = core.output_reg(); + pi.gen_reg = std::make_shared(); + pi.aux_reg = core.aux_reg(); - // Add photon generating processes + // Geometry, physics, core + pi.geometry = core.geometry(); + pi.material = optical::MaterialParams::from_import( + imported, *core.geomaterial(), *core.material()); + pi.physics = optical::PhysicsParams::from_import( + imported, core.material(), pi.material, pi.action_reg); + pi.rng = core.rng(); + pi.sim = std::make_shared(p.tracking.optical_limits); + pi.surface = core.surface(); + pi.surface_physics = std::make_shared( + pi.action_reg.get(), p.physics.optical.surfaces); + // TODO: copy detectors from core + + // Photon generating processes if (p.physics.optical.cherenkov) { - params.cherenkov = std::make_shared(*params.material); + pi.cherenkov = std::make_shared(*pi.material); } if (p.physics.optical.scintillation) { - params.scintillation + pi.scintillation = ScintillationParams::from_import(imported, core.particle()); } - //! \todo Get sensitive detectors + // Streams and capacities + pi.max_streams = core.max_streams(); + pi.capacity = *p.control.optical_capacity; - CELER_ENSURE(params); + CELER_ENSURE(pi); + return std::make_shared(std::move(pi)); +} + +//---------------------------------------------------------------------------// +/*! + * Construct optical parameters from optical problem definition. + */ +auto build_optical_params(inp::OpticalProblem const& p, + ModelLoaded&& loaded_model, + ImportData const& imported) +{ + CELER_VALIDATE(!imported.optical_materials.empty(), + << "an optical tracking loop was requested but no optical " + "materials are present"); - return std::make_shared(std::move(params)); + // Create materials and geometry/material coupling + auto material = MaterialParams::from_import(imported); + auto geomaterial = GeoMaterialParams::from_import( + imported, loaded_model.geometry, loaded_model.volume, material); + + optical::CoreParams::Input pi; + + // Registries + pi.action_reg = std::make_shared(); + pi.output_reg = nullptr; + pi.gen_reg = std::make_shared(); + pi.aux_reg = nullptr; // TODO: require instead of building in CP + + // Geometry, materials, physics + pi.geometry = std::move(loaded_model.geometry); + pi.material = optical::MaterialParams::from_import( + imported, *geomaterial, *material); + pi.physics = optical::PhysicsParams::from_import( + imported, material, pi.material, pi.action_reg); + pi.rng = std::make_shared(p.seed); + pi.sim = std::make_shared(p.limits); + pi.surface = std::move(loaded_model.surface); + pi.surface_physics = std::make_shared( + pi.action_reg.get(), p.physics.surfaces); + // TODO: save loaded detectors + + // Streams and capacities + pi.max_streams = p.num_streams; + pi.capacity = p.capacity; + + // Photon generating processes are needed to offload via Geant4 optical + if (p.physics.cherenkov) + { + pi.cherenkov = std::make_shared(*pi.material); + } + if (p.physics.scintillation) + { + auto particle = ParticleParams::from_import(imported); + pi.scintillation = ScintillationParams::from_import(imported, particle); + CELER_ASSERT(pi.scintillation); + } + + std::move(loaded_model) = {}; + + CELER_ENSURE(pi); + return std::make_shared(std::move(pi)); } //---------------------------------------------------------------------------// @@ -364,9 +421,6 @@ auto build_optical_offload( CoreParams const& params, std::shared_ptr const& optical_params) { - CELER_EXPECT(std::holds_alternative( - p.physics.optical_generator)); - CELER_VALIDATE(optical_params->cherenkov() || optical_params->scintillation(), << "failed to construct optical offload procesess"); @@ -460,6 +514,7 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) } params.surface = std::make_shared(); } + // TODO: save detectors to params } // Load materials @@ -526,8 +581,6 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) //// DIAGNOSTICS //// - result.output_file = p.diagnostics.output_file; - // TODO: timers, counters, perfetto_file if (p.diagnostics.action) @@ -604,7 +657,28 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) // TODO: this is only implemented in accel::SharedParams, not in // celeritas core: should hook this into the to-be-updated // "primary" mechanism - result.offload_file = ef.offload; + if (!ef.offload.empty()) + { + std::unique_ptr writer; + if (ends_with(ef.offload, ".jsonl")) + { + writer.reset( + new JsonEventWriter(ef.offload, core_params->particle())); + } + else if (ends_with(ef.offload, ".root")) + { + writer.reset(new RootEventWriter( + std::make_shared(ef.offload.c_str()), + core_params->particle())); + } + else + { + writer.reset( + new EventWriter(ef.offload, core_params->particle())); + } + result.offload_writer + = std::make_shared(std::move(writer)); + } } //// STEP COLLECTORS //// @@ -654,11 +728,6 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) *core_params, std::move(step_interfaces)); } - // Whether to accumulate timing results for actions - bool action_times - = (!celeritas::device() - || (p.control.device_debug && p.control.device_debug->sync_stream)); - if (p.control.optical_capacity) { if (core_params->surface()->empty()) @@ -667,63 +736,12 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) "any geometry surface definitions: default " "physics will be used for all surfaces"; } - + // Construct the optical params from the core params auto optical_params = build_optical_params(p, *core_params, imported); - // Save optical diagnostic information - core_params->output_reg()->insert( - std::make_shared( - optical_params->action_reg(), "optical-actions")); - - auto const& capacity = *p.control.optical_capacity; - - // Add optical sizes - OpticalSizes sizes; - sizes.streams = core_params->max_streams(); - sizes.generators = capacity.generators; - sizes.tracks = capacity.tracks; - - core_params->output_reg()->insert( - OutputInterfaceAdapter::from_rvalue_ref( - OutputInterface::Category::internal, - "optical-sizes", - std::move(sizes))); - - std::visit( - Overload{ - [&](inp::OpticalEmGenerator) { - // Generate Cherenkov or scintillation optical - // photons from Celeritas tracks - result.optical_collector = build_optical_offload( - p, *core_params, optical_params); - }, - [&](inp::OpticalOffloadGenerator) { - // Generate Cherenkov or scintillation photons - optical::GeneratorAction::make_and_insert( - *core_params, *optical_params, capacity.generators); - - // Build the optical transporter \em after all optical - // actions have been added to the registry - optical::Transporter::Input inp; - inp.params = optical_params; - if (action_times) - { - // Create aux data to accumulate optical action times - inp.action_times = ActionTimes::make_and_insert( - optical_params->action_reg(), - core_params->aux_reg(), - "optical-action-times"); - } - result.optical_transporter - = std::make_shared( - std::move(inp)); - }, - [](inp::OpticalPrimaryGenerator) { - //! \todo Enable optical primary generator - CELER_NOT_IMPLEMENTED("optical primary generator"); - }, - }, - p.physics.optical_generator); + // Construct the optical offload and generation actions + result.optical_collector + = build_optical_offload(p, *core_params, optical_params); } else { @@ -738,7 +756,8 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) result.actions = [&] { ActionSequence::Options opt; auto const& action_reg = core_params->action_reg(); - if (action_times) + if (!celeritas::device() + || (p.control.device_debug && p.control.device_debug->sync_stream)) { // Create aux data to accumulate action times opt.action_times = ActionTimes::make_and_insert( @@ -755,6 +774,97 @@ ProblemLoaded problem(inp::Problem const& p, ImportData const& imported) return result; } +//---------------------------------------------------------------------------// +/*! + * Create optical "core params" from a problem definition and import data. + * + * This constructs the optical params \em without additionally constructing the + * core params. + */ +OpticalProblemLoaded +problem(inp::OpticalProblem const& p, ImportData const& imported) +{ + CELER_LOG(status) << "Initializing problem"; + + ScopedMem record_mem("setup::problem"); + ScopedProfiling profile_this{"setup::problem"}; + + CELER_VALIDATE(!imported.optical_materials.empty(), + << "an optical tracking loop was requested but no optical " + "materials are present"); + + // Load geometry and model + if (auto* filename = std::get_if(&p.model.geometry)) + { + CELER_VALIDATE(!filename->empty(), + << "empty filename in problem.model.geometry"); + } + auto loaded_model = setup::model(p.model); + CELER_VALIDATE(loaded_model.surface && !loaded_model.surface->disabled(), + << "surfaces are required for optical physics"); + if (loaded_model.surface->empty()) + { + CELER_LOG(warning) << "Problem contains optical physics without any " + "geometry surface definitions: default physics " + "will be used for all surfaces"; + } + + // Set up streams + CELER_VALIDATE(p.num_streams > 0, + << "p.num_streams must be manually set before setup"); + if (auto& device = celeritas::device()) + { + device.create_streams(p.num_streams); + } + + // Build optical params + auto params = build_optical_params(p, std::move(loaded_model), imported); + CELER_ASSERT(params); + + // Construct the optical generator + result.generator = std::visit( + Overload{ + [&](inp::OpticalEmGenerator) -> SPGeneratorBase { + CELER_VALIDATE(false, + << "OpticalEmGenerator cannot be used " + "with only optical physics enabled"); + return nullptr; + }, + [&](inp::OpticalOffloadGenerator) -> SPGeneratorBase { + return optical::GeneratorAction::make_and_insert( + *params, p.capacity.generators); + }, + [&](inp::OpticalPrimaryGenerator opg) -> SPGeneratorBase { + return optical::PrimaryGeneratorAction::make_and_insert( + *params, std::move(opg)); + }, + [&](inp::OpticalDirectGenerator) -> SPGeneratorBase { + return optical::DirectGeneratorAction::make_and_insert(*params); + }, + [&](inp::OpticalTrackOffload) -> SPGeneratorBase { + return optical::DirectGeneratorAction::make_and_insert(*params); + }, + }, + p.generator); + + OpticalProblemLoaded result; + + // Build the optical transporter \em after all optical actions have been + // added to the registry + optical::Transporter::Input ti; + if (p.timers.action) + { + // Create aux data to accumulate optical action times + ti.action_times = ActionTimes::make_and_insert( + params->action_reg(), params->aux_reg(), "optical-action-times"); + } + ti.params = std::move(params); + result.transporter = std::make_shared(std::move(ti)); + + CELER_ENSURE(result.transporter); + return result; +} + //---------------------------------------------------------------------------// } // namespace setup } // namespace celeritas diff --git a/src/celeritas/setup/Problem.hh b/src/celeritas/setup/Problem.hh index c41daddbb5..967b67e651 100644 --- a/src/celeritas/setup/Problem.hh +++ b/src/celeritas/setup/Problem.hh @@ -14,6 +14,7 @@ namespace celeritas //---------------------------------------------------------------------------// namespace inp { +struct OpticalProblem; struct Problem; } // namespace inp @@ -25,6 +26,7 @@ class Transporter; class ActionSequence; class CoreParams; class GeantSd; +class OffloadWriter; class OpticalCollector; class RootFileManager; class StepCollector; @@ -44,8 +46,6 @@ struct ProblemLoaded //! Step collector std::shared_ptr step_collector; - //! Optical-only offload management - std::shared_ptr optical_transporter; //! Combined EM and optical offload management std::shared_ptr optical_collector; //! Geant4 SD interface @@ -54,24 +54,30 @@ struct ProblemLoaded std::shared_ptr root_manager; //! Action sequence std::shared_ptr actions; - //!@} //!@{ //! \name Temporary: to be used downstream - //! \todo These should be refactored: should be built in Problem //! Write offloaded primaries - std::string offload_file; - //! Write diagnostic output - std::string output_file; - + std::shared_ptr offload_writer; //!@} }; +//---------------------------------------------------------------------------// +//! Result from loaded optical standalone input to be used in front-end apps +struct OpticalProblemLoaded +{ + //! Optical-only problem setup + std::shared_ptr transporter; +}; + //---------------------------------------------------------------------------// // Set up the problem ProblemLoaded problem(inp::Problem const& p, ImportData const& imported); +// Set up the optical-only problem +OpticalProblemLoaded +problem(inp::OpticalProblem const& p, ImportData const& imported); //---------------------------------------------------------------------------// } // namespace setup diff --git a/src/celeritas/track/CoreStateCounters.hh b/src/celeritas/track/CoreStateCounters.hh index 53c716435e..144a8af65a 100644 --- a/src/celeritas/track/CoreStateCounters.hh +++ b/src/celeritas/track/CoreStateCounters.hh @@ -14,9 +14,10 @@ namespace celeritas /*! * Counters for within-step track initialization and activity. * - * These counters are updated *by value on the host at every step* so they - * should not be stored in TrackInitStateData because then the device-memory - * copy will not be synchronized. + * When running device code, these counters are now updated on the device + * throughout the step, so they are stored in TrackInitStateData. They need to + * be synchronized between the host and device before and after the step to + * maintain consistency. * * For all user \c StepActionOrder (TODO: this may change if we add a * "user_end"), all but the secondaries/alive @@ -49,6 +50,11 @@ struct CoreStateCounters size_type num_secondaries{0}; //!< Number of secondaries produced size_type num_alive{0}; //!< Number of alive tracks at end //!@} + + //!@{ + //! \name Set by CUDA CUB when partitioning the tracks, unused by celeritas + size_type num_neutral{0}; //!< Number of neutral tracks + //!@} }; //---------------------------------------------------------------------------// diff --git a/src/celeritas/track/ExtendFromPrimariesAction.cc b/src/celeritas/track/ExtendFromPrimariesAction.cc index 027a71a60f..305fc5d9b5 100644 --- a/src/celeritas/track/ExtendFromPrimariesAction.cc +++ b/src/celeritas/track/ExtendFromPrimariesAction.cc @@ -103,7 +103,7 @@ void ExtendFromPrimariesAction::insert(CoreParams const& params, CoreStateInterface& state, Span host_primaries) const { - size_type num_initializers = state.counters().num_initializers; + size_type num_initializers = state.sync_get_counters().num_initializers; size_type init_capacity = params.init()->capacity(); CELER_VALIDATE(host_primaries.size() + num_initializers <= init_capacity, @@ -184,15 +184,18 @@ void ExtendFromPrimariesAction::step_impl(CoreParams const& params, CoreState& state) const { auto& primaries = get>(state.aux(), aux_id_); + auto counters = state.sync_get_counters(); // Create track initializers from primaries - state.counters().num_initializers += primaries.count; + counters.num_initializers += primaries.count; + state.sync_put_counters(counters); this->process_primaries(params, state, primaries); // Mark that the primaries have been processed - state.counters().num_generated += primaries.count; - state.counters().num_pending = 0; + counters.num_generated += primaries.count; + counters.num_pending = 0; primaries.count = 0; + state.sync_put_counters(counters); } //---------------------------------------------------------------------------// @@ -205,10 +208,8 @@ void ExtendFromPrimariesAction::process_primaries( PrimaryStateData const& pstate) const { auto primaries = pstate.primaries(); - detail::ProcessPrimariesExecutor execute{params.ptr(), - state.ptr(), - state.counters(), - primaries}; + detail::ProcessPrimariesExecutor execute{ + params.ptr(), state.ptr(), primaries}; return launch_action(*this, primaries.size(), params, state, execute); } diff --git a/src/celeritas/track/ExtendFromPrimariesAction.cu b/src/celeritas/track/ExtendFromPrimariesAction.cu index 4d37f4be24..3e1ea93f50 100644 --- a/src/celeritas/track/ExtendFromPrimariesAction.cu +++ b/src/celeritas/track/ExtendFromPrimariesAction.cu @@ -24,11 +24,9 @@ void ExtendFromPrimariesAction::process_primaries( PrimaryStateData const& pstate) const { auto primaries = pstate.primaries(); + auto counters = state.sync_get_counters(); detail::ProcessPrimariesExecutor execute_thread{ - params.ptr(), - state.ptr(), - state.counters(), - primaries}; + params.ptr(), state.ptr(), primaries}; static ActionLauncher const launch_kernel(*this); if (!primaries.empty()) { diff --git a/src/celeritas/track/ExtendFromSecondariesAction.cc b/src/celeritas/track/ExtendFromSecondariesAction.cc index d8595f7378..139b98aa99 100644 --- a/src/celeritas/track/ExtendFromSecondariesAction.cc +++ b/src/celeritas/track/ExtendFromSecondariesAction.cc @@ -56,7 +56,6 @@ void ExtendFromSecondariesAction::step_impl(CoreParams const& core_params, CoreState& core_state) const { TrackInitStateData& init = core_state.ref().init; - CoreStateCounters& counters = core_state.counters(); // Launch a kernel to identify which track slots are still alive and count // the number of surviving secondaries per track @@ -64,14 +63,14 @@ void ExtendFromSecondariesAction::step_impl(CoreParams const& core_params, // Remove all elements in the vacancy vector that were flagged as active // tracks, leaving the (sorted) indices of the empty slots - counters.num_vacancies - = detail::remove_if_alive(init.vacancies, core_state.stream_id()); + detail::remove_if_alive(init, core_state.stream_id()); // The exclusive prefix sum of the number of secondaries produced by each // track is used to get the start index in the vector of track initializers // for each thread. Starting at that index, each thread creates track // initializers from all surviving secondaries produced in its // interaction. + auto counters = core_state.sync_get_counters(); counters.num_secondaries = detail::exclusive_scan_counts( init.secondary_counts, core_state.stream_id()); @@ -96,6 +95,7 @@ void ExtendFromSecondariesAction::step_impl(CoreParams const& core_params, // Launch a kernel to create track initializers from secondaries counters.num_alive = core_state.size() - counters.num_vacancies; + core_state.sync_put_counters(counters); this->process_secondaries(core_params, core_state); } @@ -122,9 +122,7 @@ void ExtendFromSecondariesAction::process_secondaries( { //! \todo Wrap with a regular track executor but without remapping slots? detail::ProcessSecondariesExecutor execute{ - core_params.ptr(), - core_state.ptr(), - core_state.counters()}; + core_params.ptr(), core_state.ptr()}; launch_action(*this, core_params, core_state, execute); } diff --git a/src/celeritas/track/ExtendFromSecondariesAction.cu b/src/celeritas/track/ExtendFromSecondariesAction.cu index 4ee5dacafa..5d501d24ae 100644 --- a/src/celeritas/track/ExtendFromSecondariesAction.cu +++ b/src/celeritas/track/ExtendFromSecondariesAction.cu @@ -62,9 +62,7 @@ void ExtendFromSecondariesAction::process_secondaries( using Executor = detail::ProcessSecondariesExecutor; static ActionLauncher launch(*this, "process-secondaries"); launch(core_state, - Executor{core_params.ptr(), - core_state.ptr(), - core_state.counters()}); + Executor{core_params.ptr(), core_state.ptr()}); } //---------------------------------------------------------------------------// diff --git a/src/celeritas/track/InitializeTracksAction.cc b/src/celeritas/track/InitializeTracksAction.cc index 93c5516c33..a14930fc62 100644 --- a/src/celeritas/track/InitializeTracksAction.cc +++ b/src/celeritas/track/InitializeTracksAction.cc @@ -55,7 +55,7 @@ template void InitializeTracksAction::step_impl(CoreParams const& core_params, CoreState& core_state) const { - auto& counters = core_state.counters(); + auto counters = core_state.sync_get_counters(); // The number of new tracks to initialize is the smaller of the number of // empty slots in the track vector and the number of track initializers @@ -72,7 +72,6 @@ void InitializeTracksAction::step_impl(CoreParams const& core_params, // Partition indices by whether tracks are charged or neutral detail::partition_initializers(core_params, core_state.ref().init, - counters, num_new_tracks, core_state.stream_id()); } @@ -87,6 +86,7 @@ void InitializeTracksAction::step_impl(CoreParams const& core_params, // Store number of active tracks at the start of the loop counters.num_active = core_state.size() - counters.num_vacancies; + core_state.sync_put_counters(counters); } //---------------------------------------------------------------------------// @@ -100,10 +100,8 @@ void InitializeTracksAction::step_impl(CoreParams const& core_params, CoreStateHost& core_state, size_type num_new_tracks) const { - detail::InitTracksExecutor execute{core_params.ptr(), - core_state.ptr(), - num_new_tracks, - core_state.counters()}; + detail::InitTracksExecutor execute{ + core_params.ptr(), core_state.ptr(), num_new_tracks}; return launch_action( *this, num_new_tracks, core_params, core_state, execute); } diff --git a/src/celeritas/track/InitializeTracksAction.cu b/src/celeritas/track/InitializeTracksAction.cu index 980a9f0585..da65606798 100644 --- a/src/celeritas/track/InitializeTracksAction.cu +++ b/src/celeritas/track/InitializeTracksAction.cu @@ -22,10 +22,8 @@ void InitializeTracksAction::step_impl(CoreParams const& params, CoreStateDevice& state, size_type num_new_tracks) const { - detail::InitTracksExecutor execute_thread{params.ptr(), - state.ptr(), - num_new_tracks, - state.counters()}; + detail::InitTracksExecutor execute_thread{ + params.ptr(), state.ptr(), num_new_tracks}; static ActionLauncher const launch_kernel(*this); launch_kernel(num_new_tracks, state.stream_id(), execute_thread); } diff --git a/src/celeritas/track/TrackInitData.hh b/src/celeritas/track/TrackInitData.hh index a06f19703a..32a17628ed 100644 --- a/src/celeritas/track/TrackInitData.hh +++ b/src/celeritas/track/TrackInitData.hh @@ -18,6 +18,7 @@ #include "celeritas/phys/ParticleData.hh" #include "celeritas/phys/Primary.hh" +#include "CoreStateCounters.hh" #include "SimData.hh" namespace celeritas @@ -92,7 +93,9 @@ struct TrackInitializer * created per event. * - \c secondary_counts stores the number of secondaries created by each track * (with one remainder at the end for storing the accumulated number of - * secondaries) + * secondaries). + * - \c counters stores the number of tracks with a given status and is updated + * during each step of the simulation of the event. */ template struct TrackInitStateData @@ -117,6 +120,11 @@ struct TrackInitStateData // CoreStateCounters) Items initializers; + // Maintain the counters here to allow GPU-resident computation with + // synchronization between host and device only at the end of a step or + // when explicitly requested, such as in the tests + Items counters; + //// METHODS //// //! Whether the data are assigned @@ -124,7 +132,8 @@ struct TrackInitStateData { return (indices.size() == vacancies.size() || indices.empty()) && secondary_counts.size() == vacancies.size() + 1 - && !track_counters.empty() && !initializers.empty(); + && !track_counters.empty() && !initializers.empty() + && !counters.empty(); } //! Assign from another set of data @@ -139,6 +148,7 @@ struct TrackInitStateData vacancies = other.vacancies; initializers = other.initializers; + counters = other.counters; return *this; } @@ -168,6 +178,7 @@ void resize(TrackInitStateData* data, // Allocate device data resize(&data->secondary_counts, size + 1); resize(&data->track_counters, params.max_events); + resize(&data->counters, 1); if (params.track_order == TrackOrder::init_charge) { resize(&data->indices, size); @@ -183,6 +194,9 @@ void resize(TrackInitStateData* data, // Reserve space for initializers resize(&data->initializers, params.capacity); + // Initialize the counters for the step to zero + fill(CoreStateCounters{}, &data->counters); + CELER_ENSURE(*data); } diff --git a/src/celeritas/track/detail/InitTracksExecutor.hh b/src/celeritas/track/detail/InitTracksExecutor.hh index 9f4c6ab919..804bc5b305 100644 --- a/src/celeritas/track/detail/InitTracksExecutor.hh +++ b/src/celeritas/track/detail/InitTracksExecutor.hh @@ -8,7 +8,6 @@ #include "corecel/Assert.hh" #include "corecel/Macros.hh" -#include "corecel/cont/Span.hh" #include "corecel/sys/ThreadId.hh" #include "celeritas/Types.hh" #include "celeritas/geo/GeoMaterialView.hh" @@ -47,7 +46,6 @@ struct InitTracksExecutor ParamsPtr params; StatePtr state; size_type num_init{}; - CoreStateCounters counters; //// FUNCTIONS //// @@ -68,7 +66,7 @@ CELER_FUNCTION void InitTracksExecutor::operator()(ThreadId tid) const CELER_EXPECT(tid < num_init); auto const& data = state->init; - + auto counters = state->init.counters.data().get(); // Get the track initializer from the back of the vector. Since new // initializers are pushed to the back of the vector, these will be the // most recently added and therefore the ones that still might have a @@ -79,9 +77,9 @@ CELER_FUNCTION void InitTracksExecutor::operator()(ThreadId tid) const // Get the index into the track initializer or parent track slot ID // array from the sorted indices return data.indices[TrackSlotId(index_before(num_init, tid))] - + counters.num_initializers - num_init; + + counters->num_initializers - num_init; } - return index_before(counters.num_initializers, tid); + return index_before(counters->num_initializers, tid); }())]; // View to the new track to be initialized @@ -95,11 +93,11 @@ CELER_FUNCTION void InitTracksExecutor::operator()(ThreadId tid) const } // Get the vacancy from the back of the track state return data.vacancies[TrackSlotId( - index_before(counters.num_vacancies, tid))]; + index_before(counters->num_vacancies, tid))]; }()}; // Clear parent IDs if new primaries were added this step - if (counters.num_generated) + if (counters->num_generated) { init.geo.parent = {}; } diff --git a/src/celeritas/track/detail/ProcessPrimariesExecutor.hh b/src/celeritas/track/detail/ProcessPrimariesExecutor.hh index 4af48caf45..e8d856f733 100644 --- a/src/celeritas/track/detail/ProcessPrimariesExecutor.hh +++ b/src/celeritas/track/detail/ProcessPrimariesExecutor.hh @@ -38,7 +38,6 @@ struct ProcessPrimariesExecutor ParamsPtr params; StatePtr state; - CoreStateCounters counters; Span primaries; @@ -55,7 +54,8 @@ struct ProcessPrimariesExecutor CELER_FUNCTION void ProcessPrimariesExecutor::operator()(ThreadId tid) const { CELER_EXPECT(tid < primaries.size()); - CELER_EXPECT(primaries.size() <= counters.num_initializers + tid.get()); + auto counters = state->init.counters.data().get(); + CELER_EXPECT(primaries.size() <= counters->num_initializers + tid.get()); Primary const& primary = primaries[tid.unchecked_get()]; @@ -73,7 +73,7 @@ CELER_FUNCTION void ProcessPrimariesExecutor::operator()(ThreadId tid) const ti.particle.energy = primary.energy; // Store the initializer - size_type idx = counters.num_initializers - primaries.size() + tid.get(); + size_type idx = counters->num_initializers - primaries.size() + tid.get(); state->init.initializers[ItemId(idx)] = ti; } diff --git a/src/celeritas/track/detail/ProcessSecondariesExecutor.hh b/src/celeritas/track/detail/ProcessSecondariesExecutor.hh index d1b2f5bb64..f8c7d88025 100644 --- a/src/celeritas/track/detail/ProcessSecondariesExecutor.hh +++ b/src/celeritas/track/detail/ProcessSecondariesExecutor.hh @@ -38,7 +38,6 @@ struct ProcessSecondariesExecutor ParamsPtr params; StatePtr state; - CoreStateCounters counters; //// FUNCTIONS //// @@ -77,8 +76,9 @@ ProcessSecondariesExecutor::operator()(TrackSlotId tid) const // Offset in the vector of track initializers auto& data = state->init; - CELER_ASSERT(data.secondary_counts[tid] <= counters.num_secondaries); - size_type offset = counters.num_secondaries - data.secondary_counts[tid]; + auto counters = state->init.counters.data().get(); + CELER_ASSERT(data.secondary_counts[tid] <= counters->num_secondaries); + size_type offset = counters->num_secondaries - data.secondary_counts[tid]; // Save the parent ID since it will be overwritten if a secondary is // initialized in this slot @@ -129,10 +129,11 @@ ProcessSecondariesExecutor::operator()(TrackSlotId tid) const } else { - CELER_ASSERT(offset > 0 && offset <= counters.num_initializers); + CELER_ASSERT(offset > 0 + && offset <= counters->num_initializers); - if (offset <= min(counters.num_secondaries, - counters.num_vacancies) + if (offset <= min(counters->num_secondaries, + counters->num_vacancies) && (params->init.track_order != TrackOrder::init_charge || sim.status() == TrackStatus::alive)) { @@ -147,7 +148,7 @@ ProcessSecondariesExecutor::operator()(TrackSlotId tid) const // Store the track initializer data.initializers[ItemId{ - counters.num_initializers - offset}] + counters->num_initializers - offset}] = ti; --offset; diff --git a/src/celeritas/track/detail/TrackInitAlgorithms.cc b/src/celeritas/track/detail/TrackInitAlgorithms.cc index 65d72854b8..a1c7230e4d 100644 --- a/src/celeritas/track/detail/TrackInitAlgorithms.cc +++ b/src/celeritas/track/detail/TrackInitAlgorithms.cc @@ -24,14 +24,16 @@ namespace detail * * \return New size of the vacancy vector */ -size_type remove_if_alive( - StateCollection const& - vacancies, +void remove_if_alive( + TrackInitStateData const& init, StreamId) { - auto* start = vacancies.data().get(); - auto* stop = std::remove_if(start, start + vacancies.size(), LogicalNot{}); - return stop - start; + auto* start = init.vacancies.data().get(); + auto* counters = init.counters.data().get(); + auto* stop + = std::remove_if(start, start + init.vacancies.size(), LogicalNot{}); + counters->num_vacancies = stop - start; + return; } //---------------------------------------------------------------------------// @@ -80,15 +82,15 @@ size_type exclusive_scan_counts( void partition_initializers( CoreParams const& params, TrackInitStateData const& init, - CoreStateCounters const& counters, size_type count, StreamId) { // Partition the indices based on the track initializer charge - auto start = init.indices.data().get(); - auto end = start + count; - auto stencil = init.initializers.data().get() + counters.num_initializers - - count; + auto* start = init.indices.data().get(); + auto* end = start + count; + auto* counters = init.counters.data().get(); + auto* stencil = init.initializers.data().get() + counters->num_initializers + - count; std::stable_partition( start, end, IsNeutralStencil{params.ptr(), stencil}); } diff --git a/src/celeritas/track/detail/TrackInitAlgorithms.cu b/src/celeritas/track/detail/TrackInitAlgorithms.cu index 0c55ce11c4..5121e7432c 100644 --- a/src/celeritas/track/detail/TrackInitAlgorithms.cu +++ b/src/celeritas/track/detail/TrackInitAlgorithms.cu @@ -71,35 +71,43 @@ struct NotNull * Remove all elements in the vacancy vector that were flagged as active * tracks. */ -size_type remove_if_alive( - StateCollection const& - vacancies, +void remove_if_alive( + TrackInitStateData const& init, StreamId stream_id) { ScopedProfiling profile_this{"remove-if-alive"}; #if CELER_USE_THRUST - auto start = device_pointer_cast(vacancies.data()); + auto& stream = device().stream(stream_id); + auto start = device_pointer_cast(init.vacancies.data()); + auto counters = device_pointer_cast(init.counters.data()); + auto host_counters + = ItemCopier{stream_id}(counters.get()); auto end = thrust::remove_if(thrust_execute_on(stream_id), start, - start + vacancies.size(), + start + init.vacancies.size(), LogicalNot{}); CELER_DEVICE_API_CALL(PeekAtLastError()); // New size of the vacancy vector - return end - start; + host_counters.num_vacancies = end - start; + Copier copy{{counters.get(), 1}, + stream_id}; + copy(MemSpace::host, {&host_counters, 1}); + stream.sync(); + return; #else auto& stream = device().stream(stream_id); - DeviceVector num_not_active{1, stream_id}; // Calling with nullptr causes the function to return the amount of working // space needed instead of invoking the kernel. size_t temp_storage_bytes = 0; - auto data = device_pointer_cast(vacancies.data()); + auto data = device_pointer_cast(init.vacancies.data()); + auto counters = device_pointer_cast(init.counters.data()); // HIP defines hipCUB functions as [[nodiscard]], but we defer error checks auto cub_error_code = cub::DeviceSelect::If(nullptr, temp_storage_bytes, data, - num_not_active.data(), - vacancies.size(), + &(counters->num_vacancies), + init.vacancies.size(), NotNull{}, stream.get()); CELER_DISCARD(cub_error_code); @@ -109,17 +117,13 @@ size_type remove_if_alive( cub_error_code = cub::DeviceSelect::If(temp_storage.data(), temp_storage_bytes, data, - num_not_active.data(), - vacancies.size(), + &(counters->num_vacancies), + init.vacancies.size(), NotNull{}, stream.get()); CELER_DISCARD(cub_error_code); CELER_DEVICE_API_CALL(PeekAtLastError()); - - auto result = ItemCopier{stream_id}(num_not_active.data()); - - stream.sync(); - return result; + return; #endif } @@ -196,7 +200,6 @@ size_type exclusive_scan_counts( void partition_initializers( CoreParams const& params, TrackInitStateData const& init, - CoreStateCounters const& counters, size_type count, StreamId stream_id) { @@ -207,8 +210,10 @@ void partition_initializers( // Partition the indices based on the track initializer charge auto start = device_pointer_cast(init.indices.data()); auto end = start + count; + auto counters = device_pointer_cast(init.counters.data()); + auto cpucntrs = ItemCopier{stream_id}(counters.get()); auto stencil = static_cast(init.initializers.data()) - + counters.num_initializers - count; + + cpucntrs.num_initializers - count; thrust::stable_partition( thrust_execute_on(stream_id), start, @@ -226,8 +231,10 @@ void partition_initializers( // // The initializers array is large. Use stencil to point to the start where // this array is being used + auto counters = device_pointer_cast(init.counters.data()); + auto cpucntrs = ItemCopier{stream_id}(counters.get()); auto stencil = static_cast(init.initializers.data()) - + counters.num_initializers - count; + + cpucntrs.num_initializers - count; DeviceVector flags{count, stream_id}; # if CELER_CUB_HAS_TRANSFORM || CELER_HIPCUB_HAS_TRANSFORM // HIP defines hipCUB functions as [[nodiscard]], but we defer error checks @@ -254,16 +261,15 @@ void partition_initializers( // because the indices are always sequential from zero auto start = thrust::make_counting_iterator(0); auto data = device_pointer_cast(init.indices.data()); - // Allocate storage for the number of neutral tracks (unused by celeritas) - DeviceVector num_neutral{1, stream_id}; - auto cub_error_code = cub::DevicePartition::Flagged(nullptr, - temp_storage_bytes, - start, - flags.data(), - data, - num_neutral.data(), - count, - stream.get()); + auto cub_error_code + = cub::DevicePartition::Flagged(nullptr, + temp_storage_bytes, + start, + flags.data(), + data, + &(counters->num_neutral), + count, + stream.get()); CELER_DISCARD(cub_error_code); // Allocate temporary storage DeviceVector temp_storage(temp_storage_bytes, stream_id); @@ -273,7 +279,7 @@ void partition_initializers( start, flags.data(), data, - num_neutral.data(), + &(counters->num_neutral), count, stream.get()); CELER_DISCARD(cub_error_code); diff --git a/src/celeritas/track/detail/TrackInitAlgorithms.hh b/src/celeritas/track/detail/TrackInitAlgorithms.hh index bdea2b3253..81d0b53201 100644 --- a/src/celeritas/track/detail/TrackInitAlgorithms.hh +++ b/src/celeritas/track/detail/TrackInitAlgorithms.hh @@ -39,11 +39,10 @@ struct IsNeutralStencil //---------------------------------------------------------------------------// // Remove all elements in the vacancy vector that were flagged as alive -size_type remove_if_alive( - StateCollection const&, - StreamId); -size_type remove_if_alive( - StateCollection const&, +void remove_if_alive( + TrackInitStateData const&, StreamId); +void remove_if_alive( + TrackInitStateData const&, StreamId); //---------------------------------------------------------------------------// @@ -60,23 +59,20 @@ size_type exclusive_scan_counts( void partition_initializers( CoreParams const&, TrackInitStateData const&, - CoreStateCounters const&, size_type, StreamId); void partition_initializers( CoreParams const&, TrackInitStateData const&, - CoreStateCounters const&, size_type, StreamId); //---------------------------------------------------------------------------// -// INLINE DEFINITIONS +// DEVICE-DISABLED IMPLEMENTATION //---------------------------------------------------------------------------// #if !CELER_USE_DEVICE -inline size_type remove_if_alive( - StateCollection const&, - StreamId) +inline void remove_if_alive( + TrackInitStateData const&, StreamId) { CELER_NOT_CONFIGURED("CUDA or HIP"); } @@ -91,7 +87,6 @@ inline size_type exclusive_scan_counts( inline void partition_initializers( CoreParams const&, TrackInitStateData const&, - CoreStateCounters const&, size_type, StreamId) { diff --git a/src/corecel/Assert.hh b/src/corecel/Assert.hh index eb6ea74005..097c1360e4 100644 --- a/src/corecel/Assert.hh +++ b/src/corecel/Assert.hh @@ -167,11 +167,16 @@ ::celeritas::unreachable(); \ } \ } while (0) -#define CELER_NOASSERT_(COND) \ - do \ - { \ - if (false && (COND)) {} \ - } while (0) +#ifndef CELERITAS_GCOV +# define CELER_NOASSERT_(COND) \ + do \ + { \ + if (false && (COND)) {} \ + } while (0) +#else +// Delete the code completely to avoid false posistives for coverage +# define CELER_NOASSERT_(COND) +#endif //! \endcond #define CELER_DEBUG_FAIL(MSG, WHICH) \ @@ -389,7 +394,13 @@ struct JsonPimpl; //---------------------------------------------------------------------------// /*! - * Error thrown by Celeritas assertions. + * Error thrown by Celeritas assertions and debug failures. + * + * This class contains additional user-accessible context for the source of the + * error, the failing condition, etc., allowing detailed exception output or + * forwarding the exception details through other interfaces such as + * G4Exception. Since a \c DebugError indicates a \em programming bug, it + * inherits from \c std::logic_error. */ class DebugError : public std::logic_error { @@ -411,6 +422,11 @@ class DebugError : public std::logic_error //---------------------------------------------------------------------------// /*! * Error thrown by working code from unexpected runtime conditions. + * + * The runtime error contains additional user-accessible context for the source + * of the error, the failing condition, etc. Since it indicates an error with + * user input or other system conditions, it inherits from \c + * std::runtime_error. */ class RuntimeError : public std::runtime_error { @@ -440,10 +456,10 @@ class RuntimeError : public std::runtime_error /*! * Base class for writing arbitrary exception context to JSON. * - * This can be overridden in higher-level parts of the code for specific needs + * This can be subclassed in higher-level parts of the code for specific needs * (e.g., writing thread, event, and track contexts in Celeritas solver * kernels). Note that in order for derived classes to work with - * `std::throw_with_nested`, they *MUST NOT* be `final`. + * \c std::throw_with_nested, they MUST NOT be \c final. */ class RichContextException : public std::exception { diff --git a/src/corecel/CMakeLists.txt b/src/corecel/CMakeLists.txt index dd644c2a3a..124a4844e5 100644 --- a/src/corecel/CMakeLists.txt +++ b/src/corecel/CMakeLists.txt @@ -76,6 +76,7 @@ else() string(APPEND CELERITAS_BUILD_TYPE ",pic") endif() endif() +set(CELERITAS_CONSTANTS "CODATA ${CELERITAS_CODATA}") foreach(_var BUILD_TYPE HOSTNAME REAL_TYPE UNITS CONSTANTS OPENMP CORE_GEO CORE_RNG) string(TOLOWER "${_var}" _lower) celeritas_append_cmake_string("${_lower}" "${CELERITAS_${_var}}") diff --git a/src/corecel/Macros.hh b/src/corecel/Macros.hh index 2b02814979..b4db222418 100644 --- a/src/corecel/Macros.hh +++ b/src/corecel/Macros.hh @@ -237,10 +237,6 @@ * * "Try" to execute the statement, and "handle" *all* thrown errors by calling * the given function-like error handler with a \c std::exception_ptr object. - * - * \note A file that uses this macro must include the \c \ header - * (but since the \c HANDLE_EXCEPTION needs to take an exception pointer, it's - * got to be included anyway). */ #define CELER_TRY_HANDLE(STATEMENT, HANDLE_EXCEPTION) \ do \ @@ -261,8 +257,23 @@ * Try the given statement, and if it fails, chain it into the given exception. * * The given \c CONTEXT_EXCEPTION must be an expression that yields an rvalue - * to a \c std::exception subclass that isn't \c final . The resulting chained - * exception will be passed into \c HANDLE_EXCEPTION for processing. + * to a \c std::exception subclass that is not marked \c final . The resulting + * chained exception will be passed into \c HANDLE_EXCEPTION for processing. + * + * This example shows how to safely propagate exceptions out of an OpenMP + * parallel block using celeritas::MultiExceptionHandler : + * \code + MultiExceptionHandler capture_exception; + #pragma omp parallel for + for (size_type i = 0; i < num_threads; ++i) + { + CELER_TRY_HANDLE_CONTEXT( + execute_thread(ThreadId{i}), + capture_exception, + KernelContextException(ThreadId{i})); + } + log_and_rethrow(std::move(capture_exception)); + * \endcode */ #define CELER_TRY_HANDLE_CONTEXT( \ STATEMENT, HANDLE_EXCEPTION, CONTEXT_EXCEPTION) \ diff --git a/src/corecel/Version.cc.in b/src/corecel/Version.cc.in index 8c17517dd5..4029b83218 100644 --- a/src/corecel/Version.cc.in +++ b/src/corecel/Version.cc.in @@ -14,11 +14,11 @@ namespace celeritas //! Celeritas version string with git metadata char const version_string[] = "@Celeritas_VERSION_STRING@"; //! Celeritas major version -unsigned int const version_major = @PROJECT_VERSION_MAJOR@; +unsigned int const version_major{@Celeritas_VERSION_MAJOR@}; //! Celeritas minor version -unsigned int const version_minor = @PROJECT_VERSION_MINOR@; +unsigned int const version_minor{@Celeritas_VERSION_MINOR@}; //! Celeritas patch version -unsigned int const version_patch = @PROJECT_VERSION_PATCH@; +unsigned int const version_patch{@Celeritas_VERSION_PATCH@}; //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/cont/InitializedValue.hh b/src/corecel/cont/InitializedValue.hh index e1639a3a20..a14f8973b7 100644 --- a/src/corecel/cont/InitializedValue.hh +++ b/src/corecel/cont/InitializedValue.hh @@ -9,6 +9,8 @@ #include #include +#include "corecel/Macros.hh" + namespace celeritas { namespace detail @@ -17,27 +19,28 @@ namespace detail template struct DefaultFinalize { - void operator()(T&) const noexcept {} + CELER_FORCEINLINE void operator()(T&) const noexcept {} }; //---------------------------------------------------------------------------// } // namespace detail //---------------------------------------------------------------------------// /*! - * Clear the value of the object on initialization and moving. + * Clear and finalize default values when moving and destroying. * * This helper class is used to simplify the "rule of 5" for classes that have * to treat one member data specially but can use default assign/construct for * the other elements. The default behavior is just to default-initialize when * assigning and clearing the RHS when moving; this is useful for handling - * managed memory. The *finalizer* is useful when the type has a - * destructor-type method that has to be called before clearing it. + * managed memory. The \em finalizer is called every time a \em non-default + * value is lost by assignment or destruction. */ template> class InitializedValue { - private: - static constexpr bool ne_finalize_ + static_assert(std::is_default_constructible_v); + static_assert(std::is_default_constructible_v); + static constexpr bool noexcept_finalize_ = noexcept(std::declval()(std::declval())); public: @@ -48,76 +51,33 @@ class InitializedValue InitializedValue() = default; //! Implicit construct from lvalue InitializedValue(T const& value) : value_(value) {} - //! Implicit construct from lvalue and finalizer - InitializedValue(T const& value, Finalizer fin) - : value_(value), fin_(std::move(fin)) - { - } //! Implicit construct from rvalue InitializedValue(T&& value) : value_(std::move(value)) {} - //! Implicit construct from value type and finalizer - InitializedValue(T&& value, Finalizer fin) - : value_(std::move(value)), fin_(std::move(fin)) - { - } //! Default copy constructor InitializedValue(InitializedValue const&) noexcept( std::is_nothrow_copy_constructible_v) = default; - //! Clear other value on move construct + // Move constructor InitializedValue(InitializedValue&& other) noexcept( - std::is_nothrow_move_constructible_v) - : value_(std::exchange(other.value_, {})) - { - } + std::is_nothrow_move_constructible_v); - ~InitializedValue() = default; + // Destructor + ~InitializedValue() noexcept(noexcept_finalize_); //!@} + //!@{ //! \name Assignment - - //! Finalize our value when assigning + // Copy assignment InitializedValue& operator=(InitializedValue const& other) noexcept( - ne_finalize_ && std::is_nothrow_copy_assignable_v) - { - fin_(value_); - value_ = other.value_; - fin_ = other.fin_; - return *this; - } - - //! Clear other value on move assign + noexcept_finalize_ && std::is_nothrow_copy_assignable_v); + // Move assignment InitializedValue& operator=(InitializedValue&& other) noexcept( - ne_finalize_ && std::is_nothrow_move_assignable_v) - { - fin_(value_); - value_ = std::exchange(other.value_, {}); - fin_ = std::exchange(other.fin_, {}); - return *this; - } - - //! Implicit assign from type - InitializedValue& - operator=(T const& value) noexcept(ne_finalize_ - && std::is_nothrow_copy_assignable_v) - { - fin_(value_); - value_ = value; - return *this; - } - - //! Swap with another value - void swap(InitializedValue& other) noexcept - { - using std::swap; - swap(other.value_, value_); - swap(other.fin_, fin_); - } - + noexcept_finalize_ && std::is_nothrow_move_assignable_v); //!@} + //!@{ //! \name Conversion @@ -128,20 +88,64 @@ class InitializedValue //! Explicit reference to stored value T const& value() const& { return value_; } T& value() & { return value_; } - T&& value() && { return value_; } //!@} - //!@{ - //! Access finalizer - Finalizer const& finalizer() const { return fin_; } - void finalizer(Finalizer fin) { fin_ = std::move(fin); } - //!@} - private: T value_{}; - Finalizer fin_{}; // TODO: if C++20, [[no_unique_address]] }; +//---------------------------------------------------------------------------// +// INLINE DEFINITIONS +//---------------------------------------------------------------------------// +//! Exchange with other value on move construct +template +InitializedValue::InitializedValue( + InitializedValue&& other) noexcept(std::is_nothrow_move_constructible_v) + : value_(std::exchange(other.value_, {})) +{ +} + +//---------------------------------------------------------------------------// +//! Call finalizer on destruct +template +InitializedValue::~InitializedValue() noexcept(noexcept_finalize_) +{ + if (value_ != T{}) + { + Finalizer{}(value_); + } +} + +//---------------------------------------------------------------------------// +//! Finalize our value when assigning +template +InitializedValue& +InitializedValue::operator=(InitializedValue const& other) noexcept( + noexcept_finalize_ && std::is_nothrow_copy_assignable_v) +{ + if (value_ != T{}) + { + Finalizer{}(value_); + } + value_ = other.value_; + return *this; +} + +//---------------------------------------------------------------------------// +//! Clear other value on move assign +template +InitializedValue& +InitializedValue::operator=(InitializedValue&& other) noexcept( + noexcept_finalize_ && std::is_nothrow_move_assignable_v) +{ + if (value_ != T{}) + { + Finalizer{}(value_); + } + value_ = std::exchange(other.value_, T{}); + return *this; +} + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/data/Collection.hh b/src/corecel/data/Collection.hh index f16e733828..7fbde24ba5 100644 --- a/src/corecel/data/Collection.hh +++ b/src/corecel/data/Collection.hh @@ -20,11 +20,6 @@ namespace celeritas { -//---------------------------------------------------------------------------// -// Forward declare DedupeCollectionBuilder for use as a friend class -template -class DedupeCollectionBuilder; - //---------------------------------------------------------------------------// /*! * \page collections Collection: a data portability class @@ -69,8 +64,7 @@ class DedupeCollectionBuilder; * - Define an operator bool that is true if and only if the class data is * assigned and consistent * - Define a \em templated assignment operator on "other" Ownership and - MemSpace - * which assigns every member to the right-hand-side's member + * MemSpace which assigns every member to the right-hand-side's member * * Additionally, a \c StateData collection group must define * - A member function \c size() returning the number of entries (i.e. number @@ -109,6 +103,10 @@ class DedupeCollectionBuilder; * item. These often need to reference a variable-sized range of data and do so * by storing an \c ItemRange or \c ItemMap . These two types are offsets into * "backend" data stored by a collection group. + * + * Finally, note that the templated type aliases \c HostVal, \c HostCRef, + * \c DeviceRef, etc. are useful for functions that are specialized on + MemSpace. */ //! Opaque ID representing a single element of a container. @@ -234,6 +232,11 @@ class ItemMap Range range_; }; +//---------------------------------------------------------------------------// +// Forward declare DedupeCollectionBuilder for use as a friend class +template +class DedupeCollectionBuilder; + // Forward-declare collection builder, needed for GCC7 template class CollectionBuilder; diff --git a/src/corecel/data/CollectionMirror.hh b/src/corecel/data/CollectionMirror.hh index 68b7e28036..48ef91f9e7 100644 --- a/src/corecel/data/CollectionMirror.hh +++ b/src/corecel/data/CollectionMirror.hh @@ -95,10 +95,21 @@ template class P> CollectionMirror

::CollectionMirror(HostValue&& host) : host_(std::move(host)) { - CELER_EXPECT(host_); + if (CELER_UNLIKELY(!host_)) + { + CELER_DEBUG_FAIL("incomplete host data or bad copy", precondition); + } + host_ref_ = host_; + if (celeritas::device()) { + if constexpr (!CELER_USE_DEVICE) + { + // Mark unreachable for optimization and coverage + CELER_ASSERT_UNREACHABLE(); + } + // Copy data to device and save reference device_ = host_; device_ref_ = device_; diff --git a/src/corecel/data/Filler.cu b/src/corecel/data/Filler.cu index 37e419278b..4b4eeecb27 100644 --- a/src/corecel/data/Filler.cu +++ b/src/corecel/data/Filler.cu @@ -4,6 +4,8 @@ //---------------------------------------------------------------------------// //! \file corecel/data/Filler.cu //---------------------------------------------------------------------------// +#include "celeritas/track/CoreStateCounters.hh" + #include "Filler.device.t.hh" namespace celeritas @@ -13,5 +15,6 @@ template class Filler; template class Filler; template class Filler; template class Filler; +template class Filler; //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/BuildOutput.cc b/src/corecel/io/BuildOutput.cc index ba96f93496..da311cc516 100644 --- a/src/corecel/io/BuildOutput.cc +++ b/src/corecel/io/BuildOutput.cc @@ -60,6 +60,7 @@ void BuildOutput::output(JsonPimpl* j) const CO_ADD_CFG(hostname); CO_ADD_CFG(real_type); CO_ADD_CFG(units); + CO_ADD_CFG(constants); CO_ADD_CFG(openmp); CO_ADD_CFG(core_geo); CO_ADD_CFG(core_rng); diff --git a/src/corecel/io/ColorUtils.cc b/src/corecel/io/ColorUtils.cc index 534bef7559..a96ed246c8 100644 --- a/src/corecel/io/ColorUtils.cc +++ b/src/corecel/io/ColorUtils.cc @@ -13,6 +13,7 @@ # include #endif +#include "corecel/Assert.hh" #include "corecel/sys/Environment.hh" namespace celeritas @@ -20,7 +21,7 @@ namespace celeritas namespace { //---------------------------------------------------------------------------// -// Get a default color based on the terminal settings +//! Get a default color based on the terminal settings bool default_term_color() { #ifndef _WIN32 @@ -28,9 +29,9 @@ bool default_term_color() return isatty(fileno(stderr)); #endif // Fall back to checking environment variable - if (char const* term_str = std::getenv("TERM")) + if (auto term_str = celeritas::getenv("TERM"); !term_str.empty()) { - if (std::string{term_str}.find("xterm") != std::string::npos) + if (term_str.find("xterm") != std::string::npos) { // 'xterm' is in the TERM type, so assume it uses colors return true; @@ -41,9 +42,9 @@ bool default_term_color() //---------------------------------------------------------------------------// /*! - * Get a default color based on the terminal/env settings. + * Get the preferred environment variable to use for color override. */ -char const* default_color_env_str() +char const* color_env_var() { auto hasenv = [](char const* key) { return std::getenv(key) != nullptr; }; static char const celer_env[] = "CELER_COLOR"; @@ -58,17 +59,26 @@ char const* default_color_env_str() //---------------------------------------------------------------------------// /*! - * Whether colors are enabled (currently read-only). + * Whether colors are enabled by the environment. * - * This checks the \c CELER_COLOR environment variable, or the \c - * GTEST_COLOR variable (if it is defined and CELER_COLOR is not), and defaults - * based on the terminal settings of \c stderr. + * The \c NO_COLOR environment variable, if set to a non-empty value, disables + * color output. If either of the \c CELER_COLOR or \c GTEST_COLOR variables is + * set, that value will be used. Failing that, the default is true if \c stderr + * is a tty. The result of this value is used by \c ansi_color . */ bool use_color() { - static bool const result - = celeritas::getenv_flag(default_color_env_str(), default_term_color()) - .value; + static bool const result = [] { + if (auto term_str = celeritas::getenv("NO_COLOR"); !term_str.empty()) + { + // See https://no-color.org + return false; + } + + // Check one environment variable and fall back to terminal color + auto flag = getenv_flag_lazy(color_env_var(), default_term_color); + return flag.value; + }(); return result; } @@ -83,35 +93,43 @@ bool use_color() * - [x] gray * - [R]ed bold * - [W]hite bold - * - [ ] default (reset color) + * - \c null reset color + * - [ ] reset color */ -char const* color_code(char abbrev) +char const* ansi_color(char abbrev) { if (!use_color()) return ""; switch (abbrev) { - case 'g': - return "\033[32m"; - case 'b': - return "\033[34m"; + case '\0': + [[fallthrough]]; + case ' ': + return "\033[0m"; case 'r': return "\033[31m"; - case 'x': - return "\033[37;2m"; + case 'g': + return "\033[32m"; case 'y': return "\033[33m"; + case 'b': + return "\033[34m"; case 'R': - return "\033[31;1m"; + return "\033[1;31m"; + case 'G': + return "\033[1;32m"; + case 'B': + return "\033[1;34m"; case 'W': - return "\033[37;1m"; + return "\033[1;37m"; + case 'x': + return "\033[2;37m"; default: return "\033[0m"; } - // Unknown color code: ignore - return ""; + CELER_ASSERT_UNREACHABLE(); } //---------------------------------------------------------------------------// diff --git a/src/corecel/io/ColorUtils.hh b/src/corecel/io/ColorUtils.hh index e79e706214..1435bb265c 100644 --- a/src/corecel/io/ColorUtils.hh +++ b/src/corecel/io/ColorUtils.hh @@ -7,6 +7,8 @@ //---------------------------------------------------------------------------// #pragma once +#include "corecel/Macros.hh" + namespace celeritas { //---------------------------------------------------------------------------// @@ -14,8 +16,15 @@ namespace celeritas bool use_color(); //---------------------------------------------------------------------------// -// Get an ANSI color code: [y]ellow / [r]ed / [ ]default / ... -char const* color_code(char abbrev); +// Get an ANSI color code: [y]ellow / [r]ed / [\0]reset / ... +char const* ansi_color(char abbrev = '\0'); + +//---------------------------------------------------------------------------// +// DEPRECATED (remove in v1.0): get an ANSI color code +CELER_FORCEINLINE char const* color_code(char abbrev) +{ + return ansi_color(abbrev); +} //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/JsonPimpl.hh b/src/corecel/io/JsonPimpl.hh index 0fea40a717..c0c6c6c6ed 100644 --- a/src/corecel/io/JsonPimpl.hh +++ b/src/corecel/io/JsonPimpl.hh @@ -8,8 +8,6 @@ #include -#include "corecel/Config.hh" - #include "corecel/Assert.hh" namespace celeritas @@ -46,8 +44,9 @@ void to_json_pimpl(JsonPimpl* jp, T const& self) to_json(jp->obj, self); } +//! Get a JSON object from an OutputInterface template -nlohmann::json json_pimpl_output(T const& self) +nlohmann::json output_to_json(T const& self) { JsonPimpl jp; self.output(&jp); diff --git a/src/corecel/io/LogHandlers.cc b/src/corecel/io/LogHandlers.cc index 202503a9aa..a6e9296143 100644 --- a/src/corecel/io/LogHandlers.cc +++ b/src/corecel/io/LogHandlers.cc @@ -10,6 +10,8 @@ #include #include +#include "corecel/sys/MpiCommunicator.hh" + #include "ColorUtils.hh" namespace celeritas @@ -33,7 +35,7 @@ void StreamLogHandler::operator()(LogProvenance prov, os_ << color_code(' ') << ": "; } - os_ << to_color_code(lev) << to_cstring(lev) << ": " << color_code(' '); + os_ << to_ansi_color(lev) << to_cstring(lev) << ": " << color_code(' '); os_ << std::move(msg) << std::endl; } @@ -54,5 +56,27 @@ void MutexedStreamLogHandler::operator()(LogProvenance prov, os_ << std::move(temp_os).str() << std::flush; } +//---------------------------------------------------------------------------// +/*! + * Construct with long-lived reference to a stream. + */ +LocalMpiHandler::LocalMpiHandler(std::ostream& os, MpiCommunicator const& comm) + : os_{os}, rank_(comm.rank()) +{ +} + +// Write with processor ID +void LocalMpiHandler::operator()(LogProvenance prov, + LogLevel lev, + std::string msg) const +{ + // Buffer to reduce I/O contention in MPI runner + std::ostringstream os; + os << color_code('W') << "rank " << rank_ << ": "; + StreamLogHandler{os}(prov, lev, msg); + + os_ << std::move(os).str() << std::flush; +} + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/LogHandlers.hh b/src/corecel/io/LogHandlers.hh index 13276399bc..defa8197df 100644 --- a/src/corecel/io/LogHandlers.hh +++ b/src/corecel/io/LogHandlers.hh @@ -13,6 +13,8 @@ namespace celeritas { +class MpiCommunicator; + //---------------------------------------------------------------------------// /*! * Simple log handler: write with colors to a long-lived ostream reference. @@ -47,5 +49,21 @@ class MutexedStreamLogHandler std::ostream& os_; }; +//---------------------------------------------------------------------------// +//! Log the local node number as well as the message +class LocalMpiHandler +{ + public: + // Construct with long-lived reference to a stream + LocalMpiHandler(std::ostream& os, MpiCommunicator const& comm); + + // Write with processor ID + void operator()(LogProvenance prov, LogLevel lev, std::string msg) const; + + private: + std::ostream& os_; + int rank_; +}; + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/Logger.cc b/src/corecel/io/Logger.cc index 77ad803e4e..e78469b38e 100644 --- a/src/corecel/io/Logger.cc +++ b/src/corecel/io/Logger.cc @@ -6,19 +6,10 @@ //---------------------------------------------------------------------------// #include "Logger.hh" -#include -#include #include -#include -#include #include "corecel/Assert.hh" -#include "corecel/cont/Range.hh" -#include "corecel/sys/Environment.hh" -#include "corecel/sys/MpiCommunicator.hh" -#include "corecel/sys/ScopedMpiInit.hh" -#include "ColorUtils.hh" #include "LogHandlers.hh" #include "LoggerTypes.hh" @@ -27,165 +18,73 @@ namespace celeritas namespace { //---------------------------------------------------------------------------// -// HELPER CLASSES -//---------------------------------------------------------------------------// -//! Log the local node number as well as the message -class LocalHandler -{ - public: - explicit LocalHandler(MpiCommunicator const& comm) : rank_(comm.rank()) {} - - void operator()(LogProvenance prov, LogLevel lev, std::string msg) - { - // Buffer to reduce I/O contention in MPI runner - std::ostringstream os; - os << color_code('W') << "rank " << rank_ << ": "; - StreamLogHandler{os}(prov, lev, msg); - - std::clog << std::move(os).str(); - } - - private: - int rank_; -}; - -//---------------------------------------------------------------------------// -} // namespace - -//---------------------------------------------------------------------------// -/*! - * Create a logger from a handle and level environment variable. - */ -Logger -Logger::from_handle_env(LogHandler&& handle, std::string const& level_env) +auto safe_getenv_loglevel(char const* env_var, LogLevel default_level) + -> LogLevel { - Logger result{std::move(handle)}; - try { - result.level(log_level_from_env(level_env)); + return getenv_loglevel(env_var, default_level); } catch (RuntimeError const& e) { - result(CELER_CODE_PROVENANCE, LogLevel::warning) << e.details().what; + std::clog << "Error during logger setup: " << e.what() << std::endl; + return default_level; } - - return result; } - //---------------------------------------------------------------------------// -/*! - * Construct with handler. - * - * A null handler will silence the logger. - */ -Logger::Logger(LogHandler&& handle) : handle_{std::move(handle)} {} +} // namespace -//---------------------------------------------------------------------------// -// FREE FUNCTIONS //---------------------------------------------------------------------------// /*! - * Get the log level from an environment variable. - * - * Default to \c Logger::default_level, which is 'info'. + * Construct a logger with handle. */ -LogLevel log_level_from_env(std::string const& level_env) +Logger::Logger(LogHandler&& handle) + : Logger(std::move(handle), default_level()) { - return log_level_from_env(level_env, Logger::default_level()); } //---------------------------------------------------------------------------// /*! - * Get the log level from an environment variable. + * Construct a logger with handle and level. */ -LogLevel log_level_from_env(std::string const& level_env, LogLevel default_lev) +Logger::Logger(LogHandler&& handle, LogLevel min_lev) + : handle_(std::move(handle)), min_level_(min_lev) { - // Search for the provided environment variable to set the default - // logging level using the `to_cstring` function in LoggerTypes. - std::string const& env_value = celeritas::getenv(level_env); - if (env_value.empty()) - { - return default_lev; - } - - auto levels = range(LogLevel::size_); - auto iter = std::find_if( - levels.begin(), levels.end(), [&env_value](LogLevel lev) { - return env_value == to_cstring(lev); - }); - CELER_VALIDATE(iter != levels.end(), - << "invalid log level '" << env_value - << "' in environment variable '" << level_env << "'"); - return *iter; + CELER_EXPECT(min_level_ != LogLevel::size_); } //---------------------------------------------------------------------------// -/*! - * Create a default logger using the world communicator. - * - * The result prints to stderr (buffered through \c std::clog ) only on one - * processor in the world communicator group. - * Since it's for messages that should be printed once across all processes, - * and usually during setup when no local threads are printing, it is not - * mutexed. - * This function can be useful when resetting a test harness. - */ -Logger make_default_world_logger() -{ - LogHandler handler; - if (celeritas::comm_world().rank() == 0) - { - handler = StreamLogHandler{std::clog}; - } - return Logger::from_handle_env(std::move(handler), "CELER_LOG"); -} - +// FREE FUNCTIONS //---------------------------------------------------------------------------// /*! - * Create a default logger using the local communicator. + * App-level logger: print only on "main" process. * - * If MPI is enabled, this will prepend the local process index to the message. - * Because multiple threads and processes can print log messages at the same - * time, this log output uses a mutex to synchronize output, and prints to - * buffered stderr through \c std::clog . - */ -Logger make_default_self_logger() -{ - LogHandler handler = LogHandler{MutexedStreamLogHandler{std::clog}}; - if (ScopedMpiInit::status() != ScopedMpiInit::Status::disabled) - { - handler = LocalHandler{celeritas::comm_world()}; - } - - return Logger::from_handle_env(std::move(handler), "CELER_LOG_LOCAL"); -} - -//---------------------------------------------------------------------------// -/*! - * Parallel-enabled logger: print only on "main" process. + * Setting the "CELER_LOG" environment variable to "debug", "info", + * "error", etc. will change the default log level. * - * Setting the "CELER_LOG" environment variable to "debug", "info", "error", - * etc. will change the default log level. + * \sa CELER_LOG . */ Logger& world_logger() { - // Use the null communicator if MPI isn't enabled, otherwise comm_world - static Logger logger = make_default_world_logger(); + static Logger logger{StreamLogHandler{std::clog}, + safe_getenv_loglevel("CELER_LOG", LogLevel::status)}; return logger; } //---------------------------------------------------------------------------// /*! - * Serial logger: print on *every* process that calls it. + * Serial logger: print on \em every process that calls it. * * Setting the "CELER_LOG_LOCAL" environment variable to "debug", "info", * "error", etc. will change the default log level. + * + * \sa CELER_LOG_LOCAL . */ Logger& self_logger() { - // Use the null communicator if MPI isn't enabled, otherwise comm_local. - // If only - static Logger logger = make_default_self_logger(); + static Logger logger{ + StreamLogHandler{std::clog}, + safe_getenv_loglevel("CELER_LOG_LOCAL", LogLevel::warning)}; return logger; } diff --git a/src/corecel/io/Logger.hh b/src/corecel/io/Logger.hh index 621e1a9dcf..8bfa8708cf 100644 --- a/src/corecel/io/Logger.hh +++ b/src/corecel/io/Logger.hh @@ -6,10 +6,9 @@ //---------------------------------------------------------------------------// #pragma once -#include #include -#include "corecel/Config.hh" +#include "corecel/Macros.hh" #include "LoggerTypes.hh" @@ -52,8 +51,8 @@ * \def CELER_LOG_LOCAL * * Like \c CELER_LOG but for code paths that may only happen on a single - * process or thread. Use sparingly because this can be very verbose. This is - * typically used only for error messages coming from an a event or + * process or thread. Use sparingly because this can be very verbose. This + * should be used for error messages from an event or * track at runtime. */ #define CELER_LOG_LOCAL(LEVEL) \ @@ -89,7 +88,7 @@ class MpiCommunicator; * each process: rank 0 will have a handler that outputs to screen, and the * other ranks will have a "null" handler that suppresses all log output. * - * \todo For v1.0, replace the back-end with \c spdlog to reduce maintenance + * \todo Replace the back-end with \c spdlog to reduce maintenance * burden and improve flexibility. */ class Logger @@ -104,13 +103,16 @@ class Logger //! Get the default log level static constexpr LogLevel default_level() { return LogLevel::status; } - // Create a logger from a handle and level environment variable - static Logger from_handle_env(LogHandler&& handle, std::string const& key); + // Construct a null logger + Logger() = default; - // Construct from an output handle + // Construct a logger with handle and default level explicit Logger(LogHandler&& handle); - // Create a logger that flushes its contents when it destructs + // Construct a logger with handle and level + Logger(LogHandler&& handle, LogLevel min_lev); + + // Create a log message that flushes its contents when it destructs inline Message operator()(LogProvenance&& prov, LogLevel lev) const; //! Set the minimum logging verbosity @@ -122,8 +124,8 @@ class Logger //! Access the log handle LogHandler const& handle() const { return handle_; } - //! Access the log handle (mutable, try to avoid using?) - LogHandler& handle() { return handle_; } + //! Set the log handle (null disables logger) + void handle(LogHandler&& handle) { handle_ = std::move(handle); } private: LogHandler handle_; @@ -153,13 +155,6 @@ auto Logger::operator()(LogProvenance&& prov, LogLevel lev) const -> Message //---------------------------------------------------------------------------// // FREE FUNCTIONS //---------------------------------------------------------------------------// -// Get the log level from an environment variable -LogLevel log_level_from_env(std::string const&, LogLevel default_lev); -LogLevel log_level_from_env(std::string const&); - -// Create loggers with reasonable default behaviors. -Logger make_default_world_logger(); -Logger make_default_self_logger(); // Parallel logger (print only on "main" process) Logger& world_logger(); diff --git a/src/corecel/io/LoggerTypes.cc b/src/corecel/io/LoggerTypes.cc index 86a10af783..c6a41da0ae 100644 --- a/src/corecel/io/LoggerTypes.cc +++ b/src/corecel/io/LoggerTypes.cc @@ -6,6 +6,12 @@ //---------------------------------------------------------------------------// #include "LoggerTypes.hh" +#include +#include + +#include "corecel/cont/Range.hh" +#include "corecel/sys/Environment.hh" + #include "ColorUtils.hh" #include "EnumStringMapper.hh" @@ -33,7 +39,7 @@ char const* to_cstring(LogLevel lev) /*! * Get an ANSI color code appropriate to each log level. */ -char const* to_color_code(LogLevel lev) +char const* to_ansi_color(LogLevel lev) { // clang-format off char c = ' '; @@ -50,7 +56,34 @@ char const* to_color_code(LogLevel lev) }; // clang-format on - return color_code(c); + return ansi_color(c); +} + +//---------------------------------------------------------------------------// +/*! + * Get the log level from an environment variable. + * + * \throws RuntimeError if string is invalid + */ +LogLevel getenv_loglevel(std::string const& level_env, LogLevel default_lev) +{ + // Search for the provided environment variable to set the default + // logging level using the `to_cstring` function in LoggerTypes. + std::string const& env_value = celeritas::getenv(level_env); + if (env_value.empty()) + { + return default_lev; + } + + auto levels = range(LogLevel::size_); + auto iter = std::find_if( + levels.begin(), levels.end(), [&env_value](LogLevel lev) { + return env_value == to_cstring(lev); + }); + CELER_VALIDATE(iter != levels.end(), + << "invalid log level '" << env_value + << "' in environment variable '" << level_env << "'"); + return *iter; } //---------------------------------------------------------------------------// diff --git a/src/corecel/io/LoggerTypes.hh b/src/corecel/io/LoggerTypes.hh index e665241f6f..201351c45f 100644 --- a/src/corecel/io/LoggerTypes.hh +++ b/src/corecel/io/LoggerTypes.hh @@ -15,6 +15,8 @@ namespace celeritas { //---------------------------------------------------------------------------// +// TYPES +//---------------------------------------------------------------------------// /*! * Enumeration for how important a log message is. */ @@ -30,16 +32,7 @@ enum class LogLevel size_ //!< Sentinel value for looping over log levels }; -//---------------------------------------------------------------------------// -// Get the plain text equivalent of the log level above -char const* to_cstring(LogLevel); - -//---------------------------------------------------------------------------// -// Get an ANSI color code appropriate to each log level -char const* to_color_code(LogLevel); - -//---------------------------------------------------------------------------// -//! Stand-in for a more complex class for the "provenance" of data +//! Origin of a log message struct LogProvenance { std::string_view file; //!< Originating file @@ -49,5 +42,20 @@ struct LogProvenance //! Type for handling a log message using LogHandler = std::function; +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// + +// Get the plain text equivalent of the log level above +char const* to_cstring(LogLevel); + +//---------------------------------------------------------------------------// +// Get an ANSI color code appropriate to each log level +char const* to_ansi_color(LogLevel); + +//---------------------------------------------------------------------------// +// Get a log level from an environment variable +LogLevel getenv_loglevel(std::string const&, LogLevel default_lev); + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/OutputInterface.cc b/src/corecel/io/OutputInterface.cc index 46f4864b1f..fffaef82a1 100644 --- a/src/corecel/io/OutputInterface.cc +++ b/src/corecel/io/OutputInterface.cc @@ -9,8 +9,6 @@ #include #include -#include "corecel/Config.hh" - #include "EnumStringMapper.hh" #include "JsonPimpl.hh" diff --git a/src/corecel/io/OutputRegistry.cc b/src/corecel/io/OutputRegistry.cc index 5ad7b9da9e..d64e94652e 100644 --- a/src/corecel/io/OutputRegistry.cc +++ b/src/corecel/io/OutputRegistry.cc @@ -16,11 +16,21 @@ #include "corecel/Assert.hh" #include "corecel/cont/Range.hh" +#include "corecel/io/BuildOutput.hh" #include "corecel/io/EnumStringMapper.hh" +#include "corecel/sys/Device.hh" +#include "corecel/sys/DeviceIO.json.hh" +#include "corecel/sys/Environment.hh" +#include "corecel/sys/EnvironmentIO.json.hh" +#include "corecel/sys/KernelRegistry.hh" +#include "corecel/sys/KernelRegistryIO.json.hh" +#include "corecel/sys/MemRegistry.hh" +#include "corecel/sys/MemRegistryIO.json.hh" #include "JsonPimpl.hh" #include "Logger.hh" // IWYU pragma: keep #include "OutputInterface.hh" +#include "OutputInterfaceAdapter.hh" namespace celeritas { @@ -106,5 +116,26 @@ bool OutputRegistry::empty() const [](auto const& m) { return m.empty(); }); } +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// +/*! + * Add interfaces for writing system diagnostics. + */ +void insert_system_diagnostics(OutputRegistry& output_reg) +{ + output_reg.insert(OutputInterfaceAdapter::from_const_ref( + OutputInterface::Category::system, "device", celeritas::device())); + output_reg.insert(OutputInterfaceAdapter::from_const_ref( + OutputInterface::Category::system, + "kernels", + celeritas::kernel_registry())); + output_reg.insert(OutputInterfaceAdapter::from_const_ref( + OutputInterface::Category::system, "memory", celeritas::mem_registry())); + output_reg.insert(OutputInterfaceAdapter::from_const_ref( + OutputInterface::Category::system, "environ", celeritas::environment())); + output_reg.insert(std::make_shared()); +} + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/OutputRegistry.hh b/src/corecel/io/OutputRegistry.hh index ec4fe3efce..f1db913899 100644 --- a/src/corecel/io/OutputRegistry.hh +++ b/src/corecel/io/OutputRegistry.hh @@ -56,5 +56,12 @@ class OutputRegistry EnumArray> interfaces_; }; +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// + +// Add an interfaces for writing system diagnostics +void insert_system_diagnostics(OutputRegistry&); + //---------------------------------------------------------------------------// } // namespace celeritas diff --git a/src/corecel/io/detail/LoggerMessage.hh b/src/corecel/io/detail/LoggerMessage.hh index f294a1f9a8..586e3c923c 100644 --- a/src/corecel/io/detail/LoggerMessage.hh +++ b/src/corecel/io/detail/LoggerMessage.hh @@ -11,8 +11,7 @@ #include #include "corecel/Macros.hh" - -#include "../LoggerTypes.hh" +#include "corecel/io/LoggerTypes.hh" namespace celeritas { diff --git a/src/corecel/random/data/RanluxppRngData.hh b/src/corecel/random/data/RanluxppRngData.hh index 3ef6a01afc..00f3629173 100644 --- a/src/corecel/random/data/RanluxppRngData.hh +++ b/src/corecel/random/data/RanluxppRngData.hh @@ -70,6 +70,16 @@ struct RanluxppRngState int position; }; +//---------------------------------------------------------------------------// +/*! + * Initializes an RNG state. + */ +struct RanluxppRngStateInitializer +{ + //! Ranluxpp state number and carry bit + RanluxppNumber value; +}; + //---------------------------------------------------------------------------// /*! * Initializer object for the Ranluxpp engine diff --git a/src/corecel/random/data/XorwowRngData.hh b/src/corecel/random/data/XorwowRngData.hh index 9963f66678..969c982578 100644 --- a/src/corecel/random/data/XorwowRngData.hh +++ b/src/corecel/random/data/XorwowRngData.hh @@ -90,6 +90,10 @@ struct XorwowState XorwowUInt weylstate; //!< d }; +//---------------------------------------------------------------------------// +//! Initializes an RNG state for a branched RNG +using XorwowRngStateInitializer = XorwowState; + //---------------------------------------------------------------------------// /*! * XORWOW generator states for all threads. diff --git a/src/corecel/random/engine/RanluxppRngEngine.hh b/src/corecel/random/engine/RanluxppRngEngine.hh index fac883db78..671dea2539 100644 --- a/src/corecel/random/engine/RanluxppRngEngine.hh +++ b/src/corecel/random/engine/RanluxppRngEngine.hh @@ -61,6 +61,7 @@ class RanluxppRngEngine using Initializer_t = RanluxppInitializer; using ParamsRef = NativeCRef; using StateRef = NativeRef; + using RngStateInitializer_t = RanluxppRngStateInitializer; //@} public: @@ -83,21 +84,28 @@ class RanluxppRngEngine return celeritas::numeric_limits::max(); } - // Initialize state with the given seed. + // Initialize state with the given seed initializer inline CELER_FUNCTION RanluxppRngEngine& operator=(Initializer_t const& init); - // Generate a 32-bit random integer + // Initialize state with the given state initializer + inline CELER_FUNCTION RanluxppRngEngine& + operator=(RngStateInitializer_t const& state_init); + + // Generate a 32-bit random integer. inline CELER_FUNCTION result_type operator()(); // Advance the state \c count times. inline CELER_FUNCTION void discard(RanluxppUInt count); + // Initialize a state for a new spawned RNG. + inline CELER_FUNCTION RngStateInitializer_t branch(); + private: /// IMPLEMENTATION /// - // Produce the next block of random bits. - inline CELER_FUNCTION void advance(); + // Produce the next block of random bits for the given state. + inline CELER_FUNCTION void advance(RanluxppRngState& state); /// DATA /// static constexpr int offset_ = 48; @@ -153,6 +161,17 @@ RanluxppRngEngine::operator=(Initializer_t const& init) return *this; } +//---------------------------------------------------------------------------// +/*! + * Initialize state for the given state initializer. + */ +inline CELER_FUNCTION RanluxppRngEngine& +RanluxppRngEngine::operator=(RngStateInitializer_t const& state_init) +{ + (*state_).value = state_init.value; + return *this; +} + //---------------------------------------------------------------------------// /*! * Skip `n` random numbers without generating them. @@ -201,7 +220,7 @@ CELER_FUNCTION auto RanluxppRngEngine::operator()() -> result_type { if (state_->position + offset_ > params_.max_position) { - this->advance(); + this->advance(*state_); } // Extract the Nth word from the state @@ -222,16 +241,53 @@ CELER_FUNCTION auto RanluxppRngEngine::operator()() -> result_type return bits & 0xffffffffu; } +//---------------------------------------------------------------------------// +/*! + * Initialize a state for a new spawned RNG. + * + * \par Branching + * Branching is performed in two steps. First, the state of the new RNG + * (\f$x^{\prime}\f$) is initialized as + * \f[ + * x^{i,\prime}_j = x^i_j ^ x^{i+1}_j \, . + * \f] + * Second, to decorrelate the new RNG from this RNG, the new RNG is + * advanced forward to the next block + */ +CELER_FUNCTION RanluxppRngEngine::RngStateInitializer_t +RanluxppRngEngine::branch() +{ + // Create a new state + RanluxppRngState new_state = *state_; + + // Advance the RNG + this->advance(*state_); + + // XOR the new state with the updated and advanced state + for (auto i : celeritas::range(9)) + { + new_state.value.number[i] ^= (*state_).value.number[i]; + } + + // Advance the new rng to decorrelate it + this->advance(new_state); + + // Create and return the state initializer + RngStateInitializer_t initializer{new_state.value}; + + return initializer; +} + //---------------------------------------------------------------------------// /*! * Advance to the next state (block of random bits). */ -CELER_FUNCTION void RanluxppRngEngine::advance() +CELER_FUNCTION void RanluxppRngEngine::advance(RanluxppRngState& state) { - RanluxppArray9 lcg = celeritas::detail::to_lcg(state_->value); + RanluxppArray9 lcg = celeritas::detail::to_lcg(state.value); lcg = celeritas::detail::compute_mod_multiply(params_.advance_state, lcg); - state_->value = celeritas::detail::to_ranlux(lcg); - state_->position = 0; + state.value = celeritas::detail::to_ranlux(lcg); + state.position = 0; } //---------------------------------------------------------------------------// diff --git a/src/corecel/random/engine/XorwowRngEngine.hh b/src/corecel/random/engine/XorwowRngEngine.hh index 96084083f9..502e3b4971 100644 --- a/src/corecel/random/engine/XorwowRngEngine.hh +++ b/src/corecel/random/engine/XorwowRngEngine.hh @@ -62,6 +62,7 @@ class XorwowRngEngine using Initializer_t = XorwowRngInitializer; using ParamsRef = NativeCRef; using StateRef = NativeRef; + using RngStateInitializer_t = XorwowRngStateInitializer; //!@} public: @@ -75,15 +76,22 @@ class XorwowRngEngine StateRef const& state, TrackSlotId tid); - // Initialize state + // Initialize state with an RNG initializer inline CELER_FUNCTION XorwowRngEngine& operator=(Initializer_t const&); + // Initialize state with a state initializer + inline CELER_FUNCTION XorwowRngEngine& + operator=(RngStateInitializer_t const&); + // Generate a 32-bit pseudorandom number inline CELER_FUNCTION result_type operator()(); // Advance the state \c count times inline CELER_FUNCTION void discard(ull_int count); + // Initialize a state for a new spawned RNG + inline CELER_FUNCTION RngStateInitializer_t branch(); + private: /// TYPES /// @@ -98,9 +106,10 @@ class XorwowRngEngine //// HELPER FUNCTIONS //// inline CELER_FUNCTION void discard_subsequence(ull_int); - inline CELER_FUNCTION void next(); - inline CELER_FUNCTION void jump(ull_int, ArrayJumpPoly const&); - inline CELER_FUNCTION void jump(JumpPoly const&); + inline CELER_FUNCTION void next(XorwowState&); + inline CELER_FUNCTION void + jump(ull_int, ArrayJumpPoly const&, XorwowState&); + inline CELER_FUNCTION void jump(JumpPoly const&, XorwowState&); // Helper RNG for initializing the state struct SplitMix64 @@ -177,13 +186,25 @@ XorwowRngEngine::operator=(Initializer_t const& init) return *this; } +//---------------------------------------------------------------------------// +/*! + * Initialize the RNG engine with a state initializer. + */ +CELER_FUNCTION XorwowRngEngine& +XorwowRngEngine::operator=(RngStateInitializer_t const& state_init) +{ + state_->xorstate = state_init.xorstate; + state_->weylstate = state_init.weylstate; + return *this; +} + //---------------------------------------------------------------------------// /*! * Generate a 32-bit pseudorandom number using the 'xorwow' engine. */ CELER_FUNCTION auto XorwowRngEngine::operator()() -> result_type { - this->next(); + this->next(*state_); state_->weylstate += 362437u; return state_->weylstate + state_->xorstate[4]; } @@ -194,10 +215,41 @@ CELER_FUNCTION auto XorwowRngEngine::operator()() -> result_type */ CELER_FUNCTION void XorwowRngEngine::discard(ull_int count) { - this->jump(count, params_.jump); + this->jump(count, params_.jump, *state_); state_->weylstate += static_cast(count) * 362437u; } +//---------------------------------------------------------------------------// +/*! + * Generate a branched and (hopefully) decorreleated RNG + * + * \todo There are good reasons to believe that an RNG that is based on XOR + * operations may not be decorrelated when XORing the state. Work on an + * improved methodology may be warranted. + */ +CELER_FUNCTION XorwowRngEngine::RngStateInitializer_t XorwowRngEngine::branch() +{ + XorwowState new_state; + + // Copy the state into the new state + new_state.xorstate = state_->xorstate; + + // Advance this RNG 4 times to get new state + this->jump(4, params_.jump, *state_); + + // XOR the state with the new state + for (auto i : celeritas::range(new_state.xorstate.size())) + { + new_state.xorstate[i] ^= state_->xorstate[i]; + } + + // Advance the new state 4 times to (hopefully) decorrelate + this->jump(4, params_.jump, new_state); + + // Create and return the state initializer + return RngStateInitializer_t{new_state.xorstate, state_->weylstate}; +} + //---------------------------------------------------------------------------// /*! * Advance the state \c count subsequences (\c count * 2^67 times). @@ -207,7 +259,7 @@ CELER_FUNCTION void XorwowRngEngine::discard(ull_int count) */ CELER_FUNCTION void XorwowRngEngine::discard_subsequence(ull_int count) { - this->jump(count, params_.jump_subsequence); + this->jump(count, params_.jump_subsequence, *state_); } //---------------------------------------------------------------------------// @@ -216,9 +268,9 @@ CELER_FUNCTION void XorwowRngEngine::discard_subsequence(ull_int count) * * This does not update the Weyl sequence value. */ -CELER_FUNCTION void XorwowRngEngine::next() +CELER_FUNCTION void XorwowRngEngine::next(XorwowState& state) { - auto& s = state_->xorstate; + auto& s = state.xorstate; auto const t = (s[0] ^ (s[0] >> 2u)); s[0] = s[1]; @@ -235,8 +287,9 @@ CELER_FUNCTION void XorwowRngEngine::next() * This applies the jump polynomials until the given number of steps or * subsequences have been skipped. */ -CELER_FUNCTION void -XorwowRngEngine::jump(ull_int count, ArrayJumpPoly const& jump_poly_arr) +CELER_FUNCTION void XorwowRngEngine::jump(ull_int count, + ArrayJumpPoly const& jump_poly_arr, + XorwowState& state) { // Maximum number of times to apply any jump polynomial. Since the jump // sizes are 4^i for i = [0, 32), the max is 3. @@ -251,7 +304,7 @@ XorwowRngEngine::jump(ull_int count, ArrayJumpPoly const& jump_poly_arr) for (size_type i = 0; i < num_jump; ++i) { CELER_ASSERT(jump_idx < jump_poly_arr.size()); - this->jump(jump_poly_arr[jump_idx]); + this->jump(jump_poly_arr[jump_idx], state); } ++jump_idx; count >>= 2; @@ -286,7 +339,8 @@ XorwowRngEngine::jump(ull_int count, ArrayJumpPoly const& jump_poly_arr) * addition is the same as subtraction and equivalent to bitwise exclusive or, * and multiplication is bitwise and. */ -CELER_FUNCTION void XorwowRngEngine::jump(JumpPoly const& jump_poly) +CELER_FUNCTION void +XorwowRngEngine::jump(JumpPoly const& jump_poly, XorwowState& state) { Array s = {0}; for (size_type i : range(params_.num_words())) @@ -297,13 +351,13 @@ CELER_FUNCTION void XorwowRngEngine::jump(JumpPoly const& jump_poly) { for (size_type k : range(params_.num_words())) { - s[k] ^= state_->xorstate[k]; + s[k] ^= state.xorstate[k]; } } - this->next(); + this->next(state); } } - state_->xorstate = s; + state.xorstate = s; } //---------------------------------------------------------------------------// diff --git a/src/corecel/sys/Environment.cc b/src/corecel/sys/Environment.cc index 2a88d97032..621f131049 100644 --- a/src/corecel/sys/Environment.cc +++ b/src/corecel/sys/Environment.cc @@ -14,16 +14,25 @@ #include "corecel/io/Logger.hh" #include "corecel/io/StringUtils.hh" +using BoolFunc = std::function; + namespace celeritas { namespace { //---------------------------------------------------------------------------// -std::mutex& getenv_mutex() +/*! + * Use a recursive mutex due to "lazy" callbacks possibly using environment. + * + * Recursion (one getenv_lazy within another) can also be present due to the + * CELER_LOG calls. + */ +std::recursive_mutex& getenv_mutex() { - static std::mutex mu; + static std::recursive_mutex mu; return mu; } + //---------------------------------------------------------------------------// } // namespace @@ -77,6 +86,17 @@ std::string const& getenv(std::string const& key) */ GetenvFlagResult getenv_flag(std::string const& key, bool default_val) { + return getenv_flag_lazy(key, [default_val]() { return default_val; }); +} + +//---------------------------------------------------------------------------// +/*! + * Like \c getenv_flag but calls a function only when a default is needed. + */ +GetenvFlagResult +getenv_flag_lazy(std::string const& key, BoolFunc const& get_default_value) +{ + CELER_EXPECT(get_default_value); std::scoped_lock lock_{getenv_mutex()}; // Get the string value from the existing environment *or* system @@ -102,8 +122,8 @@ GetenvFlagResult getenv_flag(std::string const& key, bool default_val) }(); GetenvFlagResult result; + bool invalid_str{false}; result.defaulted = str_value.empty(); - result.value = default_val; if (!result.defaulted) { str_value = tolower(str_value); @@ -111,8 +131,13 @@ GetenvFlagResult getenv_flag(std::string const& key, bool default_val) static char const* const true_str[] = {"1", "t", "yes", "true"}; static char const* const false_str[] = {"0", "f", "no", "false"}; - if (std::find(std::begin(true_str), std::end(true_str), str_value) - != std::end(true_str)) + if (str_value == "auto") + { + // User forcing default value + result.defaulted = true; + } + else if (std::find(std::begin(true_str), std::end(true_str), str_value) + != std::end(true_str)) { result.value = true; } @@ -123,14 +148,25 @@ GetenvFlagResult getenv_flag(std::string const& key, bool default_val) } else { + result.defaulted = true; + invalid_str = true; + } + } + + if (result.defaulted) + { + // Get automatic default + result.value = get_default_value(); + + if (invalid_str) + { + // Warn after getting value, before overwriting string CELER_LOG(warning) << "Invalid environment value " << key << "=" << str_value << " (expected a flag): using default=" << result.value; } - } - else - { - // Save string value to be added to environment + + // Save string value actually used in environment str_value = result.value ? "1" : "0"; } diff --git a/src/corecel/sys/Environment.hh b/src/corecel/sys/Environment.hh index 275dfa4073..aff88c673a 100644 --- a/src/corecel/sys/Environment.hh +++ b/src/corecel/sys/Environment.hh @@ -112,6 +112,10 @@ std::string const& getenv(std::string const& key); // Thread-safe flag access to environment variables GetenvFlagResult getenv_flag(std::string const& key, bool default_val); +// Thread-safe flag access to environment variables with lazy function default +GetenvFlagResult +getenv_flag_lazy(std::string const& key, std::function const&); + // Write the accessed environment variables to a stream std::ostream& operator<<(std::ostream&, Environment const&); diff --git a/src/corecel/sys/MultiExceptionHandler.cc b/src/corecel/sys/MultiExceptionHandler.cc index 04689d83e6..961fcf76d2 100644 --- a/src/corecel/sys/MultiExceptionHandler.cc +++ b/src/corecel/sys/MultiExceptionHandler.cc @@ -7,6 +7,7 @@ #include "MultiExceptionHandler.hh" #include +#include #include "corecel/Assert.hh" #include "corecel/Types.hh" @@ -74,6 +75,10 @@ class ExceptionLogger { public: using VecStr = std::vector; + using size_type = std::size_t; + + //! Initialize with total number of exceptions to log + explicit ExceptionLogger(size_type total_count) : size_(total_count) {} void operator()(VecStr const& msg_stack) { @@ -81,17 +86,18 @@ class ExceptionLogger if (msg_stack.front() == last_msg_) { - ++ignored_counter_; + ++num_ignored_; } else { - flush_suppressed(); + this->flush_suppressed(); last_msg_ = msg_stack.front(); - auto log_msg = CELER_LOG_LOCAL(critical); - log_msg << "Suppressed exception from parallel thread: " - << join(msg_stack.begin(), msg_stack.end(), "\n... from "); + CELER_LOG_LOCAL(critical) + << '[' << index_ + 1 << '/' << size_ << "]: " + << join(msg_stack.begin(), msg_stack.end(), "\n ...from "); } + ++index_; } // Flush any suppressed messages before destruction @@ -99,25 +105,34 @@ class ExceptionLogger { try { - flush_suppressed(); + this->flush_suppressed(); } - // NOLINTNEXTLINE(bugprone-empty-catch) - catch (...) + catch (std::exception const& e) { + std::clog + << R"(failed to print suppressed exceptions during ExceptionLogger teardown: )" + << e.what() << std::endl; } } private: std::string last_msg_; - size_type ignored_counter_{0}; + size_type index_{0}; + size_type size_{0}; + size_type num_ignored_{0}; void flush_suppressed() { - if (ignored_counter_ > 0) + if (num_ignored_ > 0) { - CELER_LOG_LOCAL(warning) - << "Suppressed " << ignored_counter_ << " similar exceptions"; - ignored_counter_ = 0; + // Count should be the + CELER_ASSERT(num_ignored_ < index_); + auto previous = index_ - num_ignored_; + CELER_LOG_LOCAL(critical) + << '[' << previous + 1 << "-" << index_ << '/' << size_ + << "]: identical root cause to exception [" << previous << '/' + << size_ << ']'; + num_ignored_ = 0; } } }; @@ -129,7 +144,7 @@ namespace detail { //---------------------------------------------------------------------------// /*! - * Throw the first exception and log all the rest. + * Log all exceptions and rethrow the first on the list. */ [[noreturn]] void log_and_rethrow_impl(MultiExceptionHandler&& exceptions) { @@ -137,9 +152,9 @@ namespace detail auto exc_vec = std::move(exceptions).release(); ExceptionStackUnwinder unwind_stack; - ExceptionLogger log_exception; + ExceptionLogger log_exception{exc_vec.size()}; - for (auto eptr_iter = exc_vec.begin() + 1; eptr_iter != exc_vec.end(); + for (auto eptr_iter = exc_vec.begin(); eptr_iter != exc_vec.end(); ++eptr_iter) { try @@ -182,8 +197,8 @@ namespace detail CELER_LOG_LOCAL(critical) << "(unknown exception)"; } } - CELER_LOG(critical) << "failed to clear exceptions from " - "MultiExceptionHandler"; + CELER_LOG(critical) + << R"(failed to clear exceptions from MultiExceptionHandler)"; std::terminate(); } diff --git a/src/corecel/sys/MultiExceptionHandler.hh b/src/corecel/sys/MultiExceptionHandler.hh index e3534da7a0..9e5572ab1b 100644 --- a/src/corecel/sys/MultiExceptionHandler.hh +++ b/src/corecel/sys/MultiExceptionHandler.hh @@ -18,7 +18,7 @@ namespace celeritas { //---------------------------------------------------------------------------// /*! - * Temporarily store exception pointers. + * Temporarily store exception pointers to allow propagation across threads. * * This is useful for storing multiple exceptions in unrelated loops (where one * exception shouldn't affect the program flow outside of the scope), @@ -34,6 +34,11 @@ namespace celeritas log_and_rethrow(std::move(capture_exception)); * \endcode * + * If an exception is caught and the stored exception pointers are not cleared + * via \c release or \c log_and_rethrow , then at the end of the exception + * handler's lifetime, the stored exceptions will be printed and the program + * will be terminated. + * * \note This class implements an OpenMP \c critical mutex, not a \c std * mutex. If using this class in a \c std::thread context, wrap the call * operator in a lambda with a \c std::scoped_lock . We could refactor as a diff --git a/src/ddceler/CMakeLists.txt b/src/ddceler/CMakeLists.txt new file mode 100644 index 0000000000..c254f0572f --- /dev/null +++ b/src/ddceler/CMakeLists.txt @@ -0,0 +1,128 @@ +#------------------------------- -*- cmake -*- -------------------------------# +# Copyright Celeritas contributors: see top-level COPYRIGHT file for details +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +#----------------------------------------------------------------------------# + +# Set variables used by internal dd4hep macros +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CELERITAS_LIBRARY_OUTPUT_DIRECTORY}") +set(LIBRARY_OUTPUT_PATH "${CELERITAS_LIBRARY_OUTPUT_DIRECTORY}") + +#----------------------------------------------------------------------------# +# >>> OVERRIDE DD4HEP TO FIX NEW/RPATH/MACOS ROOT +# see https://github.com/AIDASoft/DD4hep/pull/1545 +#----------------------------------------------------------------------------# + +function(dd4hep_generate_rootmap library) + if(APPLE) + set(_ld_var DYLD_LIBRARY_PATH) + else() + set(_ld_var LD_LIBRARY_PATH) + endif() + + set(DD4HEP_LIBRARY_PATH "$ENV{${_ld_var}}" + CACHE STRING + "Set ${_ld_var} when adding plugins" + ) + + if("$ENV{ROOT_LIBRARY_PATH}") + set(_default_root_lib_path "$ENV{ROOT_LIBRARY_PATH}") + else() + set(_default_root_lib_path "${ROOT_LIBRARY_DIR}") + endif() + set(DD4HEP_ROOT_LIBRARY_PATH "${_default_root_lib_path}" + CACHE STRING + "Set ROOT_LIBRARY_PATH when adding plugins" + ) + + string(JOIN ":" _ld_path + "$" + "$" + "${DD4HEP_LIBRARY_PATH}" + ) + + set(rootmapfile ${CMAKE_SHARED_MODULE_PREFIX}${library}.components) + + add_custom_command(OUTPUT ${rootmapfile} + DEPENDS ${library} + COMMAND + "${CMAKE_COMMAND}" -E env + "${_ld_var}=${_ld_path}" + "ROOT_LIBRARY_PATH=${DD4HEP_ROOT_LIBRARY_PATH}" + $ -o ${rootmapfile} $ + WORKING_DIRECTORY ${LIBRARY_OUTPUT_PATH} + ) + + add_custom_target(Components_${library} ALL DEPENDS ${rootmapfile}) + set(install_destination "lib") + if(CMAKE_INSTALL_LIBDIR) + set(install_destination ${CMAKE_INSTALL_LIBDIR}) + endif() + install(FILES $/${rootmapfile} + DESTINATION ${install_destination} + ) +endfunction() + +# <<< END OVERRIDE +#----------------------------------------------------------------------------# + +get_target_property(_final_lib Celeritas::accel CUDA_RDC_FINAL_LIBRARY) +if(_final_lib) + # Using CUDA RDC: check for other flags as well + if(NOT DEFINED CELERITAS_DISABLE_NEW_DTAGS) + set(_dtag_flag "--disable-new-dtags") + include(CheckLinkerFlag) + check_linker_flag(CXX "LINKER:${_dtag_flag}" CELERITAS_DISABLE_NEW_DTAGS) + if(CELERITAS_DISABLE_NEW_DTAGS) + set(CELERITAS_DISABLE_NEW_DTAGS "${_dtag_flag}" CACHE INTERNAL + "flag used to disable dtags") + endif() + endif() +endif() + +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) + cmake_language(GET_MESSAGE_LOG_LEVEL _orig_log_lev) +else() + set(_orig_log_lev STATUS) +endif() + +function(celer_dd4hep_plugin target) + # Suppress verbose dd4hep + if(_orig_log_lev STREQUAL STATUS) + set(CMAKE_MESSAGE_LOG_LEVEL WARNING) + endif() + + dd4hep_add_plugin(${target} ${ARGN}) + + if(_final_lib) + # Add dependency on the final library to ensure it's built first + add_dependencies(${target} ${_final_lib}) + + # Link CUDA RDC "final" library into plugin + target_link_options(${target} PRIVATE + "-Wl,--push-state,--no-as-needed" + "$" + "-Wl,--pop-state" + ) + if(CELERITAS_DISABLE_NEW_DTAGS) + set_property(TARGET ${_plugins} APPEND + PROPERTY LINK_FLAGS "-Wl,--disable-new-dtags" + ) + endif() + endif() +endfunction() + +#----------------------------------------------------------------------------# +# Add plugins + +celer_dd4hep_plugin(CelerPhysics + SOURCES CelerPhysics.cc + USES DD4hep::DDG4 Celeritas::accel Celeritas::celeritas Celeritas::corecel +) + +celer_dd4hep_plugin(CelerRun + SOURCES CelerRun.cc + USES DD4hep::DDG4 DD4hep::DDCore + Celeritas::accel Celeritas::celeritas Celeritas::corecel +) + +#----------------------------------------------------------------------------# diff --git a/src/ddceler/CelerPhysics.cc b/src/ddceler/CelerPhysics.cc new file mode 100644 index 0000000000..16536c18ff --- /dev/null +++ b/src/ddceler/CelerPhysics.cc @@ -0,0 +1,206 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file ddceler/CelerPhysics.cc +//---------------------------------------------------------------------------// +#include "CelerPhysics.hh" + +#include +#include +#include +#include +#include + +#include "corecel/io/Logger.hh" +#include "celeritas/field/FieldDriverOptions.hh" +#include "celeritas/inp/Field.hh" +#include "accel/TrackingManagerIntegration.hh" + +using TMI = celeritas::TrackingManagerIntegration; +using Geant4Context = dd4hep::sim::Geant4Context; +using Geant4PhysicsList = dd4hep::sim::Geant4PhysicsList; +using OverlayedField = dd4hep::OverlayedField; +using CartesianField = dd4hep::CartesianField; +using ConstantField = dd4hep::ConstantField; +using Direction = dd4hep::Direction; + +namespace celeritas +{ +namespace dd +{ +namespace +{ +//---------------------------------------------------------------------------// + +FieldDriverOptions load_driver_options(dd4hep::sim::Geant4Action* field_action) +{ + FieldDriverOptions driver_options; + constexpr auto celer_mm = units::millimeter; + + // Load field tracking parameters directly from DD4hep action properties + // Values are in DD4hep units (mm) + driver_options.delta_chord + = field_action->property("delta_chord").value() * celer_mm; + driver_options.delta_intersection + = field_action->property("delta_intersection").value() + * celer_mm; + driver_options.minimum_step + = field_action->property("delta_one_step").value() * celer_mm; + + return driver_options; +} + +} // namespace + +//---------------------------------------------------------------------------// +/*! + * Standard constructor + */ +CelerPhysics::CelerPhysics(Geant4Context* ctxt, std::string const& name) + : Geant4PhysicsList(ctxt, name) +{ + declareProperty("MaxNumTracks", max_num_tracks_); + declareProperty("InitCapacity", init_capacity_); + declareProperty("IgnoreProcesses", ignore_processes_); +} + +//---------------------------------------------------------------------------// +SetupOptions CelerPhysics::make_options() +{ + SetupOptions opts; + + // Validate configuration parameters + CELER_VALIDATE(max_num_tracks_ > 0, + << "invalid MaxNumTracks=" << max_num_tracks_ + << "(should be positive)"); + CELER_VALIDATE(init_capacity_ > 0, + << "invalid InitCapacity=" << init_capacity_ + << " (should be positive)"); + + opts.max_num_tracks = max_num_tracks_; + opts.initializer_capacity = init_capacity_; + + // Set ignored processes from configuration + for (auto const& proc : ignore_processes_) + { + opts.ignore_processes.push_back(proc); + } + + // Get the field from DD4hep detector description and validate its type + auto& detector = context()->detectorDescription(); + auto&& field = detector.field(); + auto* overlaid_obj = field.data(); + + // Validate field configuration: no electric components + CELER_VALIDATE(overlaid_obj->electric_components.empty(), + << "Celeritas does not support electric field components. " + "Found " + << overlaid_obj->electric_components.size() + << " electric component(s)."); + + CELER_VALIDATE(!overlaid_obj->magnetic_components.empty(), + << "No magnetic field components found in DD4hep field " + "description."); + + // Check that all magnetic components are ConstantField and sum them + Direction field_direction(0, 0, 0); + for (auto const& mag_component : overlaid_obj->magnetic_components) + { + auto* cartesian_obj = mag_component.data(); + auto* const_field = dynamic_cast(cartesian_obj); + + CELER_VALIDATE(const_field, + << "Celeritas currently only supports ConstantField " + "magnetic " + << "fields. Found non-constant field component in " + "DD4hep " + << "description."); + field_direction += const_field->direction; + } + + // Print field strength + // Note: field_direction is already in DD4hep internal units (parsed from + // XML) DD4hep supports tesla, gauss, kilogauss, etc. in XML and converts + // to internal units + constexpr double dd4hep_tesla = dd4hep::tesla; + CELER_LOG(debug) << "Field strength: (" + << field_direction.X() / dd4hep_tesla << ", " + << field_direction.Y() / dd4hep_tesla << ", " + << field_direction.Z() / dd4hep_tesla << ") T"; + + // Get field tracking parameters from DD4hep FieldSetup action + // These parameters are set in the steering file (runner.field.*) + dd4hep::sim::Geant4Action* field_action = nullptr; + if (auto* config_phase = context()->kernel().getPhase("configure")) + { + // Find the MagFieldTrackingSetup action in the configure phase + for (auto const& [action, callback] : config_phase->members()) + { + if (action->name() == "MagFieldTrackingSetup") + { + field_action = action; + break; + } + } + } + + FieldDriverOptions driver_options; + if (field_action) + { + driver_options = load_driver_options(field_action); + CELER_LOG(debug) << "Loaded field driver options from DD4hep " + "FieldSetup action"; + } + else + { + CELER_LOG(warning) << "MagFieldTrackingSetup action not found, using " + "default field parameters"; + } + + // Print field driver options + constexpr auto celer_mm = units::millimeter; + CELER_LOG(debug) + << "Field driver options: min_step=" + << driver_options.minimum_step / celer_mm + << " mm, delta_chord=" << driver_options.delta_chord / celer_mm + << " mm, delta_intersection=" + << driver_options.delta_intersection / celer_mm << " mm"; + + // Use a uniform magnetic field based on DD4hep ConstantField + auto make_field_input = [field_direction, driver_options] { + inp::UniformField input; + + // Convert from DD4hep (tesla) to Celeritas field units + input.strength = {field_direction.X() / dd4hep_tesla, + field_direction.Y() / dd4hep_tesla, + field_direction.Z() / dd4hep_tesla}; + input.driver_options = driver_options; + return input; + }; + opts.make_along_step = UniformAlongStepFactory(make_field_input); + opts.sd.ignore_zero_deposition = false; + + // Save diagnostic file to a unique name + opts.output_file = "ddceler.out.json"; + opts.geometry_output_file = "ddceler.out.gdml"; + return opts; +} + +//---------------------------------------------------------------------------// + +void CelerPhysics::constructPhysics(G4VModularPhysicsList* physics) +{ + // Register Celeritas tracking manager + auto& tmi = TMI::Instance(); + physics->RegisterPhysics(new TrackingManagerConstructor(&tmi)); + + // Configure Celeritas options + tmi.SetOptions(this->make_options()); +} + +//---------------------------------------------------------------------------// +} // namespace dd +} // namespace celeritas + +DECLARE_GEANT4ACTION_NS(celeritas::dd, CelerPhysics) diff --git a/src/ddceler/CelerPhysics.hh b/src/ddceler/CelerPhysics.hh new file mode 100644 index 0000000000..f99ccc14d5 --- /dev/null +++ b/src/ddceler/CelerPhysics.hh @@ -0,0 +1,47 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file ddceler/CelerPhysics.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace celeritas +{ +namespace dd +{ +//---------------------------------------------------------------------------// +/*! + * DDG4 action plugin for Celeritas tracking manager integration (TMI). + */ +class CelerPhysics final : public dd4hep::sim::Geant4PhysicsList +{ + public: + // Standard constructor + CelerPhysics(dd4hep::sim::Geant4Context* ctxt, std::string const& name); + + // Delete copy/move + DDG4_DEFINE_ACTION_CONSTRUCTORS(CelerPhysics); + + // constructPhysics callback + virtual void constructPhysics(G4VModularPhysicsList* physics) final; + + private: + int max_num_tracks_{0}; + int init_capacity_{0}; + std::vector ignore_processes_; + + // Make options for Celeritas tracking manager + SetupOptions make_options(); +}; + +//---------------------------------------------------------------------------// +} // namespace dd +} // namespace celeritas diff --git a/src/ddceler/CelerRun.cc b/src/ddceler/CelerRun.cc new file mode 100644 index 0000000000..7662b9c501 --- /dev/null +++ b/src/ddceler/CelerRun.cc @@ -0,0 +1,58 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file ddceler/CelerRun.cc +//---------------------------------------------------------------------------// +#include "CelerRun.hh" + +#include +#include + +#include "accel/TrackingManagerIntegration.hh" + +using TMI = celeritas::TrackingManagerIntegration; +using Geant4Context = dd4hep::sim::Geant4Context; +using Geant4RunAction = dd4hep::sim::Geant4RunAction; +using InstanceCount = dd4hep::InstanceCount; + +namespace celeritas +{ +namespace dd +{ +//---------------------------------------------------------------------------// +/*! + * Standard constructor. + */ +CelerRun::CelerRun(Geant4Context* ctxt, std::string const& name) + : Geant4RunAction(ctxt, name) +{ + InstanceCount::increment(this); +} + +//---------------------------------------------------------------------------// + +CelerRun::~CelerRun() +{ + InstanceCount::decrement(this); +} + +//---------------------------------------------------------------------------// + +void CelerRun::begin(G4Run const* run) +{ + TMI::Instance().BeginOfRunAction(run); +} + +//---------------------------------------------------------------------------// + +void CelerRun::end(G4Run const* run) +{ + TMI::Instance().EndOfRunAction(run); +} + +//---------------------------------------------------------------------------// +} // namespace dd +} // namespace celeritas + +DECLARE_GEANT4ACTION_NS(celeritas::dd, CelerRun) diff --git a/src/ddceler/CelerRun.hh b/src/ddceler/CelerRun.hh new file mode 100644 index 0000000000..d2eb61af9d --- /dev/null +++ b/src/ddceler/CelerRun.hh @@ -0,0 +1,39 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file ddceler/CelerRun.hh +//---------------------------------------------------------------------------// +#pragma once + +#include +#include +#include + +namespace celeritas +{ +namespace dd +{ +//---------------------------------------------------------------------------// +/*! + * DDG4 action plugin for Celeritas run action. + */ +class CelerRun final : public dd4hep::sim::Geant4RunAction +{ + public: + // Standard constructor + CelerRun(dd4hep::sim::Geant4Context* ctxt, std::string const& name); + + // Run action callbacks + void begin(G4Run const* run) final; + void end(G4Run const* run) final; + + protected: + // Define standard assignments and constructors + DDG4_DEFINE_ACTION_CONSTRUCTORS(CelerRun); + ~CelerRun() final; +}; + +//---------------------------------------------------------------------------// +} // namespace dd +} // namespace celeritas diff --git a/src/geocel/GeantGeoParams.cc b/src/geocel/GeantGeoParams.cc index 87b9288080..81544a1ae7 100644 --- a/src/geocel/GeantGeoParams.cc +++ b/src/geocel/GeantGeoParams.cc @@ -27,20 +27,16 @@ #include #include -#include "corecel/Assert.hh" -#include "geocel/inp/Model.hh" -#if G4VERSION_NUMBER >= 1070 -# include -#endif - #include "corecel/Config.hh" +#include "corecel/Assert.hh" #include "corecel/cont/Range.hh" #include "corecel/io/Logger.hh" #include "corecel/io/StringUtils.hh" #include "corecel/sys/Device.hh" #include "corecel/sys/ScopedMem.hh" #include "corecel/sys/ScopedProfiling.hh" +#include "geocel/inp/Model.hh" #include "GeantGdmlLoader.hh" #include "GeantGeoUtils.hh" @@ -674,8 +670,6 @@ GeantGeoParams::from_gdml(std::string const& filename) ScopedGeantLogger logger(celeritas::world_logger()); ScopedGeantExceptionHandler exception_handler; - disable_geant_signal_handler(); - if (!ends_with(filename, ".gdml")) { CELER_LOG(warning) << "Expected '.gdml' extension for GDML input"; diff --git a/src/geocel/GeantUtils.cc b/src/geocel/GeantUtils.cc index 5c3bc5a819..ce3c29b73e 100644 --- a/src/geocel/GeantUtils.cc +++ b/src/geocel/GeantUtils.cc @@ -15,9 +15,6 @@ #if G4VERSION_NUMBER < 1070 # include #endif -#if G4VERSION_NUMBER >= 1070 -# include -#endif #ifdef _OPENMP # include @@ -30,21 +27,6 @@ namespace celeritas { -//---------------------------------------------------------------------------// -/*! - * Clear Geant4's signal handlers that get installed on startup/activation. - * - * This should be called before instantiating a run manager. - */ -void disable_geant_signal_handler() -{ -#if G4VERSION_NUMBER >= 1070 - CELER_LOG(debug) << "Disabling Geant4 signal handlers"; - // Disable geant4 signal interception - G4Backtrace::DefaultSignals() = {}; -#endif -} - //---------------------------------------------------------------------------// /*! * Get the number of threads in a version-portable way. diff --git a/src/geocel/GeantUtils.hh b/src/geocel/GeantUtils.hh index 3feb373df2..9a8631a917 100644 --- a/src/geocel/GeantUtils.hh +++ b/src/geocel/GeantUtils.hh @@ -16,10 +16,6 @@ class G4RunManager; namespace celeritas { -//---------------------------------------------------------------------------// -// Clear Geant4's signal handlers that get installed when linking 11+ -void disable_geant_signal_handler(); - //---------------------------------------------------------------------------// // Get the number of threads in a version-portable way int get_geant_num_threads(G4RunManager const&); @@ -50,8 +46,6 @@ std::ostream& operator<<(std::ostream& os, StreamablePD const& pd); // INLINE DEFINITIONS //---------------------------------------------------------------------------// #if !CELERITAS_USE_GEANT4 -inline void disable_geant_signal_handler() {} - inline int get_geant_num_threads(G4RunManager const&) { CELER_NOT_CONFIGURED("Geant4"); diff --git a/src/geocel/ScopedGeantExceptionHandler.hh b/src/geocel/ScopedGeantExceptionHandler.hh index 2ad8bece07..3807113f45 100644 --- a/src/geocel/ScopedGeantExceptionHandler.hh +++ b/src/geocel/ScopedGeantExceptionHandler.hh @@ -23,15 +23,15 @@ namespace celeritas /*! * Convert Geant4 exceptions to RuntimeError during this class lifetime. * - * Note that creating a \c G4RunManagerKernel resets the exception - * handler, so errors thrown during setup \em cannot be caught by Celeritas, - * and this class can only be used after creating the \c G4RunManager. - * * Because the underlying Geant4 error handler is thread-local, this class must * be scoped to inside each worker thread. Additionally, since throwing from a * worker thread terminates the program, an error handler \em must be specified * if used in a worker thread: you should probably use a \c - * celeritas::MultiExceptionHandler . + * celeritas::MultiExceptionHandler if used inside a worker thread. + * + * \note Creating a \c G4RunManagerKernel resets the exception + * handler, so errors thrown during setup \em cannot be caught by Celeritas, + * and this class can only be used after creating the \c G4RunManager. */ class ScopedGeantExceptionHandler { diff --git a/src/geocel/ScopedGeantLogger.cc b/src/geocel/ScopedGeantLogger.cc index 19fbd5e792..9bf73422c4 100644 --- a/src/geocel/ScopedGeantLogger.cc +++ b/src/geocel/ScopedGeantLogger.cc @@ -104,22 +104,43 @@ G4int log_impl(celeritas::Logger& log, G4String const& str, LogLevel level) return 0; } +//---------------------------------------------------------------------------// +//! Thread-local flags for ownership/usability of the Geant4 logger +enum class SGLState +{ + inactive, + active, + disabled +}; + +SGLState& sgl_state() +{ + static G4ThreadLocal SGLState result{SGLState::inactive}; + return result; +} + +//---------------------------------------------------------------------------// +} // namespace + //---------------------------------------------------------------------------// /*! * Send Geant4 log messages to Celeritas' world logger. */ -class GeantLoggerAdapter final : public G4coutDestination +class ScopedGeantLogger::StreamDestination final : public G4coutDestination { public: // Assign to Geant handlers on construction - explicit GeantLoggerAdapter(Logger&); - CELER_DELETE_COPY_MOVE(GeantLoggerAdapter); - ~GeantLoggerAdapter() final; + explicit StreamDestination(Logger&); + CELER_DELETE_COPY_MOVE(StreamDestination); + ~StreamDestination() final; // Handle error messages G4int ReceiveG4cout(G4String const& str) final; G4int ReceiveG4cerr(G4String const& str) final; + // Access logger from ScopedGeantLogger cleanup + Logger& logger() { return celer_log_; } + private: Logger& celer_log_; #if CELER_G4SSBUF @@ -134,7 +155,7 @@ class GeantLoggerAdapter final : public G4coutDestination * * Note that all these buffers, and the UI pointers, are thread-local. */ -GeantLoggerAdapter::GeantLoggerAdapter(Logger& celer_log) +ScopedGeantLogger::StreamDestination::StreamDestination(Logger& celer_log) : celer_log_{celer_log} { // Make sure UI pointer has been instantiated, since its constructor @@ -163,7 +184,7 @@ GeantLoggerAdapter::GeantLoggerAdapter(Logger& celer_log) /*! * Restore iostream buffers on destruction. */ -GeantLoggerAdapter::~GeantLoggerAdapter() +ScopedGeantLogger::StreamDestination::~StreamDestination() { #if CELER_G4SSBUF G4coutbuf.SetDestination(saved_cout_); @@ -177,7 +198,7 @@ GeantLoggerAdapter::~GeantLoggerAdapter() /*! * Process stdout message. */ -G4int GeantLoggerAdapter::ReceiveG4cout(G4String const& str) +G4int ScopedGeantLogger::StreamDestination::ReceiveG4cout(G4String const& str) { return log_impl(celer_log_, str, LogLevel::diagnostic); } @@ -186,29 +207,11 @@ G4int GeantLoggerAdapter::ReceiveG4cout(G4String const& str) /*! * Process stderr message. */ -G4int GeantLoggerAdapter::ReceiveG4cerr(G4String const& str) +G4int ScopedGeantLogger::StreamDestination::ReceiveG4cerr(G4String const& str) { return log_impl(celer_log_, str, LogLevel::info); } -//---------------------------------------------------------------------------// -//! Thread-local flags for ownership/usability of the Geant4 logger -enum class SGLState -{ - inactive, - active, - disabled -}; - -SGLState& sgl_state() -{ - static G4ThreadLocal SGLState result{SGLState::inactive}; - return result; -} - -//---------------------------------------------------------------------------// -} // namespace - //---------------------------------------------------------------------------// //! Enable and disable to avoid recursion with accel/Logger //!@{ @@ -236,8 +239,20 @@ ScopedGeantLogger::ScopedGeantLogger(Logger& celer_log) auto& state = sgl_state(); if (state == SGLState::inactive) { + celer_log(CELER_CODE_PROVENANCE, LogLevel::debug) + << "Activating ScopedGeantLogger"; + state = SGLState::active; - logger_ = std::make_unique(celer_log); + logger_ = std::make_unique(celer_log); + } + else + { + celer_log(CELER_CODE_PROVENANCE, LogLevel::debug) + << R"(Ignoring ScopedGeantLogger initialization because )" + << (state == SGLState::active + ? R"(Geant4 is already being redirected on this thread)" + : state == SGLState::disabled ? R"(log redirection is disabled)" + : ""); } } @@ -261,7 +276,12 @@ ScopedGeantLogger::~ScopedGeantLogger() { state = SGLState::inactive; } - logger_.reset(); + if (logger_) + { + logger_->logger()(CELER_CODE_PROVENANCE, LogLevel::debug) + << "Resetting ScopedGeantLogger"; + logger_.reset(); + } } //---------------------------------------------------------------------------// diff --git a/src/geocel/ScopedGeantLogger.hh b/src/geocel/ScopedGeantLogger.hh index 1aad05be86..8a96652e9c 100644 --- a/src/geocel/ScopedGeantLogger.hh +++ b/src/geocel/ScopedGeantLogger.hh @@ -12,18 +12,28 @@ #include "corecel/Macros.hh" -class G4coutDestination; - namespace celeritas { class Logger; //---------------------------------------------------------------------------// /*! - * Install a Geant output destination during this class's lifetime. + * Redirect Geant4 logging through Celeritas' logger. + * + * This parses messages sent to \c G4cout and \c G4cerr from Geant4. Based on + * the message (whether it starts with warning, error, '!!!', '***') it tries + * to use the appropriate logging level and source context. * * Since the Geant4 output streams are thread-local, this class is as well. - * Multiple geant loggers can be nested, and only the outermost on a given - * thread will "own" the log destination. + * Multiple geant loggers can be nested in scope, but only the outermost on a + * given thread will "own" the log destination. + * + * - When instantiated during setup, this should be constructed with + * \c celeritas::world_logger to avoid printing duplicate messages per + * thread/process. + * - When instantiated during runtime, it should take the + * \c celeritas::self_logger so that only warning/error messages are printed + * for event/track-specific details. + * */ class ScopedGeantLogger { @@ -45,20 +55,24 @@ class ScopedGeantLogger CELER_DELETE_COPY_MOVE(ScopedGeantLogger); private: -#if CELERITAS_USE_GEANT4 - std::unique_ptr logger_; -#endif + class StreamDestination; + + std::unique_ptr logger_; }; #if !CELERITAS_USE_GEANT4 -// Do nothing if Geant4 is disabled (source file will not be compiled) +// Do nothing if Geant4 is disabled inline bool ScopedGeantLogger::enabled() { return false; } +inline void ScopedGeantLogger::enabled(bool) {} inline ScopedGeantLogger::ScopedGeantLogger(Logger&) {} inline ScopedGeantLogger::ScopedGeantLogger() {} inline ScopedGeantLogger::~ScopedGeantLogger() {} +class ScopedGeantLogger::StreamDestination +{ +}; #endif //---------------------------------------------------------------------------// diff --git a/src/geocel/vg/VecgeomTrackView.hh b/src/geocel/vg/VecgeomTrackView.hh index 10a30e3c0c..682694767f 100644 --- a/src/geocel/vg/VecgeomTrackView.hh +++ b/src/geocel/vg/VecgeomTrackView.hh @@ -328,6 +328,7 @@ CELER_FUNCTION VolumeInstanceId VecgeomTrackView::volume_instance_id() const */ CELER_FUNCTION VolumeLevelId VecgeomTrackView::volume_level() const { + CELER_EXPECT(!this->is_outside()); auto result = id_cast(vgstate_.GetLevel()); CELER_ENSURE(result < params_.scalars.num_volume_levels); return result; @@ -493,7 +494,7 @@ CELER_FUNCTION Propagation VecgeomTrackView::find_next_step(real_type max_step) // Soft equivalence between distance and max step is because the // BVH navigator subtracts and then re-adds a bump distance to the // step - CELER_ASSERT(soft_equal(next_step_, max_step)); + CELER_ASSERT(soft_equal(next_step_, max(max_step, this->extra_push()))); next_step_ = max_step; } diff --git a/src/geocel/vg/detail/BVHNavigator.hh b/src/geocel/vg/detail/BVHNavigator.hh index f393896db2..065d9e2285 100644 --- a/src/geocel/vg/detail/BVHNavigator.hh +++ b/src/geocel/vg/detail/BVHNavigator.hh @@ -49,7 +49,11 @@ class BVHNavigator using NavState = detail::VgNavStateWrapper; #endif - static constexpr vg_real_type kBoundaryPush = 10 * vecgeom::kTolerance; +#ifdef VECGEOM_FLOAT_PRECISION + static constexpr vg_real_type kBoundaryPush = 10 * 1e-3f; +#else + static constexpr vg_real_type kBoundaryPush = 10 * 1e-9; +#endif //! Update path (which must be reset in advance) CELER_FUNCTION static void diff --git a/src/larceler/CMakeLists.txt b/src/larceler/CMakeLists.txt index 7afc0dde94..604386d390 100644 --- a/src/larceler/CMakeLists.txt +++ b/src/larceler/CMakeLists.txt @@ -15,10 +15,23 @@ set(PUBLIC_DEPS Celeritas::BuildFlags ) -if(TARGET larsim::OpticalPropagationTools) - message(SEND_ERROR "This version of Celeritas is meant for testing larsim " - "before https://github.com/nuRiceLab/larsim/pull/1 : please implement or " - "update Celeritas") +#-----------------------------------------------------------------------------# +# Create mock upstream library for building against official LArSoft +#-----------------------------------------------------------------------------# + +set(_larsoft_tool_target larsim::OpticalPropagationTools) +if(NOT TARGET ${_larsoft_tool_target}) + message(WARNING "LArSoft version is too old for integrated Celeritas support: " + "defining fake plugin target for local testing " + "(missing target ${_larsoft_tool_target})" + ) + celeritas_add_interface_library(OpticalPropagationTools) + celeritas_target_include_directories(OpticalPropagationTools + SYSTEM INTERFACE + "$" + "$" + ) + set(_larsoft_tool_target Celeritas::OpticalPropagationTools) endif() #-----------------------------------------------------------------------------# @@ -48,6 +61,7 @@ celeritas_target_link_libraries(${_plugin_name} cetlib_except::cetlib_except larcoreobj::SimpleTypesAndConstants # Implicit dependency needed by MCBase lardataobj::MCBase + ${_larsoft_tool_target} PUBLIC ${PUBLIC_DEPS} ) diff --git a/src/larceler/LarCelerStandalone.cc b/src/larceler/LarCelerStandalone.cc index e4ef5cb300..719a15b324 100644 --- a/src/larceler/LarCelerStandalone.cc +++ b/src/larceler/LarCelerStandalone.cc @@ -14,8 +14,8 @@ #include "corecel/Assert.hh" -#include "larceler/LarStandaloneRunner.hh" -#include "larceler/inp/LarStandaloneRunner.hh" +#include "LarStandaloneRunner.hh" +#include "inp/LarStandaloneRunner.hh" namespace celeritas { @@ -47,6 +47,8 @@ auto LarCelerStandalone::executeEvent(VecSED const& edeps) -> UPVecBTR CELER_EXPECT(runner_); CELER_EXPECT(!edeps.empty()); + using VecBTR = LarStandaloneRunner::VecBTR; + // Calculate detector responsors for the input steps auto& run = *runner_; VecBTR result = run(edeps); diff --git a/src/larceler/LarCelerStandalone.hh b/src/larceler/LarCelerStandalone.hh index ace62bcade..cab866480a 100644 --- a/src/larceler/LarCelerStandalone.hh +++ b/src/larceler/LarCelerStandalone.hh @@ -8,8 +8,8 @@ #pragma once #include -#include #include +#include #include "LarStandaloneRunner.hh" @@ -21,34 +21,6 @@ class SimEnergyDeposit; class OpDetBacktrackerRecord; } // namespace sim -// TODO: This will be defined upstream: -// see https://github.com/nuRiceLab/larsim/pull/1 -namespace phot -{ -class OpticalSimInterface -{ - public: - //!@{ - //! \name Type aliases - using VecSED = std::vector; - using VecBTR = std::vector; - using UPVecBTR = std::unique_ptr; - ///@} - - // Enable polymorphic deletion - virtual ~OpticalSimInterface() = 0; - - // Set up execution - virtual void beginJob() = 0; - - // Process a single event, returning detector hits - virtual UPVecBTR executeEvent(VecSED const& edeps) = 0; - - // Tear down execution - virtual void endJob() = 0; -}; -} // namespace phot - namespace celeritas { //---------------------------------------------------------------------------// @@ -56,11 +28,11 @@ namespace celeritas * Run optical photons in a standalone simulation. * * This plugin implements a replacement for LArSim's \c phot::PDFastSimPAR - * class, taking a vector of energy-depositing steps and returning a vector - * is instantiated by a FHiCL workflow file with a set of - * parameters. It is executed after the detector simulation step (ionization, - * recombination, scintillation, etc.) with a vector of steps that contain - * energy deposition, and it returns a vector of detector responses. + * class. It is instantiated by a FHiCL workflow file with a set of + * parameters. It takes a vector of energy-depositing steps and returns a + * vector of detector responses. It is executed after the detector simulation + * module (ionization, recombination, scintillation, etc.) with a vector of + * steps that contain local energy deposition. * * The execution happens \em after LArG4 is complete, so it is completely * independent of the Geant4 run manager and execution. It requires an input @@ -75,7 +47,7 @@ namespace celeritas * * See \c celeritas::detail::LarCelerStandaloneConfig . */ -class LarCelerStandalone final : public phot::OpticalSimInterface +class LarCelerStandalone final : public phot::IOpticalPropagation { public: //!@{ diff --git a/src/larceler/inp/LarStandaloneRunner.cc b/src/larceler/inp/LarStandaloneRunner.cc index 2a7baa6100..4e5daf9459 100644 --- a/src/larceler/inp/LarStandaloneRunner.cc +++ b/src/larceler/inp/LarStandaloneRunner.cc @@ -6,8 +6,6 @@ //---------------------------------------------------------------------------// #include "LarStandaloneRunner.hh" -#include "corecel/io/Logger.hh" - #include "larceler/detail/LarCelerConfig.hh" namespace celeritas @@ -34,11 +32,9 @@ from_config(detail::LarCelerStandaloneConfig const& cfg) #endif out.geometry = cfg.geometry(); - out.optical_step_iters = cfg.optical_step_iters(); - if (out.optical_step_iters == 0) + if (auto step_iters = cfg.optical_step_iters()) { - CELER_LOG(debug) << "Using unlimited optical step iters"; - out.optical_step_iters = out.unlimited; + out.tracking_limits.step_iters = step_iters; } // Optical capacities diff --git a/src/larceler/inp/LarStandaloneRunner.hh b/src/larceler/inp/LarStandaloneRunner.hh index c84aee7e6b..eabb329631 100644 --- a/src/larceler/inp/LarStandaloneRunner.hh +++ b/src/larceler/inp/LarStandaloneRunner.hh @@ -6,12 +6,11 @@ //---------------------------------------------------------------------------// #pragma once -#include #include #include -#include "corecel/Types.hh" #include "celeritas/inp/Control.hh" +#include "celeritas/inp/Tracking.hh" namespace celeritas { @@ -36,10 +35,6 @@ namespace inp */ struct LarStandaloneRunner { - //! Don't limit the number of steps (from TrackingLimits) - static constexpr size_type unlimited - = std::numeric_limits::max(); - //// DATA //// //! Environment variables used for program setup/diagnostic @@ -49,7 +44,7 @@ struct LarStandaloneRunner std::string geometry; //! Step iterations before aborting the optical stepping loop - size_type optical_step_iters{unlimited}; + OpticalTrackingLimits tracking_limits; //! Optical buffer sizes OpticalStateCapacity optical_capacity; diff --git a/src/larceler/larsim-future/larsim/PhotonPropagation/OpticalPropagationTools/IOpticalPropagation.h b/src/larceler/larsim-future/larsim/PhotonPropagation/OpticalPropagationTools/IOpticalPropagation.h new file mode 100644 index 0000000000..535f2d1155 --- /dev/null +++ b/src/larceler/larsim-future/larsim/PhotonPropagation/OpticalPropagationTools/IOpticalPropagation.h @@ -0,0 +1,54 @@ +//---------------------------------------------------------------------------// +//! \file IOpticalPropagation.h +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // +//! \brief Abstract interface for optical simulation libraries +//---------------------------------------------------------------------------// +#pragma once + +#include +#include +#include +#include + +namespace phot +{ +class IOpticalPropagation; +} + +//-------------------------------------------------------------------------// +/*! + * Abstract interface for optical propagation. + * + * This interface allows the addition of different optical photon propagation + * tools. As an \c art::tool, the \c executeEvent function is called *once* + * during a \c art::EDProducer::produce module execution, and uses all \c + * sim::SimEnergyDeposits from an \c art::Event found by the \c art::Handle . + * + * I.e. a single \c executeEvent function call propagates all resulting + * optical photons from the existing batch of energy depositions on an + * event-by-event basis. It is currently expected to manage 3 methods: + * - \c PDFastSimPAR : already available in larsim + * - \c Celeritas : Full optical particle transport on CPU and GPU + * - \c Opticks : Full optical particle transport on Nvidia GPUs + * + * The interfaces takes a vector of \c sim::SimEnergyDeposit as input and + * produces a vector of \c sim::OpDetBacktrackerRecord from detector hits. + */ +class phot::IOpticalPropagation +{ + public: + //!@{ + //! \name Type aliases + using VecSED = std::vector; + using UPVecBTR = std::unique_ptr>; + ///@} + + // Initialize tool + virtual void beginJob() = 0; + + // Execute is called for every art::Event + virtual UPVecBTR executeEvent(VecSED const& edeps) = 0; + + // Bring tool back to invalid state + virtual void endJob() = 0; +}; diff --git a/src/orange/orangeinp/ObjectIO.json.cc b/src/orange/orangeinp/ObjectIO.json.cc index d7d3c796e0..ba0fd11259 100644 --- a/src/orange/orangeinp/ObjectIO.json.cc +++ b/src/orange/orangeinp/ObjectIO.json.cc @@ -85,21 +85,6 @@ void to_json(nlohmann::json& j, PolyCone const& obj) } } -void to_json(nlohmann::json& j, PolyPrism const& obj) -{ - j = { - {"_type", "polyprism"}, - SIO_ATTR_PAIR(obj, label), - SIO_ATTR_PAIR(obj, segments), - SIO_ATTR_PAIR(obj, num_sides), - SIO_ATTR_PAIR(obj, orientation), - }; - if (auto azi = obj.enclosed_azi()) - { - j["enclosed_azi"] = azi; - } -} - void to_json(nlohmann::json& j, RevolvedPolygon const& obj) { j = { @@ -349,7 +334,7 @@ namespace nlohmann void adl_serializer::to_json(json& j, CelerSPObjConst const& oi) { - j = oi ? celeritas::json_pimpl_output(*oi) : json(nullptr); + j = oi ? celeritas::output_to_json(*oi) : json(nullptr); } void adl_serializer::to_json(json& j, diff --git a/src/orange/orangeinp/ObjectIO.json.hh b/src/orange/orangeinp/ObjectIO.json.hh index 357ebfefed..3743828da4 100644 --- a/src/orange/orangeinp/ObjectIO.json.hh +++ b/src/orange/orangeinp/ObjectIO.json.hh @@ -25,7 +25,6 @@ template class JoinObjects; class NegatedObject; class PolyCone; -class PolyPrism; class RevolvedPolygon; class ShapeBase; class SolidBase; @@ -67,7 +66,6 @@ template void to_json(nlohmann::json& j, JoinObjects const&); void to_json(nlohmann::json& j, NegatedObject const&); void to_json(nlohmann::json& j, PolyCone const&); -void to_json(nlohmann::json& j, PolyPrism const&); void to_json(nlohmann::json& j, RevolvedPolygon const&); void to_json(nlohmann::json& j, ShapeBase const&); void to_json(nlohmann::json& j, SolidBase const&); diff --git a/src/orange/orangeinp/PolySolid.cc b/src/orange/orangeinp/PolySolid.cc index a397972fd2..250dd26af7 100644 --- a/src/orange/orangeinp/PolySolid.cc +++ b/src/orange/orangeinp/PolySolid.cc @@ -267,118 +267,6 @@ void PolyCone::output(JsonPimpl* j) const to_json_pimpl(j, *this); } -//---------------------------------------------------------------------------// -/*! - * Return a polyprism *or* a simplified version for only a single segment. - */ -auto PolyPrism::or_solid(std::string&& label, - PolySegments&& segments, - EnclosedAzi&& enclosed, - int num_sides, - real_type orientation) -> SPConstObject -{ - if (segments.size() > 1) - { - // Can't be simplified: make a polyprism - return std::make_shared(std::move(label), - std::move(segments), - std::move(enclosed), - num_sides, - orientation); - } - - auto const [zlo, zhi] = segments.z(0); - real_type const hh = (zhi - zlo) / 2; - - auto const [rlo, rhi] = segments.outer(0); - if (rlo != rhi) - { - CELER_NOT_IMPLEMENTED("prism with different lo/hi radii"); - } - - Prism outer{num_sides, rlo, hh, orientation}; - std::optional inner; - if (segments.has_exclusion()) - { - auto const [rilo, rihi] = segments.inner(0); - if (rilo != rihi) - { - CELER_NOT_IMPLEMENTED("prism with different lo/hi radii"); - } - - inner = Prism{num_sides, rilo, hh, orientation}; - } - - auto result = PrismSolid::or_shape(std::move(label), - std::move(outer), - std::move(inner), - std::move(enclosed)); - if (real_type dz = (zhi + zlo) / 2; dz != 0) - { - result = std::make_shared(std::move(result), - Translation{{0, 0, dz}}); - } - - return result; -} - -//---------------------------------------------------------------------------// -/*! - * Build with label, axial segments, optional restriction. - */ -PolyPrism::PolyPrism(std::string&& label, - PolySegments&& segments, - EnclosedAzi&& enclosed, - int num_sides, - real_type orientation) - : PolySolidBase{std::move(label), std::move(segments), std::move(enclosed)} - , num_sides_{num_sides} - , orientation_{orientation} -{ - CELER_VALIDATE(num_sides_ >= 3, - << "degenerate prism (num_sides = " << num_sides_ << ')'); - CELER_VALIDATE(orientation_ >= 0 && orientation_ < 1, - << "orientation is out of bounds [0, 1): " << orientation_); -} - -//---------------------------------------------------------------------------// -/*! - * Construct a volume from this shape. - */ -NodeId PolyPrism::build(VolumeBuilder& vb) const -{ - auto build_prism = [this](Real2 const& radii, real_type hh) { - if (radii[0] != radii[1]) - { - CELER_NOT_IMPLEMENTED("prism with different lo/hi radii"); - } - return Prism{this->num_sides_, radii[0], hh, this->orientation_}; - }; - - // Construct union of all cone segments - NodeId result = construct_segments(*this, build_prism, vb); - - /*! - *\todo After adding short-circuit logic to evaluator, add "acceleration" - * structures here, e.g. "inside(inner cylinder) || [inside(outer cylinder) - * && (original union)]" - */ - - // Construct azimuthal truncation if applicable - result = construct_enclosed_angle(*this, vb, result); - - return result; -} - -//---------------------------------------------------------------------------// -/*! - * Write the shape to JSON. - */ -void PolyPrism::output(JsonPimpl* j) const -{ - to_json_pimpl(j, *this); -} - //---------------------------------------------------------------------------// } // namespace orangeinp } // namespace celeritas diff --git a/src/orange/orangeinp/PolySolid.hh b/src/orange/orangeinp/PolySolid.hh index ad77a09064..2a9e624b0a 100644 --- a/src/orange/orangeinp/PolySolid.hh +++ b/src/orange/orangeinp/PolySolid.hh @@ -172,48 +172,6 @@ class PolyCone final : public PolySolidBase void output(JsonPimpl*) const final; }; -//---------------------------------------------------------------------------// -/*! - * A series of stacked regular prisms or cone-y prisms. - * - * \todo This class is no longer used and is slated for removal. G4Polyhedra - * are now constructed using StackedExtrudedPolygon. - */ -class PolyPrism final : public PolySolidBase -{ - public: - // Return a polyprism *or* a simplified version for only a single segment - static SPConstObject or_solid(std::string&& label, - PolySegments&& segments, - EnclosedAzi&& enclosed, - int num_sides, - real_type orientation); - - // Build with label, axial segments, parameters, optional restriction - PolyPrism(std::string&& label, - PolySegments&& segments, - EnclosedAzi&& enclosed, - int num_sides, - real_type orientation); - - // Construct a volume from this object - NodeId build(VolumeBuilder&) const final; - - // Write the shape to JSON - void output(JsonPimpl*) const final; - - //// ACCESSORS //// - - //! Number of sides - int num_sides() const { return num_sides_; } - //! Rotation factor - real_type orientation() const { return orientation_; } - - private: - int num_sides_; - real_type orientation_; -}; - //---------------------------------------------------------------------------// } // namespace orangeinp } // namespace celeritas diff --git a/test/accel/CMakeLists.txt b/test/accel/CMakeLists.txt index 6ddd92f3f3..fdc247aef0 100644 --- a/test/accel/CMakeLists.txt +++ b/test/accel/CMakeLists.txt @@ -10,6 +10,7 @@ endif() include(CeleritasG4Tests) #-----------------------------------------------------------------------------# +# Library celeritas_get_g4libs(_g4_libs global track tracking run event intercoms digits_hits geometry tasking ) @@ -30,6 +31,14 @@ celeritas_setup_tests(SERIAL testcel_accel testcel_celeritas testcel_core Celeritas::accel ) +#-----------------------------------------------------------------------------# +# Variables + +set(_optical_rm_type "serial") +if(NOT (CELERITAS_CORE_GEO STREQUAL "Geant4")) + list(APPEND _optical_rm_type "mt") +endif() + #-----------------------------------------------------------------------------# # TESTS #-----------------------------------------------------------------------------# @@ -81,18 +90,23 @@ endfunction() foreach(_basename IN ITEMS UserActionIntegration TrackingManagerIntegration) # Add every combination of test for LArSphere.run - celeritas_add_integration_tests(${_basename} LarSphere run) + celeritas_add_integration_tests( + ${_basename} LarSphere run + ) # Do TestEm3 only with celeritas cpu/gpu and serial run manager - celeritas_add_integration_tests(${_basename} TestEm3 run + celeritas_add_integration_tests( + ${_basename} TestEm3 run OFFLOAD cpu gpu RMTYPE serial ) - # Test OpNovice + + # Test OpNovice with optical-friendly run manager types celeritas_add_integration_tests( ${_basename} OpNoviceOptical run OFFLOAD cpu gpu g4 - RMTYPE ${_optical_rm_type}) + RMTYPE ${_optical_rm_type} + ) endforeach() # Test that the UI integration works (and ignores if Celeritas is disabled) @@ -102,13 +116,14 @@ celeritas_add_integration_tests( RMTYPE serial mt ) -set(_optical_rm_type "serial") -if(NOT (CELERITAS_CORE_GEO STREQUAL "Geant4")) - list(APPEND _optical_rm_type "mt") -endif() +# Test that the UI integration fails if options aren't set +celeritas_add_integration_tests( + TrackingManagerIntegration LarSphere no_set_options + OFFLOAD cpu + RMTYPE serial mt +) -# Test with optical physics, except that currently trying to use Geant4 -# navigation for the optical tracks wreaks havoc +# Test optical celeritas_add_integration_tests( TrackingManagerIntegration LarSphereOptical run OFFLOAD cpu gpu g4 @@ -122,4 +137,25 @@ celeritas_add_integration_tests( RMTYPE ${_optical_rm_type} ) +# Test with optical track offloading +celeritas_add_integration_tests( + UserActionIntegration LarSphereOpticalTrackOffload run + OFFLOAD cpu gpu g4 + RMTYPE ${_optical_rm_type} +) + +# Test optical surface physics +celeritas_add_integration_tests( + TrackingManagerIntegration OpticalSurfaces run + OFFLOAD cpu gpu g4 + RMTYPE ${_optical_rm_type} +) + +# Test with optical physics offloading distributions +celeritas_add_integration_tests( + UserActionIntegration LarSphereOpticalTrackOffload run + OFFLOAD cpu gpu g4 + RMTYPE ${_optical_rm_type} +) + #-----------------------------------------------------------------------------# diff --git a/test/accel/IntegrationTestBase.cc b/test/accel/IntegrationTestBase.cc index 3ae1e56297..cca0ae1a9c 100644 --- a/test/accel/IntegrationTestBase.cc +++ b/test/accel/IntegrationTestBase.cc @@ -7,7 +7,6 @@ #include "IntegrationTestBase.hh" #include -#include #include #include #include @@ -29,6 +28,7 @@ #include "corecel/Config.hh" +#include "corecel/io/ColorUtils.hh" #include "corecel/io/Logger.hh" #include "corecel/io/StringUtils.hh" #include "corecel/math/ArrayUtils.hh" @@ -38,7 +38,6 @@ #include "corecel/sys/TypeDemangler.hh" #include "geocel/GeantUtils.hh" #include "geocel/ScopedGeantExceptionHandler.hh" -#include "geocel/ScopedGeantLogger.hh" #include "geocel/UnitUtils.hh" #include "celeritas/Quantities.hh" #include "celeritas/Units.hh" @@ -108,7 +107,6 @@ class RunAction final : public G4UserRunAction } } - // TODO: push exception onto a vector so we can do validation testing void handle_exception(std::exception_ptr ep) { try @@ -121,15 +119,14 @@ class RunAction final : public G4UserRunAction if (cstring_equal(d.which, "Geant4")) { // GeantExceptionHandler wrapped this error - FAIL() << "GeantExceptionHandler caught runtime error (" - << thread_label() << ',' << d.condition << "): from " - << d.file << ": " << d.what; + test_->caught_g4_runtime_error(e); } else { // Some other error - FAIL() << "Caught runtime error from " << thread_description() - << ": " << e.what(); + FAIL() << ansi_color('r') << "Caught runtime error from " + << thread_description() << ansi_color(' ') << ": " + << e.what(); } } catch (std::exception const& e) @@ -294,9 +291,6 @@ G4RunManager& IntegrationTestBase::run_manager() }; CELER_ASSERT(rm); - // Disable signal handling - disable_geant_signal_handler(); - // Set up detector rm->SetUserInitialization(new DetectorConstruction{ this->test_data_path("geocel", basename + ".gdml"), @@ -385,6 +379,39 @@ auto IntegrationTestBase::make_sens_det(std::string const&) -> UPSensDet return nullptr; } +//---------------------------------------------------------------------------// +/*! + * Fail when GeantExceptionHandler catches a celeritas RuntimeError. + */ +void IntegrationTestBase::caught_g4_runtime_error(RuntimeError const& e) +{ + // GeantExceptionHandler wrapped this error + auto const& d = e.details(); + FAIL() << ansi_color('R') << "GeantExceptionHandler caught runtime error (" + << thread_label() << ',' << d.condition << ")" << ansi_color(' ') + << ": from " << d.file << ": " << d.what; +} + +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// +void enable_optical_physics(IntegrationTestBase::PhysicsInput& phys_inp) +{ + // Set default optical physics + auto& optical = phys_inp.optical; + optical = {}; + EXPECT_TRUE(optical); + EXPECT_TRUE(optical.cherenkov); + EXPECT_TRUE(optical.scintillation); + + // Disable WLS which isn't yet working (reemission) in Celeritas + using WLSO = WavelengthShiftingOptions; + optical.wavelength_shifting = WLSO::deactivated(); + optical.wavelength_shifting2 = WLSO::deactivated(); +} + +//---------------------------------------------------------------------------// +// TEST PROBLEM MIXINS //---------------------------------------------------------------------------// /*! * Create physics list: default is EM only using make_physics_input. diff --git a/test/accel/IntegrationTestBase.hh b/test/accel/IntegrationTestBase.hh index 13876004af..3adcfcb706 100644 --- a/test/accel/IntegrationTestBase.hh +++ b/test/accel/IntegrationTestBase.hh @@ -100,6 +100,9 @@ class IntegrationTestBase : public ::celeritas::test::Test // Create THREAD-LOCAL sensitive detectors for an SD name in the GDML file virtual UPSensDet make_sens_det(std::string const& sd_name); + // Fail when GeantExceptionHandler catches a celeritas RuntimeError + virtual void caught_g4_runtime_error(RuntimeError const& e); + //!@{ //! \name Dispatch from user run/event actions virtual void BeginOfRunAction(G4Run const* run) = 0; @@ -109,6 +112,14 @@ class IntegrationTestBase : public ::celeritas::test::Test //!@} }; +//---------------------------------------------------------------------------// +// FREE FUNCTIONS +//---------------------------------------------------------------------------// + +void enable_optical_physics(IntegrationTestBase::PhysicsInput&); + +//---------------------------------------------------------------------------// +// TEST PROBLEM MIXINS //---------------------------------------------------------------------------// //! Generate LAr sphere geometry with 10 MeV electrons and functional hit call class LarSphereIntegrationMixin : virtual public IntegrationTestBase diff --git a/test/accel/TrackingManagerIntegration.test.cc b/test/accel/TrackingManagerIntegration.test.cc index 7b90f12238..c5504d43af 100644 --- a/test/accel/TrackingManagerIntegration.test.cc +++ b/test/accel/TrackingManagerIntegration.test.cc @@ -8,17 +8,23 @@ #include #include +#include +#include +#include #include #include #include #include #include +#include "corecel/StringSimplifier.hh" +#include "corecel/cont/Array.hh" #include "corecel/io/Logger.hh" #include "geocel/GeantUtils.hh" #include "geocel/UnitUtils.hh" #include "celeritas/ext/GeantParticleView.hh" #include "celeritas/global/CoreState.hh" +#include "celeritas/inp/Events.hh" #include "celeritas/optical/CoreState.hh" #include "celeritas/optical/OpticalCollector.hh" #include "celeritas/phys/PDGNumber.hh" @@ -29,9 +35,11 @@ #include "accel/detail/IntegrationSingleton.hh" #include "IntegrationTestBase.hh" +#include "TestMacros.hh" #include "celeritas_test.hh" using TMI = celeritas::TrackingManagerIntegration; +using namespace std::string_view_literals; namespace celeritas { @@ -50,6 +58,39 @@ constexpr bool using_surface_vg = CELERITAS_VECGEOM_SURFACE && CELERITAS_CORE_GEO == CELERITAS_CORE_GEO_VECGEOM; +/*! + * Count particle types. + */ +class CounterTrackingAction final : public G4UserTrackingAction +{ + public: + void PreUserTrackingAction(G4Track const* t) final + { + GeantParticleView particle{*t->GetParticleDefinition()}; + + if (particle.pdg() == pdg::electron()) + { + ++num_electrons_; + } + if (particle.pdg() == pdg::positron()) + { + ++num_positrons_; + } + else if (particle.is_optical_photon()) + { + ++num_photons_; + } + } + std::size_t num_photons() const { return num_photons_; } + std::size_t num_electrons() const { return num_electrons_; } + std::size_t num_positrons() const { return num_positrons_; } + + private: + std::size_t num_photons_{}; + std::size_t num_electrons_{}; + std::size_t num_positrons_{}; +}; + } // namespace //---------------------------------------------------------------------------// @@ -101,9 +142,12 @@ class TMITestBase : virtual public IntegrationTestBase std::function check_during_run_; }; +//---------------------------------------------------------------------------// +// LAR SPHERE (EM only) //---------------------------------------------------------------------------// class LarSphere : public LarSphereIntegrationMixin, public TMITestBase { + public: void BeginOfEventAction(G4Event const* event) override { if (event->GetEventID() == 1) @@ -133,6 +177,47 @@ class LarSphere : public LarSphereIntegrationMixin, public TMITestBase EXPECT_DOUBLE_EQ((event_id == 1 ? 10.0 : 1.0), step->GetTrack()->GetWeight()); } + + //! Check wrapped RuntimeError caught by GeantExceptionHandler + void caught_g4_runtime_error(RuntimeError const& e) override + { + if (!check_runtime_errors_) + { + // Let the base class manage and fail on the caught error + return TMITestBase::caught_g4_runtime_error(e); + } + CELER_EXPECT(std::string_view(e.details().which) == "Geant4"sv); + + static std::recursive_mutex exc_mutex; + std::lock_guard scoped_lock{exc_mutex}; + + static std::regex extract_error{R"(runtime error:\s*(.+?)(?:\n|$))"}; + std::smatch match; + std::string what = e.what(); + if (std::regex_search(what, match, extract_error)) + { + CELER_ASSERT(match.size() > 1); + exceptions_.push_back(match[1].str()); + } + else + { + exceptions_.push_back(std::move(what)); + } + } + + void TearDown() override + { + if (!exceptions_.empty()) + { + FAIL() << exceptions_.size() + << " runtime errors were caught but not checked"; + } + } + + //! Append caught exceptions in this local test rather than failing + bool check_runtime_errors_{false}; + //! Exceptions that were caught by this test suite's error handler + std::vector exceptions_; }; /*! @@ -211,58 +296,69 @@ TEST_F(LarSphere, run_ui) EXPECT_EQ(get_geant_num_threads(rm), check_count.load()); } -//---------------------------------------------------------------------------// -// LAR SPHERE WITH OPTICAL -//---------------------------------------------------------------------------// /*! - * Count particle types. - * - * \todo This is redundant with (but more "Geant4-like" than) - * \c GeantStepDiagnostic . + * Check that omitting the SetOptions call causes the expected errors. */ -class TrackingAction : public G4UserTrackingAction +TEST_F(LarSphere, no_set_options) { - public: - void PreUserTrackingAction(G4Track const* t) - { - GeantParticleView particle{*t->GetParticleDefinition()}; + auto& rm = this->run_manager(); + TMI::Instance(); + check_runtime_errors_ = true; - if (particle.pdg() == pdg::electron()) - { - ++num_electrons_; - } - if (particle.pdg() == pdg::positron()) - { - ++num_positrons_; - } - else if (particle.is_optical_photon()) - { - ++num_photons_; - } - } - std::size_t num_photons() const { return num_photons_; } - std::size_t num_electrons() const { return num_electrons_; } - std::size_t num_positrons() const { return num_positrons_; } + CELER_LOG(status) << "Run initialization"; + rm.Initialize(); + EXPECT_EQ(0, exceptions_.size()); + CELER_LOG(status) << "Run two events"; + rm.BeamOn(2); - private: - std::size_t num_photons_{}; - std::size_t num_electrons_{}; - std::size_t num_positrons_{}; -}; + std::vector expected_exceptions = { + "SetOptions or UI entries were not completely set before BeginRun", + }; + if (!G4Threading::IsMultithreadedApplication()) + { + // Geant4 still starts the first local event if an error happens during + // BeginOfRun + expected_exceptions.push_back( + R"(Celeritas was not initialized properly (maybe BeginOfRunAction was not called?))"); + } + EXPECT_VEC_EQ(expected_exceptions, exceptions_); + exceptions_.clear(); +} +//---------------------------------------------------------------------------// +// LAR SPHERE WITH OPTICAL +//---------------------------------------------------------------------------// /*! * Test the LarSphere, offloading both EM tracks *and* optical photons. */ class LarSphereOptical : public LarSphere { public: - PhysicsInput make_physics_input() const override; - PrimaryInput make_primary_input() const override; + PhysicsInput make_physics_input() const override + { + auto result = LarSphere::make_physics_input(); + enable_optical_physics(result); + return result; + } + + PrimaryInput make_primary_input() const override + { + auto result = LarSphereIntegrationMixin::make_primary_input(); + + result.shape = inp::PointDistribution{ + array_cast(from_cm({0.1, 0.1, 0}))}; + result.primaries_per_event = 1; + result.energy = inp::MonoenergeticDistribution{2}; // [MeV] + return result; + } + SetupOptions make_setup_options() override; + void EndOfRunAction(G4Run const* run) override; + UPTrackAction make_tracking_action() override { - auto result = std::make_unique(); + auto result = std::make_unique(); { // Store the raw pointer in the tracking_ vector using a static // mutex @@ -274,43 +370,9 @@ class LarSphereOptical : public LarSphere } private: - std::vector tracking_; + std::vector tracking_; }; -//---------------------------------------------------------------------------// -/*! - * Enable optical physics. - */ -auto LarSphereOptical::make_physics_input() const -> PhysicsInput -{ - auto result = LarSphereIntegrationMixin::make_physics_input(); - - // Set default optical physics - auto& optical = result.optical; - optical = {}; - EXPECT_TRUE(optical); - EXPECT_TRUE(optical.cherenkov); - EXPECT_TRUE(optical.scintillation); - - // Disable WLS which isn't yet working (reemission) in Celeritas - using WLSO = WavelengthShiftingOptions; - optical.wavelength_shifting = WLSO::deactivated(); - optical.wavelength_shifting2 = WLSO::deactivated(); - - return result; -} - -auto LarSphereOptical::make_primary_input() const -> PrimaryInput -{ - auto result = LarSphereIntegrationMixin::make_primary_input(); - - result.shape - = inp::PointDistribution{array_cast(from_cm({0.1, 0.1, 0}))}; - result.primaries_per_event = 1; - result.energy = inp::MonoenergeticDistribution{2}; // [MeV] - return result; -} - //---------------------------------------------------------------------------// /*! * Enable optical tracking. @@ -349,7 +411,8 @@ void LarSphereOptical::EndOfRunAction(G4Run const* run) EXPECT_EQ(is_running_events(), static_cast(local_transporter)); EXPECT_TRUE(shared_params) << "Celeritas was not enabled"; - auto const& optical_collector = shared_params.optical_collector(); + auto const& optical_collector + = shared_params.problem_loaded().optical_collector; EXPECT_TRUE(optical_collector) << "optical offloading was not enabled"; if (local_transporter && optical_collector) { @@ -416,6 +479,9 @@ TEST_F(LarSphereOptical, run) rm.BeamOn(2); } +//---------------------------------------------------------------------------// +// OPNOVICE +//---------------------------------------------------------------------------// /*! * Test the Op-Novice example, offloading optical photons. */ @@ -425,7 +491,7 @@ class OpNoviceOptical : public OpNoviceIntegrationMixin, public TMITestBase void EndOfRunAction(G4Run const* run) override; UPTrackAction make_tracking_action() override { - auto result = std::make_unique(); + auto result = std::make_unique(); { // Store the raw pointer in the tracking_ vector using a static // mutex @@ -437,7 +503,7 @@ class OpNoviceOptical : public OpNoviceIntegrationMixin, public TMITestBase } private: - std::vector tracking_; + std::vector tracking_; }; //---------------------------------------------------------------------------// @@ -459,7 +525,8 @@ void OpNoviceOptical::EndOfRunAction(G4Run const* run) EXPECT_EQ(is_running_events(), static_cast(local_transporter)); EXPECT_TRUE(shared_params) << "Celeritas was not enabled"; - auto const& optical_collector = shared_params.optical_collector(); + auto const& optical_collector + = shared_params.problem_loaded().optical_collector; EXPECT_TRUE(optical_collector) << "optical offloading was not enabled"; if (local_transporter && optical_collector) { @@ -526,6 +593,128 @@ TEST_F(OpNoviceOptical, run) rm.BeamOn(10); } +//---------------------------------------------------------------------------// +// OPTICAL SURFACES +//---------------------------------------------------------------------------// +/*! + * Test the LarSphere, offloading both EM tracks *and* optical photons. + */ +class OpticalSurfaces : public TMITestBase +{ + public: + std::string_view gdml_basename() const final { return "optical-surfaces"; } + PhysicsInput make_physics_input() const override + { + auto result = TMITestBase::make_physics_input(); + enable_optical_physics(result); + return result; + } + + PrimaryInput make_primary_input() const override; + SetupOptions make_setup_options() override; + void EndOfRunAction(G4Run const* run) override; +}; + +//---------------------------------------------------------------------------// +/*! + * Fire positrons through the liquid argon toward the detectors. + */ +auto OpticalSurfaces::make_primary_input() const -> PrimaryInput +{ + PrimaryInput result; + result.pdg = {pdg::positron()}; + result.shape + = inp::PointDistribution{array_cast(from_cm({30, 0, 0}))}; + result.angle = inp::MonodirectionalDistribution{{-1, 0, 0}}; + result.energy = inp::MonoenergeticDistribution{10}; // [MeV] + result.primaries_per_event = 1; + result.num_events = 4; // Overridden with BeamOn + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Enable optical tracking. + */ +auto OpticalSurfaces::make_setup_options() -> SetupOptions +{ + auto result = TMITestBase::make_setup_options(); + + result.sd.enabled = false; + result.optical = [] { + OpticalSetupOptions opt; + opt.capacity.tracks = 32768; + opt.capacity.generators = 32768 * 8; + opt.capacity.primaries = opt.capacity.generators; + return opt; + }(); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Test that the optical tracking loop completed correctly. + * + * - Generator counters show whether any photons are queued but not run + * - Accumulated stats show whether the state has run some photons + */ +void OpticalSurfaces::EndOfRunAction(G4Run const* run) +{ + auto& integration = detail::IntegrationSingleton::instance(); + if (integration.mode() == OffloadMode::enabled) + { + auto& local_transporter = integration.local_transporter(); + auto const& shared_params = integration.shared_params(); + + // Check that local/shared data is available before end of run + EXPECT_EQ(is_running_events(), static_cast(local_transporter)); + EXPECT_TRUE(shared_params) << "Celeritas was not enabled"; + + auto const& optical_collector + = shared_params.problem_loaded().optical_collector; + EXPECT_TRUE(optical_collector) << "optical offloading was not enabled"; + if (local_transporter && optical_collector) + { + // Use diagnostic methods to check counters + auto const& accum_stats + = optical_collector->optical_state(local_transporter.GetState()) + .accum(); + CELER_LOG_LOCAL(info) + << "Ran " << accum_stats.steps << " over " + << accum_stats.step_iters << " step iterations from " + << accum_stats.flushes << " flushes"; + EXPECT_GT(accum_stats.steps, 0); + EXPECT_GT(accum_stats.step_iters, 0); + EXPECT_GT(accum_stats.flushes, 0); + + auto& aux_state = local_transporter.GetState().aux(); + auto counts = optical_collector->buffer_counts(aux_state); + EXPECT_EQ(0, counts.buffer_size); //!< Pending generators + EXPECT_EQ(0, counts.num_pending); //!< Photons pending generation + EXPECT_EQ(0, counts.num_generated); //!< Photons generated + } + } + + // Continue cleanup and other checks at end of run + TMITestBase::EndOfRunAction(run); +} + +//---------------------------------------------------------------------------// +/*! + * Check that the test runs. + */ +TEST_F(OpticalSurfaces, run) +{ + auto& rm = this->run_manager(); + TMI::Instance().SetOptions(this->make_setup_options()); + + CELER_LOG(status) << "Run initialization"; + rm.Initialize(); + CELER_LOG(status) << "Run two events"; + rm.BeamOn(2); +} + //---------------------------------------------------------------------------// // TESTEM3 //---------------------------------------------------------------------------// diff --git a/test/accel/UserActionIntegration.test.cc b/test/accel/UserActionIntegration.test.cc index 50bbdc3af3..afd3d83fb1 100644 --- a/test/accel/UserActionIntegration.test.cc +++ b/test/accel/UserActionIntegration.test.cc @@ -7,6 +7,7 @@ #include "accel/UserActionIntegration.hh" #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include "corecel/math/ArrayUtils.hh" +#include "corecel/math/Quantity.hh" #include "geocel/ScopedGeantExceptionHandler.hh" #include "geocel/UnitUtils.hh" #include "geocel/g4/Convert.hh" @@ -290,6 +292,147 @@ TEST_F(LarSphereOpticalOffload, run) rm.BeamOn(2); } +//---------------------------------------------------------------------------// +// LAR SPHERE WITH OPTICAL TRACK OFFLOAD +//---------------------------------------------------------------------------// +class LSOOTrackingAction final : public G4UserTrackingAction +{ + public: + void PreUserTrackingAction(G4Track const* track) final; + std::size_t num_pushed() const { return num_pushed_; } + + private: + std::size_t num_pushed_{0}; +}; + +//---------------------------------------------------------------------------// +/*! + * Offload optical tracks. + */ +class LarSphereOpticalTrackOffload : public LarSphere +{ + public: + PhysicsInput make_physics_input() const override; + SetupOptions make_setup_options() override; + void EndOfRunAction(G4Run const* run) override; + UPTrackAction make_tracking_action() override + { + auto act = std::make_unique(); + tracking_.push_back(act.get()); + return act; + } + + private: + std::vector tracking_; +}; + +//---------------------------------------------------------------------------// +/*! + * Enable optical physics + */ +auto LarSphereOpticalTrackOffload::make_physics_input() const -> PhysicsInput +{ + auto result = LarSphereIntegrationMixin::make_physics_input(); + + // Set default optical physics + auto& optical = result.optical; + optical = {}; + + optical.cherenkov.stack_photons = true; + optical.scintillation.stack_photons = true; + + using WLSO = WavelengthShiftingOptions; + optical.wavelength_shifting = WLSO::deactivated(); + optical.wavelength_shifting2 = WLSO::deactivated(); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Enable optical tracking offloading. + */ +auto LarSphereOpticalTrackOffload::make_setup_options() -> SetupOptions +{ + auto result = LarSphereIntegrationMixin::make_setup_options(); + result.optical = [] { + OpticalSetupOptions opt; + opt.capacity.tracks = 32; + opt.capacity.generators = opt.capacity.tracks * 8; + opt.capacity.primaries = opt.capacity.tracks * 16; + opt.generator = inp::OpticalTrackOffload{}; + opt.offload_tracks = true; + + return opt; + }(); + + // Don't offload any particles + result.offload_particles = SetupOptions::VecG4PD{}; + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Tracking action for pushing optical tracks to Celeritas. + */ +void LSOOTrackingAction::PreUserTrackingAction(G4Track const* track) +{ + CELER_EXPECT(track); + + auto& singleton = detail::IntegrationSingleton::instance(); + auto const mode = singleton.shared_params().mode(); + if (mode == SharedParams::Mode::disabled) + return; + + // Optical track offload path + if (track->GetDefinition() == G4OpticalPhoton::Definition()) + { + // optical photon track offloading is enabled + auto& opt_local + = detail::IntegrationSingleton::local_optical_track_offload(); + if (opt_local) + { + ++num_pushed_; + auto* mutable_track = const_cast(track); + opt_local.Push(*mutable_track); + mutable_track->SetTrackStatus(fStopAndKill); + return; + } + } +} + +//---------------------------------------------------------------------------// +/*! + * Test that the optical track offload was successful. + */ +void LarSphereOpticalTrackOffload::EndOfRunAction(G4Run const* run) +{ + if (G4Threading::IsMasterThread()) + { + std::size_t pushed{0}; + for (auto* ta : tracking_) + { + pushed += ta->num_pushed(); + } + EXPECT_EQ(pushed, 150459); + } + + // Continue cleanup and other checks at end of run + LarSphere::EndOfRunAction(run); +} + +//---------------------------------------------------------------------------// +TEST_F(LarSphereOpticalTrackOffload, run) +{ + auto& rm = this->run_manager(); + rm.SetNumberOfThreads(2); + UAI::Instance().SetOptions(this->make_setup_options()); + + rm.Initialize(); + rm.BeamOn(1); +} + //---------------------------------------------------------------------------// // TEST EM3 //---------------------------------------------------------------------------// @@ -333,5 +476,139 @@ TEST_F(OpNoviceOptical, run) } //---------------------------------------------------------------------------// + +//---------------------------------------------------------------------------// +// LAR SPHERE WITH OPTICAL TRACK OFFLOAD +//---------------------------------------------------------------------------// +class LSOOTrackingAction final : public G4UserTrackingAction +{ + public: + void PreUserTrackingAction(G4Track const* track) final; + // std::size_t num_pushed() const { return num_pushed_; } + + private: + // std::size_t num_pushed_{0}; +}; + +//---------------------------------------------------------------------------// +/*! + * Offload optical tracks. + */ +class LarSphereOpticalTrackOffload : public LarSphere +{ + public: + PhysicsInput make_physics_input() const override; + SetupOptions make_setup_options() override; + void EndOfRunAction(G4Run const* run) override; + UPTrackAction make_tracking_action() override + { + auto act = std::make_unique(); + tracking_.push_back(act.get()); + return act; + } + + private: + std::vector tracking_; +}; + +//---------------------------------------------------------------------------// +/*! + * Enable optical physics + */ +auto LarSphereOpticalTrackOffload::make_physics_input() const -> PhysicsInput +{ + auto result = LarSphereIntegrationMixin::make_physics_input(); + + // Set default optical physics + auto& optical = result.optical; + optical = {}; + + optical.cherenkov.stack_photons = true; + optical.scintillation.stack_photons = true; + + using WLSO = WavelengthShiftingOptions; + optical.wavelength_shifting = WLSO::deactivated(); + optical.wavelength_shifting2 = WLSO::deactivated(); + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Enable optical tracking offloading. + */ +auto LarSphereOpticalTrackOffload::make_setup_options() -> SetupOptions +{ + auto result = LarSphereIntegrationMixin::make_setup_options(); + result.optical = [] { + OpticalSetupOptions opt; + opt.capacity.tracks = 32; + opt.capacity.generators = opt.capacity.tracks * 8; + opt.capacity.primaries = opt.capacity.tracks * 16; + opt.generator = inp::OpticalTrackOffload{}; + opt.offload_optical_tracks = true; + + return opt; + }(); + + // Don't offload any particles + result.offload_particles = SetupOptions::VecG4PD{}; + + return result; +} + +//---------------------------------------------------------------------------// +/*! + * Tracking action for pushing optical tracks to Celeritas. + */ +void LSOOTrackingAction::PreUserTrackingAction(G4Track const* track) +{ + CELER_EXPECT(track); + // Delegate to UserActionIntegration for standard handling + UAI::Instance().PreUserTrackingAction(const_cast(track)); + + // If UserActionIntegration killed it, don't process further + if (track->GetTrackStatus() == fStopAndKill) + { + return; + } +} + +//---------------------------------------------------------------------------// +/*! + * Test that the optical track offload was successful. + */ +void LarSphereOpticalTrackOffload::EndOfRunAction(G4Run const* run) +{ + auto& integration = detail::IntegrationSingleton::instance(); + auto& local = integration.local_offload(); + + auto* opt_offload = dynamic_cast(&local); + if (!G4Threading::IsMasterThread()) + { + if (opt_offload && opt_offload->Initialized()) + { + std::size_t pushed = opt_offload->num_pushed(); + + CELER_LOG(info) << "Total optical photon tracks pushed: " << pushed; + // Validate that we intercepted optical tracks + EXPECT_GT(pushed, 15048) << "should have pushed many optical " + "tracks"; + } + } + // Continue cleanup and other checks at end of run + LarSphere::EndOfRunAction(run); +} + +//---------------------------------------------------------------------------// +TEST_F(LarSphereOpticalTrackOffload, run) +{ + auto& rm = this->run_manager(); + rm.SetNumberOfThreads(1); + UAI::Instance().SetOptions(this->make_setup_options()); + + rm.Initialize(); + rm.BeamOn(1); +} } // namespace test } // namespace celeritas diff --git a/test/celeritas/CMakeLists.txt b/test/celeritas/CMakeLists.txt index ce6e8ab5d6..496619516c 100644 --- a/test/celeritas/CMakeLists.txt +++ b/test/celeritas/CMakeLists.txt @@ -40,8 +40,10 @@ endif() if(CELERITAS_CORE_GEO STREQUAL "ORANGE") set(_core_geo_libs testcel_orange Celeritas::orange) elseif(CELERITAS_CORE_GEO STREQUAL "VecGeom") + set(_needs_orange DISABLE) set(_core_geo_libs testcel_geocel ${VecGeom_LIBRARIES}) elseif(CELERITAS_CORE_GEO STREQUAL "Geant4") + set(_needs_orange DISABLE) set(_core_geo_libs testcel_geocel ${Geant4_LIBRARIES}) endif() @@ -82,6 +84,7 @@ celeritas_add_test_library(testcel_celeritas optical/OpticalMockTestBase.cc optical/OpticalTestBase.cc optical/InteractorHostTestBase.cc + optical/SurfacePhysicsIntegrationTestBase.cc optical/ValidationUtils.cc phys/InteractionIO.cc phys/InteractorHostTestBase.cc @@ -156,6 +159,8 @@ celeritas_add_test(em/xs/ScreeningFunctions.test.cc) celeritas_add_test(em/BetheHeitler.test.cc ${_needs_double}) celeritas_add_test(em/BetheBloch.test.cc ${_needs_double}) celeritas_add_test(em/BraggICRU73QO.test.cc ${_needs_double}) +celeritas_add_test(em/ElectroNuclear.test.cc ${_needs_double} + ${_needs_geant4_11}) celeritas_add_test(em/EPlusGG.test.cc ${_needs_double}) celeritas_add_test(em/GammaNuclear.test.cc ${_needs_double} ${_needs_geant4_11}) celeritas_add_test(em/KleinNishina.test.cc ${_needs_double}) @@ -290,13 +295,17 @@ else() set(_geo_mat_filter DISABLE) endif() +if(CELERITAS_VecGeom_SURFACE) + set(_fails_vgsurf DISABLE) +endif() + celeritas_add_test(geo/GeoMaterial.test.cc FILTER ${_geo_mat_filter} ${_optional_geant4_env}) #-----------------------------------------------------------------------------# # Global -if(CELERITAS_USE_Geant4 AND CELERITAS_REAL_TYPE STREQUAL "double") +if(CELERITAS_USE_Geant4) set(_along_step_filter "-Em3*:SimpleCms*:LeadBox*:Mock*" "Mock*" @@ -326,20 +335,9 @@ if(CELERITAS_USE_Geant4 AND CELERITAS_REAL_TYPE STREQUAL "double") "LeadBox*" ) endif() -elseif(CELERITAS_USE_Geant4) - set(_along_step_filter - "-Em3*:SimpleCms*:LeadBox*" - "Em3AlongStepTest.nofluct_nomsc" - "Em3AlongStepTest.fluct_nomsc" - "LeadBox*" - ) - set(_stepper_filter - "-TestEm*:LeadBox*" - "TestEm3Compton.*" - "TestEm3Msc.*" - "TestEm3MscNofluct.*" - "TestEm15FieldMsc.*" - ) + if(Geant4_VERSION VERSION_LESS 11.0) + list(REMOVE_ITEM _along_step_filter) + endif() else() set(_along_step_filter) set(_stepper_filter) @@ -410,7 +408,9 @@ celeritas_add_test(optical/ReflectionFormSampler.test.cc) celeritas_add_test(optical/Fresnel.test.cc) celeritas_add_test(optical/SurfacePhysicsUtils.test.cc) celeritas_add_test(optical/TrivialSurfaceInteractor.test.cc) -celeritas_add_test(optical/SurfacePhysicsIntegration.test.cc ${_needs_geant4}) +celeritas_add_test(optical/SurfacePhysicsInteractionIntegration.test.cc ${_needs_geant4} ${_fails_vgsurf}) +celeritas_add_test(optical/SurfacePhysicsRoughnessIntegration.test.cc + ${_needs_geant4}) #-----------------------------------------------------------------------------# # Mat diff --git a/test/celeritas/GlobalTestBase.cc b/test/celeritas/GlobalTestBase.cc index 6bb1609305..0fe9c84082 100644 --- a/test/celeritas/GlobalTestBase.cc +++ b/test/celeritas/GlobalTestBase.cc @@ -20,6 +20,7 @@ #include "corecel/io/OutputRegistry.hh" #include "corecel/random/params/RngParams.hh" #include "corecel/sys/ActionRegistry.hh" +#include "corecel/sys/Device.hh" #include "geocel/GeantGeoParams.hh" #include "geocel/SurfaceParams.hh" #include "geocel/VolumeParams.hh" @@ -27,6 +28,7 @@ #include "celeritas/ext/ScopedRootErrorHandler.hh" #include "celeritas/geo/CoreGeoParams.hh" #include "celeritas/global/CoreParams.hh" +#include "celeritas/inp/Control.hh" #include "celeritas/phys/GeneratorRegistry.hh" #include "celeritas/track/ExtendFromPrimariesAction.hh" #include "celeritas/track/StatusChecker.hh" @@ -185,12 +187,16 @@ optical::CoreParams::Input GlobalTestBase::optical_params_input() inp.rng = this->rng(); inp.surface = this->core()->surface(); inp.action_reg = this->optical_action_reg(); + inp.output_reg = this->core()->output_reg(); inp.gen_reg = std::make_shared(); + inp.aux_reg = this->core()->aux_reg(); inp.physics = this->optical_physics(); inp.sim = this->optical_sim(); inp.surface_physics = this->optical_surface_physics(); inp.cherenkov = this->cherenkov(); inp.scintillation = this->scintillation(); + inp.capacity = inp::OpticalStateCapacity::from_default( + celeritas::Device::num_devices()); CELER_ENSURE(inp); return inp; diff --git a/test/celeritas/data/four-steel-slabs.root-dump.json b/test/celeritas/data/four-steel-slabs.root-dump.json index 22948401fa..cba069f76c 100644 --- a/test/celeritas/data/four-steel-slabs.root-dump.json +++ b/test/celeritas/data/four-steel-slabs.root-dump.json @@ -569,6 +569,25 @@ }, "mucf_physics" : { "_typename" : "celeritas::inp::MucfPhysics", + "scalars" : { + "_typename" : "celeritas::inp::MucfScalars", + "protium" : { + "_typename" : "celeritas::Quantity", + "value_" : 0 + }, + "deuterium" : { + "_typename" : "celeritas::Quantity", + "value_" : 0 + }, + "tritium" : { + "_typename" : "celeritas::Quantity", + "value_" : 0 + }, + "liquid_hydrogen_density" : { + "_typename" : "celeritas::Quantity", + "value_" : 0 + } + }, "muon_energy_cdf" : { "_typename" : "celeritas::inp::Grid", "x" : [], diff --git a/test/celeritas/em/ElectroNuclear.test.cc b/test/celeritas/em/ElectroNuclear.test.cc new file mode 100644 index 0000000000..0589b3f166 --- /dev/null +++ b/test/celeritas/em/ElectroNuclear.test.cc @@ -0,0 +1,134 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/em/ElectroNuclear.test.cc +//---------------------------------------------------------------------------// +#include + +#include "celeritas_test_config.h" + +#include "corecel/cont/Range.hh" +#include "corecel/grid/NonuniformGridData.hh" +#include "celeritas/Quantities.hh" +#include "celeritas/em/interactor/ElectroNuclearInteractor.hh" +#include "celeritas/em/model/ElectroNuclearModel.hh" +#include "celeritas/em/xs/ElectroNuclearMicroXsCalculator.hh" +#include "celeritas/mat/MaterialTrackView.hh" +#include "celeritas/phys/InteractorHostTestBase.hh" +#include "celeritas/phys/MacroXsCalculator.hh" + +#include "celeritas_test.hh" + +namespace celeritas +{ +namespace test +{ +//---------------------------------------------------------------------------// +class ElectroNuclearTest : public InteractorHostTestBase +{ + protected: + using MevEnergy = units::MevEnergy; + + void SetUp() override + { + using namespace units; + + // Set up the default particle: 1000 MeV electron along +z direction + auto const& particles = *this->particle_params(); + this->set_inc_particle(pdg::electron(), MevEnergy{1000}); + this->set_inc_direction({0, 0, 1}); + + // Build the model with the default material + MaterialParams::Input mi; + mi.elements = { + {AtomicNumber{8}, AmuMass{15.999}, {}, Label{"O"}}, + {AtomicNumber{74}, AmuMass{183.84}, {}, Label{"W"}}, + {AtomicNumber{82}, AmuMass{207.2}, {}, Label{"Pb"}}, + }; + + mi.materials = { + {native_value_from(MolCcDensity{8.28}), + 293., + MatterState::solid, + {{ElementId{0}, 0.14}, {ElementId{1}, 0.4}, {ElementId{2}, 0.46}}, + Label{"PbWO4"}}}; + this->set_material_params(mi); + + model_ = std::make_shared( + ActionId{0}, particles, *this->material_params()); + + this->set_material("PbWO4"); + } + + protected: + std::shared_ptr model_; +}; + +//---------------------------------------------------------------------------// +// TESTS +//---------------------------------------------------------------------------// +TEST_F(ElectroNuclearTest, micro_xs) +{ + // Calculate the electro-nuclear microscopic (element) cross section + using XsCalculator = ElectroNuclearMicroXsCalculator; + + // Set the target element: Pb + ElementId el_id{2}; + + // Check the size of the element cross section data + HostCRef shared = model_->host_ref(); + + // Calculate the electro-nuclear cross section using parameterizated data + + NonuniformGridRecord grid = shared.micro_xs[el_id]; + EXPECT_EQ(grid.grid.size(), 300); + + // Expected microscopic cross section (units::BarnXs) in [100:1e+8] (MeV) + std::vector> const energy_xs + = {{200, 0.0076866011595330607}, + {500, 0.010594901781001968}, + {1e+3, 0.012850271316595725}, + {5e+3, 0.018062295361634773}, + {5e+4, 0.025265243621752368}, + {1e+6, 0.035225631593952887}, + {1e+7, 0.044179324718181687}, + {1e+8, 0.05492003274983899}}; + + for (auto i : range(energy_xs.size())) + { + XsCalculator calc_micro_xs(shared, MevEnergy{energy_xs[i].first}); + EXPECT_SOFT_EQ(calc_micro_xs(el_id).value(), energy_xs[i].second); + } +} + +TEST_F(ElectroNuclearTest, macro_xs) +{ + // Calculate the electro-nuclear macroscopic cross section + auto material = this->material_track().material_record(); + auto calc_xs = MacroXsCalculator( + model_->host_ref(), material); + + // Expected macroscopic cross section (\f$ cm^{-1} \f$)} in [100:1e+8](MeV) + std::vector> const energy_xs + = {{200, 0.03099484247367704}, + {500, 0.042848174635210838}, + {1e+3, 0.05204849281161035}, + {5e+3, 0.073325144774783371}, + {5e+4, 0.10275850385625593}, + {1e+6, 0.14353937451738991}, + {1e+7, 0.1802859228767722}, + {1e+8, 0.22446330065829731}}; + + for (auto i : range(energy_xs.size())) + { + EXPECT_SOFT_EQ(native_value_to( + calc_xs(MevEnergy{energy_xs[i].first})) + .value(), + energy_xs[i].second); + } +} + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace celeritas diff --git a/test/celeritas/ext/GeantImporter.test.cc b/test/celeritas/ext/GeantImporter.test.cc index 68022e0a32..a42578192a 100644 --- a/test/celeritas/ext/GeantImporter.test.cc +++ b/test/celeritas/ext/GeantImporter.test.cc @@ -2132,15 +2132,27 @@ TEST_F(OpticalSurfaces, surfaces) } //---------------------------------------------------------------------------// -TEST_F(MucfBox, run) +TEST_F(MucfBox, static_data) { auto const& mucf = this->imported_data().mucf_physics; EXPECT_TRUE(mucf); - static double const expected_muon_energy_cdf_y[] = {1, 1}; - EXPECT_EQ(2, mucf.muon_energy_cdf.x.size()); - EXPECT_VEC_EQ(expected_muon_energy_cdf_y, mucf.muon_energy_cdf.y); + auto average = [](inp::Grid::VecDbl const& data) -> double { + double sum{0}; + for (auto y : data) + { + sum += y; + } + return sum / data.size(); + }; + + static size_t const expected_muon_energy_cdf_size = 21; + EXPECT_EQ(expected_muon_energy_cdf_size, mucf.muon_energy_cdf.x.size()); + EXPECT_EQ(expected_muon_energy_cdf_size, mucf.muon_energy_cdf.y.size()); + EXPECT_SOFT_EQ(0.55157437567861023, average(mucf.muon_energy_cdf.x)); + EXPECT_SOFT_EQ(11.250286274435437, average(mucf.muon_energy_cdf.y)); + // Dummy data auto const& cycle_f0 = mucf.cycle_rates[0]; static double const expected_cycle_rate_f0_y[] = {2, 2}; EXPECT_TRUE(cycle_f0); diff --git a/test/celeritas/field/Integrators.test.cc b/test/celeritas/field/Integrators.test.cc index 02366c43c7..03f1ad1f3f 100644 --- a/test/celeritas/field/Integrators.test.cc +++ b/test/celeritas/field/Integrators.test.cc @@ -156,7 +156,7 @@ TEST_F(IntegratorsTest, host_helix) TEST_F(IntegratorsTest, host_classical_rk4) { // Construct a uniform magnetic field - UniformField field({0, 0, param.field_value}); + UniformField field(Real3{0, 0, param.field_value}); // Test the classical 4th order Runge-Kutta integrate this->run_integration(field); @@ -166,7 +166,7 @@ TEST_F(IntegratorsTest, host_classical_rk4) TEST_F(IntegratorsTest, host_dormand_prince_547) { // Construct a uniform magnetic field - UniformField field({0, 0, param.field_value}); + UniformField field(Real3{0, 0, param.field_value}); // Test the Dormand-Prince 547(M) integrate this->run_integration(field); diff --git a/test/celeritas/field/LinearPropagator.test.cc b/test/celeritas/field/LinearPropagator.test.cc index 825401a152..f81a7311a8 100644 --- a/test/celeritas/field/LinearPropagator.test.cc +++ b/test/celeritas/field/LinearPropagator.test.cc @@ -109,7 +109,7 @@ TEST_F(LinearPropagatorTest, simple_cms) LinearPropagator propagate(geo); // Move to result boundary (infinite max distance) - Propagation result = propagate(); + Propagation result = propagate(numeric_limits::infinity()); EXPECT_SOFT_EQ(20, to_cm(result.distance)); EXPECT_TRUE(result.boundary); geo.cross_boundary(); diff --git a/test/celeritas/global/AlongStep.test.cc b/test/celeritas/global/AlongStep.test.cc index 32373f710c..00ed3456a9 100644 --- a/test/celeritas/global/AlongStep.test.cc +++ b/test/celeritas/global/AlongStep.test.cc @@ -297,7 +297,7 @@ TEST_F(MockAlongStepTest, basic) } } -TEST_F(MockAlongStepFieldTest, TEST_IF_CELERITAS_DOUBLE(basic)) +TEST_F(MockAlongStepFieldTest, basic) { size_type num_tracks = 10; Input inp; @@ -305,12 +305,16 @@ TEST_F(MockAlongStepFieldTest, TEST_IF_CELERITAS_DOUBLE(basic)) { inp.energy = MevEnergy{0.1}; auto result = this->run(inp, num_tracks); - EXPECT_SOFT_EQ(0.087685148514851444, result.eloss); - EXPECT_SOFT_EQ(0.072154637489842119, result.displacement); - EXPECT_SOFT_EQ(-0.77818527618217903, result.angle); - EXPECT_SOFT_EQ(1.1701381163128199e-11, result.time); - EXPECT_SOFT_EQ(0.14614191419141928, result.step); - EXPECT_SOFT_EQ(0.00013152772277225111, result.mfp); + + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(0.087685148514851444, result.eloss); + EXPECT_SOFT_EQ(0.072154637489842119, result.displacement); + EXPECT_SOFT_EQ(-0.77818527618217903, result.angle); + EXPECT_SOFT_EQ(1.1701381163128199e-11, result.time); + EXPECT_SOFT_EQ(0.14614191419141928, result.step); + EXPECT_SOFT_EQ(0.00013152772277225111, result.mfp); + } EXPECT_SOFT_EQ(1, result.alive); EXPECT_EQ("eloss-range", result.action); } @@ -319,11 +323,14 @@ TEST_F(MockAlongStepFieldTest, TEST_IF_CELERITAS_DOUBLE(basic)) inp.position = {0, 0, 7}; // Outside top sphere, heading out inp.phys_mfp = 100; auto result = this->run(inp, num_tracks); - EXPECT_SOFT_EQ(0.001, result.eloss); - EXPECT_SOFT_NEAR(0.0036768333578785931, result.displacement, 1e-10); - EXPECT_SOFT_NEAR(0.65590801657964626, result.angle, 1e-10); - EXPECT_SOFT_EQ(6.9431339225049422e-10, result.time); - EXPECT_SOFT_EQ(0.930177246841563, result.step); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(0.001, result.eloss); + EXPECT_SOFT_NEAR(0.0036768333578785931, result.displacement, 1e-10); + EXPECT_SOFT_NEAR(0.65590801657964626, result.angle, 1e-10); + EXPECT_SOFT_EQ(6.9431339225049422e-10, result.time); + EXPECT_SOFT_EQ(0.930177246841563, result.step); + } EXPECT_SOFT_EQ(0, result.mfp); EXPECT_SOFT_EQ(0, result.alive); EXPECT_EQ("eloss-range", result.action); @@ -494,7 +501,10 @@ TEST_F(Em3AlongStepTest, msc_nofluct_finegrid) inp.phys_mfp = 0.469519866261640; auto result = this->run(inp, num_tracks); // Distance to interaction = 0.0499189990540797 - EXPECT_SOFT_NEAR(0.049721747266950993, result.step, 1e-8); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_NEAR(0.049721747266950993, result.step, 1e-8); + } EXPECT_EQ("geo-boundary", result.action); } } @@ -581,6 +591,10 @@ TEST_F(SimpleCmsFieldVolAlongStepTest, msc_field) { GTEST_SKIP() << "Not using reference RNG"; } + if (CELERITAS_REAL_TYPE != CELERITAS_REAL_TYPE_DOUBLE) + { + GTEST_SKIP() << "Not using double precision"; + } EXPECT_SOFT_NEAR(0.28064807889290933, result.displacement, tol); EXPECT_SOFT_NEAR(0.68629076604678063, result.angle, tol); @@ -628,7 +642,10 @@ TEST_F(SimpleCmsAlongStepTest, msc_field) auto result = this->run(inp, num_tracks); - EXPECT_SOFT_EQ(2.7199323076809536, result.step); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(2.7199323076809536, result.step); + } EXPECT_EQ(0, result.eloss); EXPECT_EQ(0, result.mfp); EXPECT_EQ("geo-propagation-limit", result.action); @@ -648,6 +665,10 @@ TEST_F(SimpleCmsAlongStepTest, msc_field) { GTEST_SKIP() << "Not using reference RNG"; } + if (CELERITAS_REAL_TYPE != CELERITAS_REAL_TYPE_DOUBLE) + { + GTEST_SKIP() << "Not using double precision"; + } EXPECT_SOFT_NEAR(0.28057298212898418, result.displacement, tol); EXPECT_SOFT_NEAR(0.6882027184831665, result.angle, tol); EXPECT_SOFT_NEAR(0.33775753626703175, result.step, tol); @@ -676,7 +697,7 @@ TEST_F(SimpleCmsAlongStepTest, msc_field_finegrid) { bpd_ = 56; - size_type num_tracks = 1024; + size_type num_tracks = 8; Input inp; { SCOPED_TRACE("range-limited electron in field near boundary"); @@ -694,14 +715,23 @@ TEST_F(SimpleCmsAlongStepTest, msc_field_finegrid) auto result = this->run(inp, num_tracks); if (is_ci_build()) { - // Range = 6.4161473386016025e-06 - EXPECT_SOFT_EQ(6.4161473386016025e-06, result.step); + constexpr double range = 6.4161473386016025e-06; + EXPECT_SOFT_EQ(range, result.step); } - else + else if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) { EXPECT_SOFT_EQ(inp.energy.value(), result.eloss); } - EXPECT_EQ("eloss-range", result.action); + + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_EQ("eloss-range", result.action); + } + else + { + // Step is too small for single precision + EXPECT_EQ("tracking-cut", result.action); + } EXPECT_REAL_EQ(0, result.alive); } } @@ -709,11 +739,6 @@ TEST_F(SimpleCmsAlongStepTest, msc_field_finegrid) // Test nearly tangent value nearly on the boundary TEST_F(SimpleCmsRZFieldAlongStepTest, msc_rzfield) { - if (CELERITAS_REAL_TYPE != CELERITAS_REAL_TYPE_DOUBLE) - { - GTEST_SKIP() << "This edge case only occurs with double"; - } - size_type num_tracks = 128; Input inp; { @@ -726,9 +751,12 @@ TEST_F(SimpleCmsRZFieldAlongStepTest, msc_rzfield) -0.0391118941072485030}; auto result = this->run(inp, num_tracks); - EXPECT_SOFT_EQ(0.55155207893668967, result.displacement); - EXPECT_SOFT_NEAR(0.095305171122713847, result.angle, 1e-11); - EXPECT_EQ("geo-propagation-limit", result.action); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(0.55155207893668967, result.displacement); + EXPECT_SOFT_NEAR(0.095305171122713847, result.angle, 1e-11); + EXPECT_EQ("geo-propagation-limit", result.action); + } } } @@ -805,6 +833,69 @@ TEST_F(LeadBoxAlongStepTest, position_change) EXPECT_EQ("eloss-range", result.action); } } + +TEST_F(LeadBoxAlongStepTest, mfp_steps) +{ + size_type num_tracks = 128; + Input inp; + inp.particle_id = this->particle()->find(pdg::electron()); + inp.energy = MevEnergy{10000}; + inp.direction = {1, 0, 0}; + inp.position = {0, 0, 0}; + inp.phys_mfp = 1; + { + SCOPED_TRACE("near-zero mfp"); + inp.phys_mfp = 1e-20; + auto result = this->run(inp, num_tracks); + EXPECT_SOFT_EQ(0, result.eloss); + EXPECT_SOFT_EQ(1, result.angle); + EXPECT_SOFT_EQ(0, result.mfp); + EXPECT_SOFT_EQ(1, result.alive); + EXPECT_EQ("physics-discrete-select", result.action); + } + { + SCOPED_TRACE("tiny mfp"); + inp.phys_mfp = 1e-6; + auto result = this->run(inp, num_tracks); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(6.9164161686786e-07, result.eloss); + EXPECT_SOFT_EQ(1, result.angle); + EXPECT_SOFT_EQ(1.6943121845301e-18, result.time); + EXPECT_SOFT_EQ(5.0794201375653e-08, result.step); + } + EXPECT_SOFT_EQ(0, result.mfp); + EXPECT_SOFT_EQ(1, result.alive); + EXPECT_EQ("physics-discrete-select", result.action); + } + { + SCOPED_TRACE("single mfp"); + inp.phys_mfp = 1; + auto result = this->run(inp, num_tracks); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(0.68234478660682, result.eloss); + } + EXPECT_SOFT_EQ(0, result.mfp); + EXPECT_SOFT_EQ(1, result.alive); + EXPECT_EQ("physics-discrete-select", result.action); + } + { + SCOPED_TRACE("large mfp"); + inp.phys_mfp = 1e6; + auto result = this->run(inp, num_tracks); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + { + EXPECT_SOFT_EQ(2001.409778094, result.eloss); + EXPECT_SOFT_EQ(4.9030072611395e-09, result.time); + EXPECT_SOFT_EQ(146.988459649, result.step); + EXPECT_SOFT_EQ(2893.8039317112, result.mfp); + } + EXPECT_SOFT_EQ(1, result.alive); + EXPECT_EQ("eloss-range", result.action); + } +} + //---------------------------------------------------------------------------// } // namespace test } // namespace celeritas diff --git a/test/celeritas/global/Stepper.test.cc b/test/celeritas/global/Stepper.test.cc index 43d7626a64..0980a02993 100644 --- a/test/celeritas/global/Stepper.test.cc +++ b/test/celeritas/global/Stepper.test.cc @@ -372,8 +372,8 @@ TEST_F(StepperOrderTest, warm_up) EXPECT_EQ(0, dumstate.action_order.size()); step.warm_up(); - EXPECT_EQ(0, step.state().counters().num_active); - EXPECT_EQ(0, step.state().counters().num_alive); + EXPECT_EQ(0, step.state().sync_get_counters().num_active); + EXPECT_EQ(0, step.state().sync_get_counters().num_alive); static char const* const expected_action_order[] = {"user_start", "user_pre", "user_post"}; diff --git a/test/celeritas/optical/Generator.test.cc b/test/celeritas/optical/Generator.test.cc index 05578a4876..ae74aad2fb 100644 --- a/test/celeritas/optical/Generator.test.cc +++ b/test/celeritas/optical/Generator.test.cc @@ -143,7 +143,7 @@ TEST_F(LArSphereGeneratorTest, primary_generator) inp.angle = inp::IsotropicDistribution{}; inp.shape = inp::PointDistribution{{0, 0, 0}}; auto generate = optical::PrimaryGeneratorAction::make_and_insert( - *this->core(), *this->optical_params(), std::move(inp)); + *this->optical_params(), std::move(inp)); this->build_transporter(); this->build_state(4096); @@ -180,7 +180,7 @@ TEST_F(LArSphereGeneratorTest, TEST_IF_CELER_DEVICE(device_primary_generator)) inp.angle = inp::MonodirectionalDistribution{{1, 0, 0}}; inp.shape = inp::UniformBoxDistribution{{-10, -10, -10}, {10, 10, 10}}; auto generate = optical::PrimaryGeneratorAction::make_and_insert( - *this->core(), *this->optical_params(), std::move(inp)); + *this->optical_params(), std::move(inp)); this->build_transporter(); this->build_state(16384); @@ -220,7 +220,7 @@ TEST_F(LArSphereGeneratorTest, direct_generator) 0, ImplVolumeId{0}}); auto generate = optical::DirectGeneratorAction::make_and_insert( - *this->core(), *this->optical_params()); + *this->optical_params()); this->build_transporter(); this->build_state(32); @@ -253,7 +253,7 @@ TEST_F(LArSphereGeneratorTest, generator) // Create optical action to generate Cherenkov and scintillation photons size_type capacity = 512; auto generate = optical::GeneratorAction::make_and_insert( - *this->core(), *this->optical_params(), capacity); + *this->optical_params(), capacity); this->build_transporter(); this->build_state(4096); @@ -312,7 +312,7 @@ TEST_F(LArSphereGeneratorTest, TEST_IF_CELER_DEVICE(device_generator)) // Create optical action to generate Cherenkov and scintillation photons size_type capacity = 4096; auto generate = optical::GeneratorAction::make_and_insert( - *this->core(), *this->optical_params(), capacity); + *this->optical_params(), capacity); this->build_transporter(); this->build_state(16384); diff --git a/test/celeritas/optical/RoughnessCalculator.test.cc b/test/celeritas/optical/RoughnessCalculator.test.cc index f4377bf7f7..b8f833da5c 100644 --- a/test/celeritas/optical/RoughnessCalculator.test.cc +++ b/test/celeritas/optical/RoughnessCalculator.test.cc @@ -137,7 +137,7 @@ TEST_F(RoughnessSamplerTest, gaussian) // A "very rough" crystal in the UNIFIED paper has sigma_alpha of 0.2053 // (note that the paper gives the value in degrees), having at most a // deflection angle cosine of ~0.76 (40 degrees) - for (real_type sigma_alpha : {0.01, 0.05, 0.1, 0.2053, 0.4}) + for (real_type sigma_alpha : {0.01, 0.05, 0.1, 0.2053, 0.4, 0.6}) { GaussianRoughnessSampler sample_normal{normal, sigma_alpha}; @@ -149,11 +149,12 @@ TEST_F(RoughnessSamplerTest, gaussian) } static SampledHistogram const expected[] = { - {{0, 0, 0, 0, 5}, 21.7884}, - {{0, 0, 0, 0, 5}, 21.9014}, - {{0, 0, 0, 0, 5}, 21.9502}, - {{0, 0, 0, 0.034, 4.966}, 20.192}, - {{0.0105, 0.051, 0.235, 0.971, 3.7325}, 15.1376}, + {{0, 0, 0, 0, 5}, 21.8074}, + {{0, 0, 0, 0, 5}, 22.0256}, + {{0, 0, 0, 0, 5}, 22.4858}, + {{0, 0, 0.0005, 0.037, 4.9625}, 22.3334}, + {{0.011, 0.051, 0.2305, 0.967, 3.7405}, 15.1088}, + {{0.174, 0.366, 0.7145, 1.298, 2.4475}, 11.5844}, }; if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) { diff --git a/test/celeritas/optical/SurfacePhysics.test.cc b/test/celeritas/optical/SurfacePhysics.test.cc index aafb0894f6..b5035fcd84 100644 --- a/test/celeritas/optical/SurfacePhysics.test.cc +++ b/test/celeritas/optical/SurfacePhysics.test.cc @@ -342,8 +342,8 @@ TEST_F(SurfacePhysicsTest, init_params) SurfaceOrderArray> expected_model_names; expected_model_names[SurfacePhysicsOrder::roughness] = { "roughness-polished", - "smear", - "gaussian", + "roughness-smear", + "roughness-gaussian", }; expected_model_names[SurfacePhysicsOrder::reflectivity] = { "grid", diff --git a/test/celeritas/optical/SurfacePhysicsIntegration.test.cc b/test/celeritas/optical/SurfacePhysicsIntegration.test.cc deleted file mode 100644 index 86d2c43240..0000000000 --- a/test/celeritas/optical/SurfacePhysicsIntegration.test.cc +++ /dev/null @@ -1,558 +0,0 @@ -//------------------------------- -*- C++ -*- -------------------------------// -// Copyright Celeritas contributors: see top-level COPYRIGHT file for details -// SPDX-License-Identifier: (Apache-2.0 OR MIT) -//---------------------------------------------------------------------------// -//! \file celeritas/optical/SurfacePhysicsIntegration.test.cc -//---------------------------------------------------------------------------// -#include - -#include "corecel/Config.hh" - -#include "corecel/cont/ArrayIO.hh" -#include "corecel/data/AuxInterface.hh" -#include "corecel/data/AuxParamsRegistry.hh" -#include "corecel/data/AuxStateVec.hh" -#include "corecel/sys/ActionGroups.hh" -#include "corecel/sys/ActionRegistry.hh" -#include "corecel/sys/KernelLauncher.hh" -#include "geocel/UnitUtils.hh" -#include "celeritas/GeantTestBase.hh" -#include "celeritas/ext/GeantImporter.hh" -#include "celeritas/global/CoreParams.hh" -#include "celeritas/optical/CoreParams.hh" -#include "celeritas/optical/CoreState.hh" -#include "celeritas/optical/CoreTrackView.hh" -#include "celeritas/optical/TrackInitializer.hh" -#include "celeritas/optical/Transporter.hh" -#include "celeritas/optical/action/ActionLauncher.hh" -#include "celeritas/optical/gen/DirectGeneratorAction.hh" -#include "celeritas/optical/gen/OffloadData.hh" -#include "celeritas/optical/surface/SurfacePhysicsParams.hh" -#include "celeritas/phys/GeneratorRegistry.hh" -#include "celeritas/track/CoreStateCounters.hh" -#include "celeritas/track/TrackFunctors.hh" -#include "celeritas/track/Utils.hh" - -#include "celeritas_test.hh" - -namespace celeritas -{ -namespace optical -{ -namespace test -{ -/*! - * Reference results: - * - Double precision - * - Orange geometry (requires valid surface normals and relocation on - * boundary) - */ -constexpr bool reference_configuration - = ((CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) - && (CELERITAS_CORE_GEO == CELERITAS_CORE_GEO_ORANGE) - && (CELERITAS_CORE_RNG == CELERITAS_CORE_RNG_XORWOW)); - -using namespace ::celeritas::test; -//---------------------------------------------------------------------------// -/*! - * Counters for photon status after a run at a single angle. - */ -struct CollectResults -{ - size_type num_absorbed{0}; - size_type num_failed{0}; - size_type num_reflected{0}; - size_type num_refracted{0}; - - //! Clear counters - void reset() - { - num_absorbed = 0; - num_failed = 0; - num_reflected = 0; - num_refracted = 0; - } - - //! Score track - void operator()(CoreTrackView const& track) - { - if (track.sim().status() == TrackStatus::alive) - { - auto vol = track.geometry().volume_instance_id(); - if (vol == VolumeInstanceId{1}) - { - num_reflected++; - return; - } - else if (vol == VolumeInstanceId{2}) - { - num_refracted++; - return; - } - } - else if (track.sim().status() == TrackStatus::killed) - { - num_absorbed++; - return; - } - - num_failed++; - } -}; - -//---------------------------------------------------------------------------// -/*! - * Hook into the stepping loop to score and kill optical tracks that finish - * doing a surface crossing. - */ -class CollectResultsAction final : public OpticalStepActionInterface, - public ConcreteAction -{ - public: - explicit CollectResultsAction(ActionId aid, CollectResults& results) - : ConcreteAction(aid, "collect-results", "collect test results") - , results_(results) - { - } - - void step(CoreParams const& params, CoreStateHost& state) const final - { - for (auto tid : range(TrackSlotId{state.size()})) - { - CoreTrackView track(params.host_ref(), state.ref(), tid); - auto sim = track.sim(); - if (this->is_post_boundary(track) - || this->is_absorbed_on_boundary(track)) - { - results_(track); - sim.status(TrackStatus::killed); - } - } - } - - void step(CoreParams const&, CoreStateDevice&) const final - { - CELER_NOT_IMPLEMENTED("not collecting on device"); - } - - StepActionOrder order() const final { return StepActionOrder::post; } - - private: - //! Whether the track finished a boundary crossing - inline bool is_post_boundary(CoreTrackView const& track) const - { - return AppliesValid{}(track) - && track.sim().post_step_action() - == track.surface_physics().scalars().post_boundary_action; - } - - //! Whether the track was absorbed during a boundary crossing - inline bool is_absorbed_on_boundary(CoreTrackView const& track) const - { - return track.sim().status() == TrackStatus::killed - && track.sim().post_step_action() - == track.surface_physics().scalars().surface_stepping_action; - } - - CollectResults& results_; -}; - -//---------------------------------------------------------------------------// -/*! - * Counter results for a series of runs at different angles. - */ -struct SurfaceTestResults -{ - std::vector num_absorbed; - std::vector num_reflected; - std::vector num_refracted; -}; - -//---------------------------------------------------------------------------// -// TEST CHASSIS -//---------------------------------------------------------------------------// - -class SurfacePhysicsIntegrationTest : public GeantTestBase -{ - public: - std::string_view gdml_basename() const override { return "optical-box"; } - - GeantPhysicsOptions build_geant_options() const override - { - auto result = GeantTestBase::build_geant_options(); - result.optical = {}; - CELER_ENSURE(result.optical); - return result; - } - - GeantImportDataSelection build_import_data_selection() const override - { - auto result = GeantTestBase::build_import_data_selection(); - result.processes |= GeantImportDataSelection::optical; - return result; - } - - std::vector select_optical_models() const override - { - return {IMC::absorption}; - } - - void SetUp() override {} - - virtual void setup_surface_models(inp::SurfacePhysics&) const = 0; - - SPConstOpticalSurfacePhysics build_optical_surface_physics() override - { - inp::SurfacePhysics input; - - this->setup_surface_models(input); - - // Default surface - - PhysSurfaceId phys_surface = [&] { - size_type num_surfaces = 0; - for (auto const& mats : input.materials) - { - num_surfaces += mats.size() + 1; - } - return PhysSurfaceId(num_surfaces); - }(); - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - input.interaction.trivial.emplace(phys_surface, - TrivialInteractionMode::absorb); - - return std::make_shared( - this->optical_action_reg().get(), input); - } - - void build_state(size_type num_tracks) - { - auto state = std::make_shared>( - *this->optical_params(), StreamId{0}, num_tracks); - state->aux() = std::make_shared( - *this->core()->aux_reg(), MemSpace::host, StreamId{0}, num_tracks); - state_ = state; - } - - void make_collector() - { - auto& reg = *this->optical_params()->action_reg(); - auto collector - = std::make_shared(reg.next_id(), collect_); - reg.insert(collector); - } - - void build_transporter() - { - Transporter::Input inp; - inp.params = this->optical_params(); - transport_ = std::make_shared(std::move(inp)); - } - - //! Run over a set of angles and collect the results - SurfaceTestResults run(std::vector const& angles) - { - auto generate = DirectGeneratorAction::make_and_insert( - *this->core(), *this->optical_params()); - - this->make_collector(); - this->build_transporter(); - this->build_state(128); - - SurfaceTestResults results; - for (auto deg_angle : angles) - { - collect_.reset(); - - auto angle = deg_angle * constants::pi / 180; - real_type sin_theta = std::sin(angle); - real_type cos_theta = std::cos(angle); - - std::vector inits( - 100, - TrackInitializer{units::MevEnergy{3e-6}, - from_cm(Real3{0, 49, 0}), - Real3{sin_theta, cos_theta, 0}, - Real3{0, 0, 1}, - 0, - ImplVolumeId{0}}); - - generate->insert(*state_, make_span(inits)); - - (*transport_)(*state_); - - EXPECT_EQ(0, collect_.num_failed); - results.num_absorbed.push_back(collect_.num_absorbed); - results.num_reflected.push_back(collect_.num_reflected); - results.num_refracted.push_back(collect_.num_refracted); - } - - return results; - } - - protected: - std::shared_ptr> state_; - std::shared_ptr aux_; - std::shared_ptr transport_; - CollectResults collect_; -}; - -//---------------------------------------------------------------------------// -class SurfacePhysicsIntegrationBackscatterTest - : public SurfacePhysicsIntegrationTest -{ - public: - void setup_surface_models(inp::SurfacePhysics& input) const final - { - PhysSurfaceId phys_surface{0}; - - // center-top surface - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - - // Only back-scattering - - input.interaction.trivial.emplace(phys_surface, - TrivialInteractionMode::backscatter); - } -}; - -//---------------------------------------------------------------------------// -class SurfacePhysicsIntegrationAbsorbTest : public SurfacePhysicsIntegrationTest -{ - public: - void setup_surface_models(inp::SurfacePhysics& input) const final - { - PhysSurfaceId phys_surface{0}; - - // center-top surface - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - - // Only absorption - - input.interaction.trivial.emplace(phys_surface, - TrivialInteractionMode::absorb); - } -}; - -//---------------------------------------------------------------------------// -class SurfacePhysicsIntegrationTransmitTest - : public SurfacePhysicsIntegrationTest -{ - public: - void setup_surface_models(inp::SurfacePhysics& input) const final - { - PhysSurfaceId phys_surface{0}; - - // center-top surface - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - - // Only transmission - - input.interaction.trivial.emplace(phys_surface, - TrivialInteractionMode::transmit); - } -}; - -//---------------------------------------------------------------------------// -class SurfacePhysicsIntegrationFresnelTest - : public SurfacePhysicsIntegrationTest -{ - public: - void setup_surface_models(inp::SurfacePhysics& input) const final - { - PhysSurfaceId phys_surface{0}; - - // center-top surface - - input.materials.push_back({}); - input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); - input.reflectivity.fresnel.emplace(phys_surface, - inp::FresnelReflection{}); - - // Fresnel refraction / reflection - - input.interaction.dielectric.emplace( - phys_surface, - inp::DielectricInteraction::from_dielectric( - inp::ReflectionForm::from_spike())); - } -}; - -//---------------------------------------------------------------------------// -// TESTS -//---------------------------------------------------------------------------// -// Only back-scattering -TEST_F(SurfacePhysicsIntegrationBackscatterTest, backscatter) -{ - if (reference_configuration) - { - std::vector angles{0, 30, 60}; - auto result = this->run(angles); - - SurfaceTestResults expected; - expected.num_reflected = {100, 100, 100}; - expected.num_refracted = {0, 0, 0}; - expected.num_absorbed = {0, 0, 0}; - - EXPECT_EQ(expected.num_reflected, result.num_reflected); - EXPECT_EQ(expected.num_refracted, result.num_refracted); - EXPECT_EQ(expected.num_absorbed, result.num_absorbed); - } -} - -//---------------------------------------------------------------------------// -// Only absorption -TEST_F(SurfacePhysicsIntegrationAbsorbTest, absorb) -{ - if (reference_configuration) - { - std::vector angles{0, 30, 60}; - auto result = this->run(angles); - - SurfaceTestResults expected; - expected.num_refracted = {0, 0, 0}; - expected.num_reflected = {0, 0, 0}; - expected.num_absorbed = {100, 100, 100}; - - EXPECT_EQ(expected.num_reflected, result.num_reflected); - EXPECT_EQ(expected.num_refracted, result.num_refracted); - EXPECT_EQ(expected.num_absorbed, result.num_absorbed); - } -} - -//---------------------------------------------------------------------------// -// Only transmission -TEST_F(SurfacePhysicsIntegrationTransmitTest, transmit) -{ - if (reference_configuration) - { - std::vector angles{0, 30, 60}; - auto result = this->run(angles); - - SurfaceTestResults expected; - expected.num_refracted = {100, 100, 100}; - expected.num_reflected = {0, 0, 0}; - expected.num_absorbed = {0, 0, 0}; - - EXPECT_EQ(expected.num_reflected, result.num_reflected); - EXPECT_EQ(expected.num_refracted, result.num_refracted); - EXPECT_EQ(expected.num_absorbed, result.num_absorbed); - } -} - -//---------------------------------------------------------------------------// -// Fresnel reflection / refraction -TEST_F(SurfacePhysicsIntegrationFresnelTest, fresnel) -{ - if (reference_configuration) - { - std::vector angles{ - 0, - 10, - 20, - 30, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 60, - 70, - 80, - }; - - auto result = this->run(angles); - - static unsigned int const expected_num_absorbed[] = { - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - }; - static unsigned int const expected_num_reflected[] = { - 2u, - 0u, - 3u, - 4u, - 15u, - 11u, - 9u, - 17u, - 18u, - 34u, - 27u, - 42u, - 60u, - 100u, - 100u, - 100u, - 100u, - 100u, - }; - static unsigned int const expected_num_refracted[] = { - 98u, - 100u, - 97u, - 96u, - 85u, - 89u, - 91u, - 83u, - 82u, - 66u, - 73u, - 58u, - 40u, - 0u, - 0u, - 0u, - 0u, - 0u, - }; - - EXPECT_VEC_EQ(expected_num_reflected, result.num_reflected); - EXPECT_VEC_EQ(expected_num_refracted, result.num_refracted); - EXPECT_VEC_EQ(expected_num_absorbed, result.num_absorbed); - } -} - -//---------------------------------------------------------------------------// -} // namespace test -} // namespace optical -} // namespace celeritas diff --git a/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.cc b/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.cc new file mode 100644 index 0000000000..0607e29ce2 --- /dev/null +++ b/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.cc @@ -0,0 +1,116 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/SurfacePhysicsIntegrationTestBase.cc +//---------------------------------------------------------------------------// +#include "SurfacePhysicsIntegrationTestBase.hh" + +#include "geocel/UnitUtils.hh" +#include "celeritas/optical/TrackInitializer.hh" + +using ::celeritas::test::from_cm; + +namespace celeritas +{ +namespace optical +{ +namespace test +{ +//---------------------------------------------------------------------------// +GeantPhysicsOptions +SurfacePhysicsIntegrationTestBase::build_geant_options() const +{ + auto result = GeantTestBase::build_geant_options(); + result.optical = {}; + CELER_ENSURE(result.optical); + return result; +} + +//---------------------------------------------------------------------------// +GeantImportDataSelection +SurfacePhysicsIntegrationTestBase::build_import_data_selection() const +{ + auto result = GeantTestBase::build_import_data_selection(); + result.processes |= GeantImportDataSelection::optical; + return result; +} + +//---------------------------------------------------------------------------// +auto SurfacePhysicsIntegrationTestBase::select_optical_models() const + -> std::vector +{ + return {IMC::absorption}; +} + +//---------------------------------------------------------------------------// +auto SurfacePhysicsIntegrationTestBase::build_optical_surface_physics() + -> SPConstOpticalSurfacePhysics +{ + inp::SurfacePhysics input; + + this->setup_surface_models(input); + + // Default surface + + PhysSurfaceId phys_surface = [&] { + size_type num_surfaces = 0; + for (auto const& mats : input.materials) + { + num_surfaces += mats.size() + 1; + } + return PhysSurfaceId(num_surfaces); + }(); + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, inp::FresnelReflection{}); + input.interaction.trivial.emplace(phys_surface, + TrivialInteractionMode::absorb); + + return std::make_shared( + this->optical_action_reg().get(), input); +} + +//---------------------------------------------------------------------------// +void SurfacePhysicsIntegrationTestBase::initialize_run() +{ + generate_ = DirectGeneratorAction::make_and_insert(*this->optical_params()); + + Transporter::Input inp; + inp.params = this->optical_params(); + transport_ = std::make_shared(std::move(inp)); + + size_type num_tracks = 128; + auto state = std::make_shared>( + *this->optical_params(), StreamId{0}, num_tracks); + state->aux() = std::make_shared( + *this->core()->aux_reg(), MemSpace::host, StreamId{0}, num_tracks); + state_ = state; +} + +//---------------------------------------------------------------------------// +void SurfacePhysicsIntegrationTestBase::run_step(RealTurn angle) +{ + real_type sin_theta; + real_type cos_theta; + sincos(angle, &sin_theta, &cos_theta); + + std::vector inits( + 100, + TrackInitializer{units::MevEnergy{3e-6}, + from_cm(Real3{0, 49, 0}), + Real3{sin_theta, cos_theta, 0}, // direction + Real3{0, 0, 1}, // polarization + 0, // time + ImplVolumeId{0}}); + + generate_->insert(*state_, make_span(inits)); + + (*transport_)(*state_); +} + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace optical +} // namespace celeritas diff --git a/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.hh b/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.hh new file mode 100644 index 0000000000..44c83bddb9 --- /dev/null +++ b/test/celeritas/optical/SurfacePhysicsIntegrationTestBase.hh @@ -0,0 +1,151 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/SurfacePhysicsIntegrationTestBase.hh +//---------------------------------------------------------------------------// +#pragma once + +#include + +#include "corecel/Config.hh" + +#include "corecel/data/AuxStateVec.hh" +#include "corecel/math/Turn.hh" +#include "corecel/sys/ActionGroups.hh" +#include "corecel/sys/ActionRegistry.hh" +#include "celeritas/GeantTestBase.hh" +#include "celeritas/ext/GeantImporter.hh" +#include "celeritas/global/CoreParams.hh" +#include "celeritas/optical/CoreParams.hh" +#include "celeritas/optical/CoreState.hh" +#include "celeritas/optical/CoreTrackView.hh" +#include "celeritas/optical/Transporter.hh" +#include "celeritas/optical/gen/DirectGeneratorAction.hh" +#include "celeritas/optical/surface/SurfacePhysicsParams.hh" +#include "celeritas/track/TrackFunctors.hh" + +namespace celeritas +{ +namespace optical +{ +namespace test +{ +/*! + * Reference results: + * - Double precision + * - Orange geometry (requires valid surface normals and relocation on + * boundary) + */ +constexpr bool reference_configuration + = ((CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_DOUBLE) + && (CELERITAS_CORE_GEO == CELERITAS_CORE_GEO_ORANGE) + && (CELERITAS_CORE_RNG == CELERITAS_CORE_RNG_XORWOW)); + +//---------------------------------------------------------------------------// +/*! + * Template class for capturing photons after a surface interaction and scoring + * them with the given functor. + */ +template +class CollectResultsAction final : public OpticalStepActionInterface, + public ConcreteAction +{ + public: + explicit CollectResultsAction(ActionId aid, Collector& results) + : ConcreteAction(aid, "collect-results", "collect test results") + , results_(results) + { + } + + void step(CoreParams const& params, CoreStateHost& state) const final + { + for (auto tid : range(TrackSlotId{state.size()})) + { + CoreTrackView track(params.host_ref(), state.ref(), tid); + auto sim = track.sim(); + if (this->is_post_boundary(track) + || this->is_absorbed_on_boundary(track)) + { + results_(track); + sim.status(TrackStatus::killed); + } + } + } + + void step(CoreParams const&, CoreStateDevice&) const final + { + CELER_NOT_IMPLEMENTED("not collecting on device"); + } + + StepActionOrder order() const final { return StepActionOrder::post; } + + private: + //! Whether the track finished a boundary crossing + inline bool is_post_boundary(CoreTrackView const& track) const + { + return AppliesValid{}(track) + && track.sim().post_step_action() + == track.surface_physics().scalars().post_boundary_action; + } + + //! Whether the track was absorbed during a boundary crossing + inline bool is_absorbed_on_boundary(CoreTrackView const& track) const + { + return track.sim().status() == TrackStatus::killed + && track.sim().post_step_action() + == track.surface_physics().scalars().surface_stepping_action; + } + + Collector& results_; +}; + +//---------------------------------------------------------------------------// +/*! + * A test base for running surface physics integration tests. + * + * Tests are run in the optical-box.gdml setup, where photons are initialized + * close to the top (positive-y) edge and are shot directly into it. The + * collect action is used to capture photons immediately after a surface + * interaction and log them in an appropriate functor. + */ +class SurfacePhysicsIntegrationTestBase + : public ::celeritas::test::GeantTestBase +{ + public: + std::string_view gdml_basename() const override { return "optical-box"; } + + GeantPhysicsOptions build_geant_options() const override; + GeantImportDataSelection build_import_data_selection() const override; + std::vector select_optical_models() const override; + SPConstOpticalSurfacePhysics build_optical_surface_physics() override; + + //! Initialize transporter and state for run + void initialize_run(); + + //! Run a single set of photons at the given angle + void run_step(RealTurn angle); + + //! Create a collector action for the given functor + template + void create_collector(C& collect) + { + auto& reg = *this->optical_params()->action_reg(); + auto collector = std::make_shared>( + reg.next_id(), collect); + reg.insert(collector); + } + + protected: + std::shared_ptr> state_; + std::shared_ptr aux_; + std::shared_ptr transport_; + std::shared_ptr generate_; + + virtual void setup_surface_models(inp::SurfacePhysics&) const = 0; +}; + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace optical +} // namespace celeritas diff --git a/test/celeritas/optical/SurfacePhysicsInteractionIntegration.test.cc b/test/celeritas/optical/SurfacePhysicsInteractionIntegration.test.cc new file mode 100644 index 0000000000..fea311635c --- /dev/null +++ b/test/celeritas/optical/SurfacePhysicsInteractionIntegration.test.cc @@ -0,0 +1,356 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/SurfacePhysicsInteractionIntegration.test.cc +//---------------------------------------------------------------------------// + +#include "corecel/math/Turn.hh" + +#include "SurfacePhysicsIntegrationTestBase.hh" +#include "celeritas_test.hh" + +namespace celeritas +{ +namespace optical +{ +namespace test +{ +constexpr Turn degree{real_type{1} / 360}; +//---------------------------------------------------------------------------// +/*! + * Counters for photon status after a run at a single angle. + */ +struct CollectResults +{ + size_type num_absorbed{0}; + size_type num_failed{0}; + size_type num_reflected{0}; + size_type num_refracted{0}; + + //! Clear counters + void reset() + { + num_absorbed = 0; + num_failed = 0; + num_reflected = 0; + num_refracted = 0; + } + + //! Score track + void operator()(CoreTrackView const& track) + { + if (track.sim().status() == TrackStatus::alive) + { + auto vol = track.geometry().volume_instance_id(); + if (vol == VolumeInstanceId{1}) + { + num_reflected++; + return; + } + else if (vol == VolumeInstanceId{2}) + { + num_refracted++; + return; + } + } + else if (track.sim().status() == TrackStatus::killed) + { + num_absorbed++; + return; + } + + num_failed++; + } +}; + +//---------------------------------------------------------------------------// +/*! + * Counter results for a series of runs at different angles. + */ +struct SurfaceTestResults +{ + std::vector num_absorbed; + std::vector num_reflected; + std::vector num_refracted; +}; + +//---------------------------------------------------------------------------// +// TEST CHASSIS +//---------------------------------------------------------------------------// + +class SurfacePhysicsInteractionIntegrationTest + : public SurfacePhysicsIntegrationTestBase +{ + public: + SurfaceTestResults run(std::vector const& angles) + { + this->create_collector(collect_); + + this->initialize_run(); + + // Run over angles + SurfaceTestResults results; + for (auto angle : angles) + { + collect_.reset(); + + this->run_step(angle); + + EXPECT_EQ(0, collect_.num_failed); + results.num_absorbed.push_back(collect_.num_absorbed); + results.num_reflected.push_back(collect_.num_reflected); + results.num_refracted.push_back(collect_.num_refracted); + } + + return results; + } + + void reference_run(std::vector const& angles, + SurfaceTestResults const& expected) + { + auto result = this->run(angles); + if (reference_configuration) + { + EXPECT_EQ(expected.num_reflected, result.num_reflected); + EXPECT_EQ(expected.num_refracted, result.num_refracted); + EXPECT_EQ(expected.num_absorbed, result.num_absorbed); + } + } + + protected: + CollectResults collect_; +}; + +//---------------------------------------------------------------------------// +class SurfacePhysicsIntegrationBackscatterTest + : public SurfacePhysicsInteractionIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + + // Only back-scattering + + input.interaction.trivial.emplace(phys_surface, + TrivialInteractionMode::backscatter); + } +}; + +//---------------------------------------------------------------------------// +class SurfacePhysicsIntegrationAbsorbTest + : public SurfacePhysicsInteractionIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + + // Only absorption + + input.interaction.trivial.emplace(phys_surface, + TrivialInteractionMode::absorb); + } +}; + +//---------------------------------------------------------------------------// +class SurfacePhysicsIntegrationTransmitTest + : public SurfacePhysicsInteractionIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + + // Only transmission + + input.interaction.trivial.emplace(phys_surface, + TrivialInteractionMode::transmit); + } +}; + +//---------------------------------------------------------------------------// +class SurfacePhysicsIntegrationFresnelTest + : public SurfacePhysicsInteractionIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + + // Fresnel refraction / reflection + + input.interaction.dielectric.emplace( + phys_surface, + inp::DielectricInteraction::from_dielectric( + inp::ReflectionForm::from_spike())); + } +}; + +//---------------------------------------------------------------------------// +// TESTS +//---------------------------------------------------------------------------// +// Only back-scattering +TEST_F(SurfacePhysicsIntegrationBackscatterTest, backscatter) +{ + std::vector angles{RealTurn{0}, 30 * degree, 60 * degree}; + + SurfaceTestResults expected; + expected.num_reflected = {100, 100, 100}; + expected.num_refracted = {0, 0, 0}; + expected.num_absorbed = {0, 0, 0}; + + this->reference_run(angles, expected); +} + +//---------------------------------------------------------------------------// +// Only absorption +TEST_F(SurfacePhysicsIntegrationAbsorbTest, absorb) +{ + std::vector angles{RealTurn{0}, 30 * degree, 60 * degree}; + + SurfaceTestResults expected; + expected.num_refracted = {0, 0, 0}; + expected.num_reflected = {0, 0, 0}; + expected.num_absorbed = {100, 100, 100}; + + this->reference_run(angles, expected); +} + +//---------------------------------------------------------------------------// +// Only transmission +TEST_F(SurfacePhysicsIntegrationTransmitTest, transmit) +{ + std::vector angles{RealTurn{0}, 30 * degree, 60 * degree}; + + SurfaceTestResults expected; + expected.num_refracted = {100, 100, 100}; + expected.num_reflected = {0, 0, 0}; + expected.num_absorbed = {0, 0, 0}; + + this->reference_run(angles, expected); +} + +//---------------------------------------------------------------------------// +// Fresnel reflection / refraction +TEST_F(SurfacePhysicsIntegrationFresnelTest, fresnel) +{ + std::vector angles{ + RealTurn{0}, + 10 * degree, + 20 * degree, + 30 * degree, + 40 * degree, + 41 * degree, + 42 * degree, + 43 * degree, + 44 * degree, + 45 * degree, + 46 * degree, + 47 * degree, + 48 * degree, + 49 * degree, + 50 * degree, + 60 * degree, + 70 * degree, + 80 * degree, + }; + + SurfaceTestResults expected; + expected.num_absorbed = { + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + 0u, + }; + expected.num_reflected = { + 2u, + 0u, + 3u, + 4u, + 15u, + 11u, + 9u, + 17u, + 18u, + 34u, + 27u, + 42u, + 60u, + 100u, + 100u, + 100u, + 100u, + 100u, + }; + expected.num_refracted = { + 98u, + 100u, + 97u, + 96u, + 85u, + 89u, + 91u, + 83u, + 82u, + 66u, + 73u, + 58u, + 40u, + 0u, + 0u, + 0u, + 0u, + 0u, + }; + + this->reference_run(angles, expected); +} + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace optical +} // namespace celeritas diff --git a/test/celeritas/optical/SurfacePhysicsRoughnessIntegration.test.cc b/test/celeritas/optical/SurfacePhysicsRoughnessIntegration.test.cc new file mode 100644 index 0000000000..6e1cffb2a4 --- /dev/null +++ b/test/celeritas/optical/SurfacePhysicsRoughnessIntegration.test.cc @@ -0,0 +1,209 @@ +//------------------------------- -*- C++ -*- -------------------------------// +// Copyright Celeritas contributors: see top-level COPYRIGHT file for details +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +//---------------------------------------------------------------------------// +//! \file celeritas/optical/SurfacePhysicsRoughnessIntegration.test.cc +//---------------------------------------------------------------------------// +#include "corecel/math/Turn.hh" +#include "corecel/random/Histogram.hh" + +#include "SurfacePhysicsIntegrationTestBase.hh" +#include "celeritas_test.hh" + +namespace celeritas +{ +namespace optical +{ +namespace test +{ +using ::celeritas::test::Histogram; + +//---------------------------------------------------------------------------// +/*! + * Collect results based on the track's direction dot produced with respect to + * the surface normal. + * + * The surface normal is (0,1,0), so the dot product is just the y-component. + * This gives a distribution of reflected and refracted angles. + */ +struct CollectResults +{ + Histogram reflection_cosine{20, {-1, 1}}; + size_type num_failed{0}; + + //! Score track + void operator()(CoreTrackView const& track) + { + if (track.sim().status() == TrackStatus::alive) + { + reflection_cosine(track.geometry().dir()[1]); + return; + } + num_failed++; + } +}; + +//---------------------------------------------------------------------------// +// TEST CHASSIS +//---------------------------------------------------------------------------// +/*! + * Base class for testing surface roughness models. + * + * Sub-classes should use Fresnel reflection with the lobe mode to ensure the + * local facet normal is used for reflection. + */ +class SurfacePhysicsRoughnessIntegrationTest + : public SurfacePhysicsIntegrationTestBase +{ + public: + /*! + * Run for a certain number of iterations and compare to the expected + * distribution. + */ + void run(size_type loops, std::vector const& expected) + { + this->create_collector(collect_); + + this->initialize_run(); + + for ([[maybe_unused]] auto i : range(loops)) + { + this->run_step(RealTurn(0.0)); // along x = 0 + } + + if (reference_configuration) + { + EXPECT_EQ(0, collect_.num_failed); + EXPECT_VEC_EQ(expected, collect_.reflection_cosine.counts()); + } + } + + protected: + CollectResults collect_; +}; + +//---------------------------------------------------------------------------// +/*! + * Polished roughness model. + */ +class SurfacePhysicsIntegrationPolishedTest + : public SurfacePhysicsRoughnessIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + input.interaction.dielectric.emplace( + phys_surface, + inp::DielectricInteraction::from_dielectric( + inp::ReflectionForm::from_lobe())); + + // polished roughness + + input.roughness.polished.emplace(phys_surface, inp::NoRoughness{}); + } +}; + +//---------------------------------------------------------------------------// +/*! + * Uniform smear roughness model. + */ +class SurfacePhysicsIntegrationSmearTest + : public SurfacePhysicsRoughnessIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + input.interaction.dielectric.emplace( + phys_surface, + inp::DielectricInteraction::from_dielectric( + inp::ReflectionForm::from_lobe())); + + // smear roughness + + input.roughness.smear.emplace(phys_surface, inp::SmearRoughness{0.8}); + } +}; + +//---------------------------------------------------------------------------// +/*! + * Gaussian roughness model. + */ +class SurfacePhysicsIntegrationGaussianTest + : public SurfacePhysicsRoughnessIntegrationTest +{ + public: + void setup_surface_models(inp::SurfacePhysics& input) const final + { + PhysSurfaceId phys_surface{0}; + + // center-top surface + + input.materials.push_back({}); + input.reflectivity.fresnel.emplace(phys_surface, + inp::FresnelReflection{}); + input.interaction.dielectric.emplace( + phys_surface, + inp::DielectricInteraction::from_dielectric( + inp::ReflectionForm::from_lobe())); + + // Gaussian roughness + + input.roughness.gaussian.emplace(phys_surface, + inp::GaussianRoughness{0.6}); + } +}; + +//---------------------------------------------------------------------------// +// TESTS +//---------------------------------------------------------------------------// +// Only polished +TEST_F(SurfacePhysicsIntegrationPolishedTest, polished) +{ + std::vector expected{ + 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 985, + }; + + this->run(10, expected); +} + +//---------------------------------------------------------------------------// +// Only uniform smear +TEST_F(SurfacePhysicsIntegrationSmearTest, smear) +{ + std::vector expected{ + 4, 11, 6, 5, 7, 4, 3, 4, 7, 15, 0, 0, 0, 1, 0, 0, 0, 1, 34, 898, + }; + + this->run(10, expected); +} + +//---------------------------------------------------------------------------// +// Only Gaussian roughness +TEST_F(SurfacePhysicsIntegrationGaussianTest, gaussian) +{ + std::vector expected{ + 4, 17, 14, 23, 20, 27, 26, 21, 36, 33, + 22, 11, 21, 9, 11, 13, 13, 14, 57, 608, + }; + + this->run(10, expected); +} + +//---------------------------------------------------------------------------// +} // namespace test +} // namespace optical +} // namespace celeritas diff --git a/test/celeritas/phys/Particle.test.cc b/test/celeritas/phys/Particle.test.cc index 49ec9c06de..8eb914da55 100644 --- a/test/celeritas/phys/Particle.test.cc +++ b/test/celeritas/phys/Particle.test.cc @@ -193,8 +193,6 @@ TEST_F(ParticleTestHost, electron) // Stop the particle EXPECT_FALSE(particle.is_stopped()); - particle.subtract_energy(MevEnergy{0.25}); - EXPECT_REAL_EQ(0.25, particle.energy().value()); particle.energy(zero_quantity()); EXPECT_TRUE(particle.is_stopped()); EXPECT_REAL_EQ(0.0, particle.energy().value()); diff --git a/test/celeritas/phys/Physics.test.cc b/test/celeritas/phys/Physics.test.cc index 9e19179128..520605c4a8 100644 --- a/test/celeritas/phys/Physics.test.cc +++ b/test/celeritas/phys/Physics.test.cc @@ -399,8 +399,11 @@ TEST_F(PhysicsTrackViewHostTest, step_view) gamma.reset_energy_deposition(); gamma.deposit_energy(Energy(2.5)); EXPECT_REAL_EQ(2.5, value_as(gamma_cref.energy_deposition())); - // Allow zero-energy deposition - EXPECT_NO_THROW(gamma.deposit_energy(zero_quantity())); + // Forbid zero-energy deposition + if (CELERITAS_DEBUG) + { + EXPECT_THROW(gamma.deposit_energy(zero_quantity()), DebugError); + } EXPECT_REAL_EQ(2.5, value_as(gamma_cref.energy_deposition())); gamma.reset_energy_deposition(); EXPECT_REAL_EQ(0.0, value_as(gamma_cref.energy_deposition())); diff --git a/test/celeritas/phys/ProcessBuilder.test.cc b/test/celeritas/phys/ProcessBuilder.test.cc index 7901812b2b..316be921c0 100644 --- a/test/celeritas/phys/ProcessBuilder.test.cc +++ b/test/celeritas/phys/ProcessBuilder.test.cc @@ -13,6 +13,7 @@ #include "celeritas/em/process/CoulombScatteringProcess.hh" #include "celeritas/em/process/EIonizationProcess.hh" #include "celeritas/em/process/EPlusAnnihilationProcess.hh" +#include "celeritas/em/process/ElectroNuclearProcess.hh" #include "celeritas/em/process/GammaConversionProcess.hh" #include "celeritas/em/process/GammaNuclearProcess.hh" #include "celeritas/em/process/MuBremsstrahlungProcess.hh" @@ -277,6 +278,46 @@ TEST_F(ProcessBuilderTest, gamma_conversion) } } +TEST_F(ProcessBuilderTest, electro_nuclear) +{ + if (!this->has_particle_xs_data()) + { + GTEST_SKIP() << "Missing ElectroNuclearData"; + } + + ProcessBuilder build_process( + this->import_data(), this->particle(), this->material()); + + // Create process + auto process = build_process(IPC::electro_nuclear); + EXPECT_PROCESS_TYPE(ElectroNuclearProcess, process.get()); + + // Test model + auto models = process->build_models(ActionIdIter{}); + ASSERT_EQ(1, models.size()); + ASSERT_TRUE(models.front()); + EXPECT_EQ("electro-nuclear", models.front()->label()); + auto all_applic = models.front()->applicability(); + ASSERT_EQ(2, all_applic.size()); + Applicability applic = *all_applic.begin(); + + for (auto mat_id : range(PhysMatId{this->material()->num_materials()})) + { + // Test step limits + { + applic.material = mat_id; + EXPECT_FALSE(process->macro_xs(applic)); + EXPECT_FALSE(process->energy_loss(applic)); + } + + // Test micro xs + for (auto const& model : models) + { + EXPECT_TRUE(model->micro_xs(applic).empty()); + } + } +} + TEST_F(ProcessBuilderTest, gamma_nuclear) { if (!this->has_particle_xs_data()) diff --git a/test/celeritas/track/TrackInit.test.cc b/test/celeritas/track/TrackInit.test.cc index 29aab0b16f..8c3b8750dd 100644 --- a/test/celeritas/track/TrackInit.test.cc +++ b/test/celeritas/track/TrackInit.test.cc @@ -63,14 +63,14 @@ RunResult RunResult::from_state(CoreState& state) data = state.ref().init; // Store the IDs of the vacant track slots - for (auto tid : range(TrackSlotId{state.counters().num_vacancies})) + for (auto tid : range(TrackSlotId{state.sync_get_counters().num_vacancies})) { result.vacancies.push_back(id_to_int(data.vacancies[tid])); } // Store the track IDs of the initializers - for (auto init_id : - range(ItemId{state.counters().num_initializers})) + for (auto init_id : range(ItemId{ + state.sync_get_counters().num_initializers})) { auto const& init = data.initializers[init_id]; result.init_ids.push_back(id_to_int(init.sim.track_id)); @@ -226,15 +226,15 @@ TYPED_TEST_SUITE(TrackInitTest, MemspaceTypes, MemspaceTypeString); TYPED_TEST(TrackInitTest, add_more_primaries) { this->build_states(16); - EXPECT_EQ(0, this->state().counters().num_initializers); + EXPECT_EQ(0, this->state().sync_get_counters().num_initializers); auto primaries = this->make_primaries(22); this->extend_from_primaries(make_span(primaries)); - EXPECT_EQ(22, this->state().counters().num_initializers); + EXPECT_EQ(22, this->state().sync_get_counters().num_initializers); primaries = this->make_primaries(32); this->extend_from_primaries(make_span(primaries)); - EXPECT_EQ(54, this->state().counters().num_initializers); + EXPECT_EQ(54, this->state().sync_get_counters().num_initializers); } //! Test that we can add more primaries than the first allocation @@ -247,8 +247,7 @@ TYPED_TEST(TrackInitTest, extend_primaries) auto primaries = this->make_primaries(2); this->insert_primaries(this->state(), make_span(primaries)); RunResult::from_state(this->state()); - - EXPECT_EQ(0, this->state().counters().num_initializers); + EXPECT_EQ(0, this->state().sync_get_counters().num_initializers); } { // Now initialize after adding @@ -415,8 +414,9 @@ TYPED_TEST(TrackInitTest, primaries) // Find vacancies and create track initializers from secondaries extend_from_secondaries.step(*this->core(), this->state()); EXPECT_EQ(i * num_tracks / 2, - this->state().counters().num_initializers); - EXPECT_EQ(num_tracks / 2, this->state().counters().num_vacancies); + this->state().sync_get_counters().num_initializers); + EXPECT_EQ(num_tracks / 2, + this->state().sync_get_counters().num_vacancies); } // Check the results @@ -474,8 +474,8 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) // Create track initializers on device from primary particles auto primaries = this->make_primaries(num_primaries); this->extend_from_primaries(make_span(primaries)); - EXPECT_EQ(num_primaries, this->state().counters().num_initializers); - + EXPECT_EQ(num_primaries, + this->state().sync_get_counters().num_initializers); auto apply_actions = [&actions, this] { for (auto const& ea_interface : actions) { @@ -497,7 +497,7 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) static int const expected_geo_parent_ids[] = {0, 2}; EXPECT_VEC_EQ(expected_geo_parent_ids, result.geo_parent_ids); - // init ids may not be deterministic, but can guarantee they are in the + // init IDs may not be deterministic, but can guarantee they are in the // range 8<=x<=12 as we create 4 tracks per iteration, 2 in reused // slots from their parent, 2 as new inits EXPECT_EQ(2, result.init_ids.size()); @@ -506,7 +506,7 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) std::end(result.init_ids), [i](int id) { return id >= 8 + i * 4 && id <= 11 + i * 4; })); - // Track ids may not be deterministic, so only validate size and + // Track IDs may not be deterministic, so only validate size and // range. (Remember that we create 4 new tracks per iteration, with 2 // slots reused EXPECT_EQ(num_tracks, result.track_ids.size()); @@ -518,9 +518,9 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) + (i + 1) * 4; })); - // Parent ids may not be deterministic, but all non-killed tracks are - // guaranteed to be primaries at every iteration. At end of first - // iteration, will still have some primary ids as these are not cleared + // Parent IDs may not be deterministic, but all non-killed tracks are + // guaranteed to be primaries at every iteration. At end of the first + // iteration, will still have some primary IDs as these are not cleared // until the next iteration for (size_type pidx : range(num_tracks)) { @@ -529,7 +529,7 @@ TYPED_TEST(TrackInitTest, extend_from_secondaries) << "iteration " << i; } } -} // namespace test +} //---------------------------------------------------------------------------// } // namespace test diff --git a/test/corecel/Assert.test.cc b/test/corecel/Assert.test.cc index 51d11c48fa..4172904937 100644 --- a/test/corecel/Assert.test.cc +++ b/test/corecel/Assert.test.cc @@ -25,11 +25,10 @@ class AssertTest : public ::celeritas::test::Test protected: static void SetUpTestSuite() { - EXPECT_FALSE(celeritas::getenv("CELER_COLOR").empty() - && celeritas::getenv("GTEST_COLOR").empty()) + EXPECT_TRUE(celeritas::getenv("NO_COLOR").empty() + && (celeritas::getenv_flag("CELER_COLOR", true).value + || celeritas::getenv_flag("GTEST_COLOR", true).value)) << "Color must be enabled for this test"; - EXPECT_FALSE(celeritas::getenv("CELER_LOG").empty()) - << "Logging (for verbose output) must be enabled for this test"; } }; @@ -42,8 +41,8 @@ TEST_F(AssertTest, debug_error) details.line = 123; EXPECT_STREQ( - "\x1B[37;1mAssert.test.cc:123:\x1B[0m\nceleritas: \x1B[31;1minternal " - "assertion failed: \x1B[37;2m2 + 2 == 5\x1B[0m", + "\x1B[1;37mAssert.test.cc:123:\x1B[0m\nceleritas: \x1B[1;31minternal " + "assertion failed: \x1B[2;37m2 + 2 == 5\x1B[0m", DebugError{std::move(details)}.what()); } @@ -70,8 +69,8 @@ TEST_F(AssertTest, runtime_error) { EXPECT_TRUE(std::string{e.what()}.find("implementation error:") != std::string::npos); - EXPECT_TRUE(std::string{e.what()}.find("feature is not yet " - "implemented: bar") + EXPECT_TRUE(std::string{e.what()}.find( + R"(feature is not yet implemented: bar)") != std::string::npos) << e.what(); } @@ -81,10 +80,9 @@ TEST_F(AssertTest, runtime_error) } catch (RuntimeError const& e) { - EXPECT_TRUE(std::string{e.what()}.find("runtime error: \x1B[0mthis is " - "not OK") - != std::string::npos) - << e.what(); + char const expected[] = "runtime error: \x1B[0mthis is not OK"; + EXPECT_TRUE(std::string{e.what()}.find(expected) != std::string::npos) + << repr(e.what()); } } @@ -122,22 +120,22 @@ TEST_F(AssertTest, runtime_error_variations) // clang-format off static char const* const expected_messages[] = { - "\x1b[31;1munknown error: \x1b[0m\n\x1b[37;2munknown source:\x1b[0m failure", - "\x1b[31;1mruntime error: \x1b[0m\n\x1b[37;2munknown source:\x1b[0m failure", - "\x1b[31;1munknown error: \x1b[0mbad things happened\n\x1b[37;2munknown source:\x1b[0m failure", - "\x1b[31;1mruntime error: \x1b[0mbad things happened\n\x1b[37;2munknown source:\x1b[0m failure", - "\x1b[31;1munknown error: \x1b[0m\n\x1b[37;1munknown source:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1mruntime error: \x1b[0m\n\x1b[37;1munknown source:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1munknown error: \x1b[0mbad things happened\n\x1b[37;1munknown source:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1mruntime error: \x1b[0mbad things happened\n\x1b[37;1munknown source:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1munknown error: \x1b[0m\n\x1b[37;2mAssert.test.cc:\x1b[0m failure", - "\x1b[31;1mruntime error: \x1b[0m\n\x1b[37;2mAssert.test.cc:123:\x1b[0m failure", - "\x1b[31;1munknown error: \x1b[0mbad things happened\n\x1b[37;2mAssert.test.cc:\x1b[0m failure", - "\x1b[31;1mruntime error: \x1b[0mbad things happened\n\x1b[37;2mAssert.test.cc:123:\x1b[0m failure", - "\x1b[31;1munknown error: \x1b[0m\n\x1b[37;1mAssert.test.cc:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1mruntime error: \x1b[0m\n\x1b[37;1mAssert.test.cc:123:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1munknown error: \x1b[0mbad things happened\n\x1b[37;1mAssert.test.cc:\x1b[0m '2 + 2 == 5' failed", - "\x1b[31;1mruntime error: \x1b[0mbad things happened\n\x1b[37;1mAssert.test.cc:123:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31munknown error: \x1b[0m\n\x1b[2;37munknown source:\x1b[0m failure", + "\x1b[1;31mruntime error: \x1b[0m\n\x1b[2;37munknown source:\x1b[0m failure", + "\x1b[1;31munknown error: \x1b[0mbad things happened\n\x1b[2;37munknown source:\x1b[0m failure", + "\x1b[1;31mruntime error: \x1b[0mbad things happened\n\x1b[2;37munknown source:\x1b[0m failure", + "\x1b[1;31munknown error: \x1b[0m\n\x1b[1;37munknown source:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31mruntime error: \x1b[0m\n\x1b[1;37munknown source:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31munknown error: \x1b[0mbad things happened\n\x1b[1;37munknown source:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31mruntime error: \x1b[0mbad things happened\n\x1b[1;37munknown source:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31munknown error: \x1b[0m\n\x1b[2;37mAssert.test.cc:\x1b[0m failure", + "\x1b[1;31mruntime error: \x1b[0m\n\x1b[2;37mAssert.test.cc:123:\x1b[0m failure", + "\x1b[1;31munknown error: \x1b[0mbad things happened\n\x1b[2;37mAssert.test.cc:\x1b[0m failure", + "\x1b[1;31mruntime error: \x1b[0mbad things happened\n\x1b[2;37mAssert.test.cc:123:\x1b[0m failure", + "\x1b[1;31munknown error: \x1b[0m\n\x1b[1;37mAssert.test.cc:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31mruntime error: \x1b[0m\n\x1b[1;37mAssert.test.cc:123:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31munknown error: \x1b[0mbad things happened\n\x1b[1;37mAssert.test.cc:\x1b[0m '2 + 2 == 5' failed", + "\x1b[1;31mruntime error: \x1b[0mbad things happened\n\x1b[1;37mAssert.test.cc:123:\x1b[0m '2 + 2 == 5' failed", }; // clang-format on diff --git a/test/corecel/ScopedLogStorer.cc b/test/corecel/ScopedLogStorer.cc index b30ac182e7..37d7a0b308 100644 --- a/test/corecel/ScopedLogStorer.cc +++ b/test/corecel/ScopedLogStorer.cc @@ -30,6 +30,12 @@ void debug_clog(LogProvenance, LogLevel lev, std::string msg) } } // namespace +//---------------------------------------------------------------------------// +/*! + * Construct null storer for disassociating before destruction. + */ +ScopedLogStorer::ScopedLogStorer() = default; + //---------------------------------------------------------------------------// /*! * Construct reference to log to temporarily replace. @@ -41,9 +47,7 @@ ScopedLogStorer::ScopedLogStorer(Logger* orig, LogLevel min_level) CELER_EXPECT(min_level != LogLevel::size_); // Create a new logger that calls our operator(), replace orig and store saved_logger_ = std::make_unique( - std::exchange(*logger_, Logger{std::ref(*this)})); - // Catch everything, keep only what we want - logger_->level(LogLevel::debug); + std::exchange(*logger_, Logger{std::ref(*this), LogLevel::debug})); } //---------------------------------------------------------------------------// @@ -74,7 +78,7 @@ void ScopedLogStorer::operator()(LogProvenance prov, std::string msg) { static LogLevel const debug_level - = log_level_from_env("CELER_LOG_SCOPED", LogLevel::warning); + = getenv_loglevel("CELER_LOG_SCOPED", LogLevel::warning); if (lev >= debug_level) { if (getenv_flag("CELER_LOG_SCOPED_VERBOSE", false).value) diff --git a/test/corecel/ScopedLogStorer.hh b/test/corecel/ScopedLogStorer.hh index af198509bc..df5805fb18 100644 --- a/test/corecel/ScopedLogStorer.hh +++ b/test/corecel/ScopedLogStorer.hh @@ -53,7 +53,7 @@ class ScopedLogStorer explicit ScopedLogStorer(Logger* orig); // Construct null storer for disassociating before destruction - ScopedLogStorer() = default; + ScopedLogStorer(); // Restore original logger on destruction ~ScopedLogStorer(); diff --git a/test/corecel/cont/InitializedValue.test.cc b/test/corecel/cont/InitializedValue.test.cc index 1b334a1fea..cad98f4516 100644 --- a/test/corecel/cont/InitializedValue.test.cc +++ b/test/corecel/cont/InitializedValue.test.cc @@ -17,8 +17,7 @@ namespace test TEST(InitializedValue, no_finalizer) { using InitValueInt = InitializedValue; - // TODO: in C++20 use [[no_unique_address]] and restore this assertion - // static_assert(sizeof(InitValueInt) == sizeof(int), "Bad size"); + static_assert(sizeof(InitValueInt) == sizeof(int), "Bad size"); // Use operator new to test that the int is being initialized properly by // constructing into data space that's been set to a different value @@ -66,61 +65,87 @@ TEST(InitializedValue, no_finalizer) } //---------------------------------------------------------------------------// +using OptionalInt = std::optional; struct Finalizer { - static std::vector& finalized_values() + static OptionalInt& last_finalized() { - static std::vector result; + static OptionalInt result; return result; } - void operator()(int val) const { finalized_values().push_back(val); } + void operator()(int val) const { last_finalized() = val; } }; -TEST(InitializedValue, finalizer) +OptionalInt get_last_finalized() { - std::vector expected; - std::vector& actual = Finalizer::finalized_values(); - actual.clear(); + return std::exchange(Finalizer::last_finalized(), {}); +} +TEST(InitializedValue, finalizer) +{ using InitValueInt = InitializedValue; + // Test default value + InitValueInt{}; + EXPECT_EQ(std::nullopt, get_last_finalized()); + + { + // Test nondefault value + InitValueInt derp{1}; + EXPECT_EQ(1, derp); + EXPECT_EQ(std::nullopt, get_last_finalized()); + } + EXPECT_EQ(1, get_last_finalized()); + InitValueInt ival{}; EXPECT_EQ(0, ival); - EXPECT_VEC_EQ(expected, actual); + EXPECT_EQ(std::nullopt, get_last_finalized()); + + { + InitValueInt temp{2}; + ival = std::move(temp); + EXPECT_EQ(std::nullopt, get_last_finalized()); + EXPECT_EQ(0, temp); + EXPECT_EQ(2, ival); + } + EXPECT_EQ(std::nullopt, get_last_finalized()); + ival = {}; + EXPECT_EQ(2, get_last_finalized()); InitValueInt other{345}; EXPECT_EQ(345, other); ival = other; - expected.push_back(0); - EXPECT_VEC_EQ(expected, actual); + EXPECT_EQ(std::nullopt, get_last_finalized()); + EXPECT_EQ(345, ival); + EXPECT_EQ(345, other); + other = ival; + EXPECT_EQ(345, get_last_finalized()); EXPECT_EQ(345, ival); EXPECT_EQ(345, other); other = 1000; - expected.push_back(345); - EXPECT_VEC_EQ(expected, actual); + EXPECT_EQ(345, get_last_finalized()); ival = std::move(other); - expected.push_back(345); - EXPECT_VEC_EQ(expected, actual); + EXPECT_EQ(345, get_last_finalized()); EXPECT_EQ(1000, ival); EXPECT_EQ(0, other); InitValueInt third(std::move(ival)); EXPECT_EQ(0, ival); EXPECT_EQ(1000, third); + EXPECT_EQ(std::nullopt, get_last_finalized()); // Test const T& constructor int const cint = 1234; - other = InitValueInt(cint); - expected.push_back(0); - EXPECT_VEC_EQ(expected, actual); - EXPECT_EQ(1234, other); + third = InitValueInt(cint); + EXPECT_EQ(1000, get_last_finalized()); + EXPECT_EQ(1234, third); // Test implicit conversion int tempint; tempint = third; - EXPECT_EQ(1000, tempint); + EXPECT_EQ(1234, tempint); } //---------------------------------------------------------------------------// diff --git a/test/corecel/data/DeviceAllocation.test.cc b/test/corecel/data/DeviceAllocation.test.cc index ab2625395f..c648aa2b2c 100644 --- a/test/corecel/data/DeviceAllocation.test.cc +++ b/test/corecel/data/DeviceAllocation.test.cc @@ -23,7 +23,14 @@ TEST(ConstructionTest, should_work_always) { DeviceAllocation alloc; EXPECT_EQ(0, alloc.size()); + EXPECT_EQ(nullptr, alloc.data()); EXPECT_TRUE(alloc.empty()); + EXPECT_EQ(StreamId{}, alloc.stream_id()); + + DeviceAllocation alloc2; + swap(alloc, alloc2); + EXPECT_TRUE(alloc.empty()); + EXPECT_TRUE(alloc.device_ref().empty()); } #if !CELER_USE_DEVICE diff --git a/test/corecel/io/Logger.test.cc b/test/corecel/io/Logger.test.cc index e30a7a6474..15e420b0c2 100644 --- a/test/corecel/io/Logger.test.cc +++ b/test/corecel/io/Logger.test.cc @@ -169,7 +169,7 @@ TEST_F(LoggerTest, level_from_env) { auto set_level = [](std::string const& key, std::string const& val) { environment().insert({key, val}); - return log_level_from_env(key); + return getenv_loglevel(key, LogLevel::size_); }; EXPECT_EQ(LogLevel::debug, set_level("CELER_TEST_ENV_0", "debug")); diff --git a/test/corecel/random/RanluxppRngEngine.test.cc b/test/corecel/random/RanluxppRngEngine.test.cc index 45bf2a907b..28d2476e11 100644 --- a/test/corecel/random/RanluxppRngEngine.test.cc +++ b/test/corecel/random/RanluxppRngEngine.test.cc @@ -337,6 +337,67 @@ TEST_F(RanluxppRngEngineTest, jump) } } +TEST_F(RanluxppRngEngineTest, branch) +{ + unsigned int size = 4; + + // Initialize first RNG + HostStore states(params_->host_ref(), StreamId{0}, size); + RanluxppRngEngine rng(params_->host_ref(), states.ref(), TrackSlotId{0}); + rng = RanluxppInitializer{12345, 0, 0}; + + // Initialize second RNG + RanluxppRngEngine branched_rng( + params_->host_ref(), states.ref(), TrackSlotId{1}); + branched_rng = rng.branch(); + + // Create a third RNG, and branch its state manually + auto& ref_rng_state = states.ref().state[TrackSlotId{2}]; + auto& ref_branched_rng_state = states.ref().state[TrackSlotId{3}]; + RanluxppRngEngine ref_branched_rng( + params_->host_ref(), states.ref(), TrackSlotId{3}); + { + RanluxppRngEngine ref_rng( + params_->host_ref(), states.ref(), TrackSlotId{2}); + ref_rng = RanluxppInitializer{12345, 0, 0}; + ref_branched_rng = RanluxppInitializer{12345, 0, 0}; + } + + // Advance the reference RNG + { + RanluxppArray9 lcg = celeritas::detail::to_lcg(ref_rng_state.value); + lcg = celeritas::detail::compute_mod_multiply( + params_->host_ref().advance_state, lcg); + ref_rng_state.value = celeritas::detail::to_ranlux(lcg); + ref_rng_state.position = 0; + } + + // XOR with updated state + { + for (auto i : celeritas::range(9)) + { + ref_branched_rng_state.value.number[i] + ^= ref_rng_state.value.number[i]; + } + } + + // Advance the new RNG + { + RanluxppArray9 lcg + = celeritas::detail::to_lcg(ref_branched_rng_state.value); + lcg = celeritas::detail::compute_mod_multiply( + params_->host_ref().advance_state, lcg); + ref_branched_rng_state.value = celeritas::detail::to_ranlux(lcg); + ref_branched_rng_state.position = 0; + } + + // Draw 10 random numbers from the two branched RNGs and compare + for ([[maybe_unused]] auto i : celeritas::range(10)) + { + EXPECT_EQ(ref_branched_rng(), branched_rng()); + } +} + TEST_F(RanluxppRngEngineTest, TEST_IF_CELER_DEVICE(device)) { celeritas::device().create_streams(1); diff --git a/test/corecel/random/XorwowRngEngine.test.cc b/test/corecel/random/XorwowRngEngine.test.cc index d73b66f0f8..844274a52d 100644 --- a/test/corecel/random/XorwowRngEngine.test.cc +++ b/test/corecel/random/XorwowRngEngine.test.cc @@ -258,6 +258,54 @@ TEST_F(XorwowRngEngineTest, jump) } } +TEST_F(XorwowRngEngineTest, branch) +{ + unsigned int size = 4; + + HostStore states(params_->host_ref(), StreamId{0}, size); + XorwowRngEngine rng(params_->host_ref(), states.ref(), TrackSlotId{0}); + rng = XorwowRngInitializer{12345, 0, 0}; + + // Initialize second RNG + XorwowRngEngine branched_rng( + params_->host_ref(), states.ref(), TrackSlotId{1}); + branched_rng = rng.branch(); + + // Create a third RNG, and branch its state manually + auto& ref_rng_state = states.ref().state[TrackSlotId{2}]; + auto& ref_branched_rng_state = states.ref().state[TrackSlotId{3}]; + XorwowRngEngine ref_branched_rng( + params_->host_ref(), states.ref(), TrackSlotId{3}); + { + XorwowRngEngine ref_rng( + params_->host_ref(), states.ref(), TrackSlotId{2}); + ref_rng = XorwowRngInitializer{12345, 0, 0}; + ref_branched_rng = XorwowRngInitializer{12345, 0, 0}; + + // Advance the reference RNG + auto old_weyl = ref_rng_state.weylstate; + ref_rng.discard(4); + ref_rng_state.weylstate = old_weyl; + + // XOR the branched state with the updated ref state + for (auto i : celeritas::range(ref_branched_rng_state.xorstate.size())) + { + ref_branched_rng_state.xorstate[i] ^= ref_rng_state.xorstate[i]; + } + + // Advance the branched RNG + old_weyl = ref_branched_rng_state.weylstate; + ref_branched_rng.discard(4); + ref_branched_rng_state.weylstate = old_weyl; + } + + // Draw 10 random numbers from the two branched RNGs and compare + for ([[maybe_unused]] auto i : celeritas::range(10)) + { + EXPECT_EQ(ref_branched_rng(), branched_rng()); + } +} + TEST_F(XorwowRngEngineTest, TEST_IF_CELER_DEVICE(device)) { // Create and initialize states diff --git a/test/corecel/sys/Environment.test.cc b/test/corecel/sys/Environment.test.cc index 870c28cb64..e208bee2ca 100644 --- a/test/corecel/sys/Environment.test.cc +++ b/test/corecel/sys/Environment.test.cc @@ -10,6 +10,8 @@ #include "corecel/Config.hh" +#include "corecel/ScopedLogStorer.hh" +#include "corecel/io/Logger.hh" #include "corecel/sys/EnvironmentIO.json.hh" #include "celeritas_test.hh" @@ -57,10 +59,6 @@ TEST(EnvironmentTest, global) getenv_flag("ENVTEST_ZERO", false)); EXPECT_EQ((GetenvFlagResult{true, false}), getenv_flag("ENVTEST_ONE", false)); - EXPECT_EQ((GetenvFlagResult{false, true}), - getenv_flag("ENVTEST_EMPTY", false)); - EXPECT_EQ((GetenvFlagResult{true, true}), - getenv_flag("ENVTEST_EMPTY", true)); EXPECT_EQ((GetenvFlagResult{true, true}), getenv_flag("ENVTEST_NEW_T", true)); EXPECT_EQ((GetenvFlagResult{false, true}), @@ -77,6 +75,73 @@ TEST(EnvironmentTest, global) getenv_flag("ENVTEST_FALSE", false)); EXPECT_EQ((GetenvFlagResult{true, false}), getenv_flag("ENVTEST_TRUE", false)); + + environment().insert({"ENVTEST_AUTO", "auto"}); + EXPECT_EQ((GetenvFlagResult{true, true}), + getenv_flag("ENVTEST_AUTO", true)); + EXPECT_EQ((GetenvFlagResult{false, true}), + getenv_flag("ENVTEST_AUTO", false)); + + { + // Empty should act like auto + ScopedLogStorer scoped_log_{&world_logger()}; + EXPECT_EQ((GetenvFlagResult{false, true}), + getenv_flag("ENVTEST_EMPTY", false)); + EXPECT_EQ((GetenvFlagResult{true, true}), + getenv_flag("ENVTEST_EMPTY", true)); + static char const* const expected_log_messages[] = { + R"(Already-set but empty environment value 'ENVTEST_EMPTY' is being ignored)", + R"(Already-set but empty environment value 'ENVTEST_EMPTY' is being ignored)"}; + EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); + static char const* const expected_log_levels[] = {"warning", "warning"}; + EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); + } + { + // Invalid flag should also act like auto + ScopedLogStorer scoped_log_{&world_logger()}; + environment().insert({"ENVTEST_NOTAFLAG", "notaflag"}); + EXPECT_EQ((GetenvFlagResult{false, true}), + getenv_flag("ENVTEST_NOTAFLAG", false)); + EXPECT_EQ((GetenvFlagResult{true, true}), + getenv_flag("ENVTEST_NOTAFLAG", true)); + static char const* const expected_log_messages[] = { + R"(Invalid environment value ENVTEST_NOTAFLAG=notaflag (expected a flag): using default=0)", + R"(Invalid environment value ENVTEST_NOTAFLAG=notaflag (expected a flag): using default=1)"}; + EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); + static char const* const expected_log_levels[] = {"warning", "warning"}; + EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); + } +} + +TEST(EnvironmentTest, lazy) +{ + environment() = {}; + + // Set did_call to true and return its previous value + bool did_call{false}; + auto get_default_false = [&did_call] { + did_call = true; + return false; + }; + auto get_default_true = [&did_call] { + did_call = true; + return true; + }; + + EXPECT_EQ((GetenvFlagResult{false, false}), + getenv_flag_lazy("ENVTEST_ZERO", get_default_false)); + EXPECT_FALSE(did_call); + EXPECT_EQ((GetenvFlagResult{false, true}), + getenv_flag_lazy("ENVTEST_NEW_F", get_default_false)); + EXPECT_TRUE(did_call); + did_call = false; + EXPECT_EQ((GetenvFlagResult{true, true}), + getenv_flag_lazy("ENVTEST_NEW_T", get_default_true)); + EXPECT_TRUE(did_call); + did_call = false; + EXPECT_EQ((GetenvFlagResult{true, false}), + getenv_flag_lazy("ENVTEST_NEW_T", get_default_true)); + EXPECT_FALSE(did_call); } TEST(EnvironmentTest, global_overrides) @@ -105,7 +170,16 @@ TEST(EnvironmentTest, merge) Environment input; input.insert({"FOO", "foo2"}); input.insert({"BAZ", "baz"}); - sys.merge(input); + { + ScopedLogStorer scoped_log_{&world_logger()}; + sys.merge(input); + static char const* const expected_log_messages[] = { + R"(Ignoring new environment variable FOO=foo2: using existing value 'foo')", + }; + EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); + static char const* const expected_log_levels[] = {"warning"}; + EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); + } std::ostringstream os; os << sys; diff --git a/test/corecel/sys/MultiExceptionHandler.test.cc b/test/corecel/sys/MultiExceptionHandler.test.cc index 3b354de225..2048b337ad 100644 --- a/test/corecel/sys/MultiExceptionHandler.test.cc +++ b/test/corecel/sys/MultiExceptionHandler.test.cc @@ -7,7 +7,9 @@ #include "corecel/sys/MultiExceptionHandler.hh" #include +#include +#include "corecel/Assert.hh" #include "corecel/ScopedLogStorer.hh" #include "corecel/io/Logger.hh" @@ -22,7 +24,15 @@ namespace test class MockContextException : public std::exception { public: - char const* what() const noexcept final { return "some context"; } + explicit MockContextException(std::string msg = "some context") + : msg_(std::move(msg)) + { + } + + char const* what() const noexcept final { return msg_.c_str(); } + + private: + std::string msg_; }; //---------------------------------------------------------------------------// @@ -35,6 +45,7 @@ class MultiExceptionHandlerTest : public ::celeritas::test::Test ScopedLogStorer scoped_log_; }; +// Test that a single error throws as expected TEST_F(MultiExceptionHandlerTest, single) { MultiExceptionHandler capture_exception; @@ -46,11 +57,13 @@ TEST_F(MultiExceptionHandlerTest, single) EXPECT_THROW(log_and_rethrow(std::move(capture_exception)), RuntimeError); } +//! Test that four different messages all show up TEST_F(MultiExceptionHandlerTest, multi) { MultiExceptionHandler capture_exception; - CELER_TRY_HANDLE(CELER_RUNTIME_THROW("runtime", "first exception", ""), - capture_exception); + CELER_TRY_HANDLE( + throw RuntimeError({"runtime", "first exception", "", "test.cc", 0}), + capture_exception); for (auto i : range(3)) { DebugErrorDetails deets{ @@ -60,54 +73,61 @@ TEST_F(MultiExceptionHandlerTest, multi) EXPECT_THROW(log_and_rethrow(std::move(capture_exception)), RuntimeError); static char const* const expected_log_messages[] = { - R"(Suppressed exception from parallel thread: test.cc:0: -celeritas: internal assertion failed: false)", - R"(Suppressed exception from parallel thread: test.cc:1: -celeritas: internal assertion failed: false)", - R"(Suppressed exception from parallel thread: test.cc:2: -celeritas: internal assertion failed: false)", + "[1/4]: runtime error: first exception\ntest.cc: failure", + "[2/4]: test.cc:0:\nceleritas: internal assertion failed: false", + "[3/4]: test.cc:1:\nceleritas: internal assertion failed: false", + "[4/4]: test.cc:2:\nceleritas: internal assertion failed: false", }; EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); static char const* const expected_log_levels[] - = {"critical", "critical", "critical"}; + = {"critical", "critical", "critical", "critical"}; EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); } +//! Test that similar messages are suppressed TEST_F(MultiExceptionHandlerTest, multi_nested) { MultiExceptionHandler capture_exception; CELER_TRY_HANDLE_CONTEXT( - CELER_RUNTIME_THROW("runtime", "first exception", ""), + throw RuntimeError({"runtime", "it just got real", "", "test.cc", 1}), capture_exception, MockContextException{}); - DebugErrorDetails deets{DebugErrorType::internal, "false", "test.cc", 2}; for (auto i = 0; i < 4; ++i) { - CELER_TRY_HANDLE_CONTEXT(throw DebugError(std::move(deets)), - capture_exception, - MockContextException{}); + CELER_TRY_HANDLE_CONTEXT( + throw DebugError({DebugErrorType::internal, "false", "test.cc", 2}), + capture_exception, + MockContextException{"context " + std::to_string(i)}); } EXPECT_THROW(log_and_rethrow(std::move(capture_exception)), MockContextException); static char const* const expected_log_messages[] = { - R"(Suppressed exception from parallel thread: test.cc:2: + R"([1/5]: runtime error: it just got real +test.cc:1: failure + ...from some context)", + R"([2/5]: test.cc:2: celeritas: internal assertion failed: false -... from some context)", - "Suppressed 3 similar exceptions", + ...from context 0)", + "[3-5/5]: identical root cause to exception [2/5]", }; - EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()); - static char const* const expected_log_levels[] = {"critical", "warning"}; + EXPECT_VEC_EQ(expected_log_messages, scoped_log_.messages()) << scoped_log_; + static char const* const expected_log_levels[] + = {"critical", "critical", "critical"}; EXPECT_VEC_EQ(expected_log_levels, scoped_log_.levels()); } -// Failure case can't be tested as part of the rest of the suite -TEST_F(MultiExceptionHandlerTest, DISABLED_uncaught) +// Test that uncaught exceptions terminate the program +TEST_F(MultiExceptionHandlerTest, uncaught) { - MultiExceptionHandler catchme; - CELER_TRY_HANDLE(CELER_VALIDATE(false, << "derp"), catchme); - // Program will terminate when catchme leaves scope + EXPECT_DEATH( + { + MultiExceptionHandler catchme; + CELER_TRY_HANDLE(CELER_VALIDATE(false, << "derp"), catchme); + // Program will terminate when catchme leaves scope + }, + "failed to clear exceptions from MultiExceptionHandler"); } //---------------------------------------------------------------------------// diff --git a/test/geocel/CMakeLists.txt b/test/geocel/CMakeLists.txt index 03d2208aab..a06a425de9 100644 --- a/test/geocel/CMakeLists.txt +++ b/test/geocel/CMakeLists.txt @@ -81,6 +81,7 @@ if(CELERITAS_USE_VecGeom) "TwoBoxesVgdml" ) set(_g4vg_tests + "AtlasHgtd" "CmsEeBackDee" "Cmse" "FourLevels" diff --git a/test/geocel/GenericGeoTestInterface.cc b/test/geocel/GenericGeoTestInterface.cc index 623e57a523..438f8ac37e 100644 --- a/test/geocel/GenericGeoTestInterface.cc +++ b/test/geocel/GenericGeoTestInterface.cc @@ -8,6 +8,7 @@ #include +#include "corecel/Types.hh" #include "corecel/io/Logger.hh" #include "corecel/math/ArrayOperators.hh" #include "corecel/math/ArrayUtils.hh" @@ -26,6 +27,7 @@ namespace celeritas { namespace test { + //---------------------------------------------------------------------------// /*! * Track until exiting the geometry. @@ -35,6 +37,7 @@ namespace test */ auto GenericGeoTestInterface::track(Real3 const& pos, Real3 const& dir, + TrackingTol const& tol, int remaining_steps) -> TrackingResult { TrackingResult result; @@ -86,7 +89,6 @@ auto GenericGeoTestInterface::track(Real3 const& pos, // Convert from Celeritas native unit system to unit test's internal system auto from_native_length = [scale = unit_length.value](auto&& v) { return v / scale; }; - auto const tol = this->tracking_tol(); while (!geo.is_outside()) { @@ -143,8 +145,15 @@ auto GenericGeoTestInterface::track(Real3 const& pos, result.volumes.back() += "/" + this->volume_name(geo); } GGTI_EXPECT_NO_THROW(next = geo.find_next_step()); - EXPECT_SOFT_NEAR(next.distance, half_distance, tol.distance) - << "reinitialized distance mismatch at index " + real_type length_scale = half_distance; + for (auto x : geo.pos()) + { + length_scale = max(length_scale, std::fabs(x)); + } + EXPECT_NEAR( + next.distance, half_distance, tol.distance * length_scale) + << "reinitialized distance mismatch (length scale=" + << length_scale << ") at index " << result.volumes.size() - 1 << ": " << geo; } } @@ -174,6 +183,14 @@ auto GenericGeoTestInterface::track(Real3 const& pos, return result; } +// Track until exiting the geometry (default test tol) +auto GenericGeoTestInterface::track(Real3 const& pos_cm, + Real3 const& dir, + int max_steps) -> TrackingResult +{ + return this->track(pos_cm, dir, this->tracking_tol(), max_steps); +} + //---------------------------------------------------------------------------// /*! * Get the volume instance stack at a position. @@ -181,20 +198,24 @@ auto GenericGeoTestInterface::track(Real3 const& pos, auto GenericGeoTestInterface::volume_stack(Real3 const& pos) -> VolumeStackResult { + VolumeStackResult result; + CheckedGeoTrackView geo{this->make_geo_track_view_interface()}; - geo = this->make_initializer(pos, Real3{0, 0, 1}); + GGTI_EXPECT_NO_THROW(geo = this->make_initializer(pos, Real3{0, 0, 1})); auto vlev = geo.volume_level(); if (!vlev) { - return {}; + return result; } std::vector inst_ids(vlev.get() + 1); geo.volume_instance_id(make_span(inst_ids)); - return VolumeStackResult::from_span( + result = VolumeStackResult::from_span( this->get_test_volumes()->volume_instance_labels(), make_span(inst_ids)); + + return result; } //---------------------------------------------------------------------------// diff --git a/test/geocel/GenericGeoTestInterface.hh b/test/geocel/GenericGeoTestInterface.hh index 6e47a3eb0c..7587d4ab4a 100644 --- a/test/geocel/GenericGeoTestInterface.hh +++ b/test/geocel/GenericGeoTestInterface.hh @@ -43,6 +43,7 @@ class GenericGeoTestInterface : public LazyGeantGeoManager //!@{ //! \name Type aliases using TrackingResult = GenericGeoTrackingResult; + using TrackingTol = GenericGeoTrackingTolerance; using VolumeStackResult = GenericGeoVolumeStackResult; using GeoTrackView = GeoTrackInterface; using UPGeoTrack = std::unique_ptr; @@ -52,6 +53,12 @@ class GenericGeoTestInterface : public LazyGeantGeoManager //// TESTS //// // Track until exiting the geometry + TrackingResult track(Real3 const& pos_cm, + Real3 const& dir, + TrackingTol const& tol, + int max_steps); + + // Track until exiting the geometry (default test tol) TrackingResult track(Real3 const& pos_cm, Real3 const& dir, int max_steps = 50); diff --git a/test/geocel/GeoTests.cc b/test/geocel/GeoTests.cc index 6499e67f94..1f5c6bae4c 100644 --- a/test/geocel/GeoTests.cc +++ b/test/geocel/GeoTests.cc @@ -12,6 +12,7 @@ #include "corecel/cont/Range.hh" #include "corecel/io/Logger.hh" #include "corecel/math/ArrayOperators.hh" +#include "corecel/math/ArrayUtils.hh" #include "corecel/math/Turn.hh" #include "corecel/sys/Version.hh" #include "geocel/BoundingBox.hh" @@ -89,6 +90,294 @@ void delete_orange_safety(GenericGeoTestInterface const& interface, //---------------------------------------------------------------------------// } // namespace +//---------------------------------------------------------------------------// +// ATLAS HGTD +//---------------------------------------------------------------------------// +void AtlasHgtdGeoTest::test_trace() const +{ + auto tol = test_->tracking_tol(); + if (CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_FLOAT) + { + // Tracking errors at most distances: 5e-6 ~ 2e-4 + tol.distance = 5e-4; + } + { + SCOPED_TRACE("+z"); + auto result = test_->track({12.5, 0, -2600}, {0, 0, 1}, tol, 50); + GenericGeoTrackingResult ref; + ref.volumes = { + "Atlas", "HGTD", "SPlate", "HGTD", "SPlate", "HGTD", "SPlate", + "HGTD", "SPlate", "HGTD", "ITK", "HGTD", "SPlate", "HGTD", + "SPlate", "HGTD", "SPlate", "HGTD", "SPlate", "HGTD", "Atlas", + }; + ref.volume_instances = { + "Atlas_PV", "HGTD", "SPlate_7@1", "HGTD", "SPlate_6@1", "HGTD", + "SPlate_5@1", "HGTD", "SPlate_4@1", "HGTD", "ITK", "HGTD", + "SPlate_4@0", "HGTD", "SPlate_5@0", "HGTD", "SPlate_6@0", "HGTD", + "SPlate_7@0", "HGTD", "Atlas_PV", + }; + ref.distances = { + 2245.5, 6.75, 0.1, 0.6, 0.1, 1.7, 0.1, 0.6, 0.1, 2.45, 684, + 2.45, 0.1, 0.6, 0.1, 1.7, 0.1, 0.6, 0.1, 6.75, 2250.1, + }; + ref.halfway_safeties = { + 771.8918204645, + 1.5, + 0.05, + 0.3, + 0.05, + 0.85, + 0.05, + 0.3, + 0.05, + 1.225, + 9.62, + 1.225, + 0.05, + 0.3, + 0.05, + 0.85, + 0.05, + 0.3, + 0.05, + 1.5, + 769.72940862358, + }; + ref.bumps = {}; + delete_orange_safety(*test_, ref, result); + if (test_->geometry_type() == "VecGeom" && CELERITAS_VECGEOM_SURFACE) + { + // World safety differs + ref.halfway_safeties[0] = 725.849243164062; + ref.halfway_safeties[20] = 723.549255371094; + } + + EXPECT_REF_NEAR(ref, result, tol); + } + { + SCOPED_TRACE("+z near trouble"); + auto result = test_->track({24, 18, 300}, {0, 0, 1}, tol, 50); + GenericGeoTrackingResult ref; + ref.volumes = { + "ITK", + "HGTD", + "SPlate", + "HGTD", + "SPlate", + "HGTD", + "SPlate", + "HGTD", + "SPlate", + "HGTD", + "Atlas", + }; + ref.volume_instances = { + "ITK", + "HGTD", + "SPlate_4@0", + "HGTD", + "SPlate_5@0", + "HGTD", + "SPlate_6@0", + "HGTD", + "SPlate_7@0", + "HGTD", + "Atlas_PV", + }; + ref.distances = { + 42, // enter HGTD at z=342 + 2.45, // enter HGTDSupportPlate at z=344.45 + 0.1, // leave into HGTD at z=344.55 + 0.6, + 0.1, + 1.7, + 0.1, + 0.6, + 0.1, + 6.75, + 2250.1, + }; + ref.halfway_safeties = { + 21, + 1.225, + 0.05, + 0.3, + 0.05, + 0.85, + 0.05, + 0.3, + 0.05, + 3.375, + 763.93626206641, + }; + ref.bumps = {}; + delete_orange_safety(*test_, ref, result); + if (test_->geometry_type() == "VecGeom" && CELERITAS_VECGEOM_SURFACE) + { + // World safety differs + ref.halfway_safeties[10] = 723.549255371094; + } + EXPECT_REF_NEAR(ref, result, tol); + } + + { + // See https://github.com/celeritas-project/celeritas/issues/1902 + // in HGTD::HGTDSupportPlate, on boundary, taking small step + SCOPED_TRACE("tangent at far away point"); + + auto tol = test_->tracking_tol(); + tol.distance = 1e-7; + + Real3 pos{24.097769534015998, 17.956803215217408, 344.45}; + Real3 dir{ + 0.5784236876658104, 0.8157365000698582, -9.290358099212079e-7}; + axpy(real_type{-1}, dir, &pos); + + if (test_->geometry_type() == "VecGeom" && !CELERITAS_VECGEOM_SURFACE) + { + GTEST_SKIP() << "VecGeom fails the tangent trace"; + } + else if (test_->geometry_type() == "ORANGE" + && CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_FLOAT) + { + // Initialization is exactly on a surface with single precision + auto up_view = test_->make_geo_track_view_interface(); + *up_view = test_->make_initializer(pos, dir); + EXPECT_TRUE(up_view->failed()); + GTEST_SKIP() << "ORANGE single precision starts on a boundary"; + } + + auto result = test_->track(pos, dir, tol, /* max steps = */ 10); + + GenericGeoTrackingResult ref; + ref.volumes = {"SPlate", "HGTD", "ITK", "Atlas"}; + ref.volume_instances = {"SPlate_4@0", "HGTD", "ITK", "Atlas_PV"}; + ref.distances = { + 1.0000000238587, 81.021892625066, 4.8164185705351, 1305.6446868933}; + ref.halfway_safeties = { + 4.6451791604341e-07, + 2.4499623638801, + 2.3998244128449, + 652.50340341824, + }; + ref.dot_normal + = {9.2903580992121e-07, 0.99644211932289, 0.99673389979137}; + ref.bumps = {}; + delete_orange_safety(*test_, ref, result); + EXPECT_REF_NEAR(ref, result, tol); + } +} + +//---------------------------------------------------------------------------// +void AtlasHgtdGeoTest::test_volume_stack() const +{ + if (test_->geometry_type() == "ORANGE" + && CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_FLOAT) + { + GTEST_SKIP() << "Track starts on a boundary with single precision"; + } + + std::vector all_stacks; + + Real3 const dir{ + 0.5784236876658104, 0.8157365000698582, -9.290358099212079e-7}; + + for (real_type dx : {-1.0, -1e-3, -1e-5, 1e-5, 1e-3, 1.0}) + { + // Near z=344.45 is inside HGTDSupportPlate + Real3 pos{24.097769534015998, 17.956803215217408, 344.45}; + axpy(dx, dir, &pos); + + auto result = test_->volume_stack(pos); + all_stacks.emplace_back(to_string(join(result.volume_instances.begin(), + result.volume_instances.end(), + ","))); + } + + // NOTE: Geant4 returns SPlate for dx=1e-6, whereas VecGeom returns HGTD + std::vector expected_all_stacks = { + "Atlas_PV,ITK,HGTD,SPlate_4@0", + "Atlas_PV,ITK,HGTD,SPlate_4@0", + "Atlas_PV,ITK,HGTD,SPlate_4@0", + "Atlas_PV,ITK,HGTD", + "Atlas_PV,ITK,HGTD", + "Atlas_PV,ITK,HGTD", + }; + if (test_->geometry_type() == "Geant4") + { + // Geant4 navigation (probably "skin" checks) overpredicts the + // volume extents + expected_all_stacks[3] = expected_all_stacks.front(); + } + if (test_->geometry_type() == "VecGeom" && vecgeom_version >= Version{2}) + { + // VecGeom surface overpredicts even more + expected_all_stacks[3] = expected_all_stacks.front(); + expected_all_stacks[4] = expected_all_stacks.front(); + if (CELERITAS_VECGEOM_SURFACE) + { + expected_all_stacks[5] = expected_all_stacks.front(); + } + } + + EXPECT_VEC_EQ(expected_all_stacks, all_stacks); +} + +void AtlasHgtdGeoTest::test_detailed_tracking() const +{ + if (test_->geometry_type() == "ORANGE" + && CELERITAS_REAL_TYPE == CELERITAS_REAL_TYPE_FLOAT) + { + GTEST_SKIP() << "Track starts on a boundary with single precision"; + } + + { + // See https://github.com/celeritas-project/celeritas/issues/1902 + SCOPED_TRACE("almost tangent at large Z"); + auto geo = make_geo_track_view( + *test_, + {23.51934584635, 17.141066715148, 344.45000092904}, + {0.5784236876658104, 0.8157365000698582, -9.290358099212079e-7}); + ASSERT_FALSE(geo.is_outside()); + EXPECT_EQ("SPlate", test_->volume_name(geo)); + EXPECT_FALSE(geo.is_on_boundary()); + + // Find next boundary + auto next = geo.find_next_step(from_cm(2.0)); + EXPECT_SOFT_NEAR(1.0, to_cm(next.distance), 1e-5); + EXPECT_TRUE(next.boundary); + geo.move_to_boundary(); + EXPECT_SOFT_EQ(344.45, to_cm(geo.pos()[2])); + EXPECT_EQ("SPlate", test_->volume_name(geo)); + EXPECT_TRUE(geo.is_on_boundary()); + geo.cross_boundary(); + if (test_->geometry_type() == "VecGeom" && vecgeom_version < Version{2}) + { + // VecGeom fails to cross the boundary! the internal bump along the + // path of travel doesn't change the Z coordinate, so it assumes + // the updated point is still inside the original volume. + EXPECT_EQ("SPlate", test_->volume_name(geo)); + return; + } + EXPECT_EQ("HGTD", test_->volume_name(geo)); + EXPECT_TRUE(geo.is_on_boundary()); + + // Suppose safety distance results in small step limit + next = geo.find_next_step(from_cm(5e-9)); + EXPECT_SOFT_EQ(5e-9, to_cm(next.distance)); + geo.move_internal(from_cm(5e-9)); + EXPECT_FALSE(geo.is_on_boundary()); + EXPECT_EQ("HGTD", test_->volume_name(geo)); + + // Should be able to continue stepping as normal + next = geo.find_next_step(from_cm(1e-6)); + EXPECT_SOFT_EQ(1e-6, to_cm(next.distance)); + EXPECT_FALSE(next.boundary); + EXPECT_FALSE(geo.is_on_boundary()); + EXPECT_EQ("HGTD", test_->volume_name(geo)); + } +} + //---------------------------------------------------------------------------// // CMS EE //! Test geometry accessors diff --git a/test/geocel/GeoTests.hh b/test/geocel/GeoTests.hh index 99141f5dff..8b5ee75528 100644 --- a/test/geocel/GeoTests.hh +++ b/test/geocel/GeoTests.hh @@ -24,6 +24,26 @@ constexpr bool using_surface_vg = CELERITAS_VECGEOM_SURFACE; constexpr bool using_solids_vg = CELERITAS_VECGEOM_VERSION && !CELERITAS_VECGEOM_SURFACE; +//---------------------------------------------------------------------------// +/*! + * Test the ATLAS HGTD (translated distant pancakes). + */ +class AtlasHgtdGeoTest +{ + public: + static std::string_view gdml_basename() { return "atlas-hgtd"; } + + //! Construct with a reference to the GoogleTest + AtlasHgtdGeoTest(GenericGeoTestInterface* geo_test) : test_{geo_test} {} + + void test_trace() const; + void test_volume_stack() const; + void test_detailed_tracking() const; + + private: + GenericGeoTestInterface* test_; +}; + //---------------------------------------------------------------------------// /*! * Test the CMS EE (reflecting) geometry. diff --git a/test/geocel/data/atlas-hgtd.gdml b/test/geocel/data/atlas-hgtd.gdml new file mode 100644 index 0000000000..221dd77ee4 --- /dev/null +++ b/test/geocel/data/atlas-hgtd.gdml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/geocel/g4/GeantGeo.test.cc b/test/geocel/g4/GeantGeo.test.cc index 864cc334a1..e405e2f909 100644 --- a/test/geocel/g4/GeantGeo.test.cc +++ b/test/geocel/g4/GeantGeo.test.cc @@ -106,6 +106,62 @@ class GeantGeoTest : public GeantGeoTestBase } }; +//---------------------------------------------------------------------------// +using AtlasHgtdTest + = GenericGeoParameterizedTest; + +TEST_F(AtlasHgtdTest, model) +{ + auto result = this->summarize_model(); + GenericGeoModelInp ref; + ref.volume.labels = {"SPlate", "HGTD", "ITK", "Atlas"}; + ref.volume.materials = {0, 1, 1, 1}; + ref.volume.daughters = {{}, {3, 4, 5, 6, 7, 8, 9, 10}, {2}, {1}}; + ref.volume_instance.labels = { + "Atlas_PV", + "ITK", + "HGTD", + "SPlate_4@0", + "SPlate_5@0", + "SPlate_6@0", + "SPlate_7@0", + "SPlate_4@1", + "SPlate_5@1", + "SPlate_6@1", + "SPlate_7@1", + }; + ref.volume_instance.volumes = { + 3, + 2, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }; + ref.world = "Atlas"; + EXPECT_REF_EQ(ref, result); +} + +TEST_F(AtlasHgtdTest, trace) +{ + this->impl().test_trace(); +} + +TEST_F(AtlasHgtdTest, volume_stack) +{ + this->impl().test_volume_stack(); +} + +TEST_F(AtlasHgtdTest, detailed_track) +{ + this->impl().test_detailed_tracking(); +} + //---------------------------------------------------------------------------// using CmseTest = GenericGeoParameterizedTest; diff --git a/test/geocel/vg/Vecgeom.test.cc b/test/geocel/vg/Vecgeom.test.cc index 7efa23db61..885291fcc2 100644 --- a/test/geocel/vg/Vecgeom.test.cc +++ b/test/geocel/vg/Vecgeom.test.cc @@ -119,6 +119,26 @@ using GeantVecgeomTest = VecgeomTestBase; //---------------------------------------------------------------------------// +using AtlasHgtdTest + = GenericGeoParameterizedTest; + +TEST_F(AtlasHgtdTest, trace) +{ + this->impl().test_trace(); +} + +TEST_F(AtlasHgtdTest, volume_stack) +{ + this->impl().test_volume_stack(); +} + +TEST_F(AtlasHgtdTest, detailed_track) +{ + this->impl().test_detailed_tracking(); +} + +//---------------------------------------------------------------------------// + using CmsEeBackDeeTest = GenericGeoParameterizedTest; diff --git a/test/larceler/LarStandaloneRunner.test.cc b/test/larceler/LarStandaloneRunner.test.cc index 4efc5a17a8..404edc65da 100644 --- a/test/larceler/LarStandaloneRunner.test.cc +++ b/test/larceler/LarStandaloneRunner.test.cc @@ -68,7 +68,7 @@ auto LarSphereTest::make_input() const -> Input { Input result; result.geometry = this->test_data_path("geocel", "lar-sphere.gdml"); - result.optical_step_iters = 10; + result.tracking_limits.steps = 10; result.optical_capacity.tracks = 16; return result; } @@ -103,6 +103,7 @@ TEST_F(LarSphereTest, single_photon) /* origTrackID = */ 123); auto response = run({sed}); + EXPECT_EQ(0, response.size()); } //---------------------------------------------------------------------------// diff --git a/test/orange/OrangeGeant.test.cc b/test/orange/OrangeGeant.test.cc index 7bbbc8dd78..9f059b8cd6 100644 --- a/test/orange/OrangeGeant.test.cc +++ b/test/orange/OrangeGeant.test.cc @@ -59,6 +59,26 @@ class GeantOrangeTest : public OrangeTestBase } }; +//---------------------------------------------------------------------------// + +using AtlasHgtdTest + = GenericGeoParameterizedTest; + +TEST_F(AtlasHgtdTest, trace) +{ + this->impl().test_trace(); +} + +TEST_F(AtlasHgtdTest, volume_stack) +{ + this->impl().test_volume_stack(); +} + +TEST_F(AtlasHgtdTest, detailed_track) +{ + this->impl().test_detailed_tracking(); +} + //---------------------------------------------------------------------------// using FourLevelsTest = GenericGeoParameterizedTest; diff --git a/test/testdetail/TestMacrosImpl.hh b/test/testdetail/TestMacrosImpl.hh index 89200d33e6..7390ffd8a4 100644 --- a/test/testdetail/TestMacrosImpl.hh +++ b/test/testdetail/TestMacrosImpl.hh @@ -281,16 +281,25 @@ struct IsContainer : std::false_type { }; +template<> +struct IsContainer : std::false_type +{ +}; + template struct IsContainer> : std::true_type { }; -template +template struct IsContainer : std::true_type { }; +//! Whether a type is a container +template +inline constexpr bool IsContainer_v = IsContainer::value; + //---------------------------------------------------------------------------// /*! * Get the type of a container. @@ -310,6 +319,12 @@ struct ValueType template using ValueTypeT = typename ValueType::type; +//---------------------------------------------------------------------------// + +//! Whether a container *holds* another container +template +inline constexpr bool IsNestedContainer_v = IsContainer_v>; + //---------------------------------------------------------------------------// /*! * Recursively get the underlying scalar type of a container. @@ -321,7 +336,7 @@ struct ScalarValueType }; template -struct ScalarValueType::value>> +struct ScalarValueType>> { using type = typename ScalarValueType>::type; }; @@ -403,8 +418,8 @@ template char const* actual_expr, BinaryOp comp) { - if constexpr (IsContainer>::value - && IsContainer>::value) + if constexpr (IsNestedContainer_v + && IsNestedContainer_v) { // Handle nested containers recursively auto exp_size = std::distance(std::begin(expected), std::end(expected)); @@ -598,8 +613,8 @@ template ContainerE const& expected, ContainerA const& actual) { - if constexpr (IsContainer>::value - && IsContainer>::value) + if constexpr (IsNestedContainer_v + && IsNestedContainer_v) { // Handle nested containers recursively auto exp_size = std::distance(std::begin(expected), std::end(expected)); @@ -722,7 +737,7 @@ template * Compare two vectors of reference values using a tolerance. */ template -std::enable_if_t::value && IsContainer::value, +std::enable_if_t && IsContainer_v, ::testing::AssertionResult> IsRefEq(char const* expr1, char const* expr2, @@ -784,7 +799,7 @@ IsRefEq(char const* expr1, * Compare two vectors of reference values without a special tolerance. */ template -std::enable_if_t::value && IsContainer::value, +std::enable_if_t && IsContainer_v, ::testing::AssertionResult> IsRefEq(char const* expr1, char const* expr2,