diff --git a/.clang-format b/.clang-format index b3bb8d132..5839c7261 100644 --- a/.clang-format +++ b/.clang-format @@ -100,6 +100,8 @@ IncludeCategories: Priority: 2 - Regex: '^"utils\/.*.h"' Priority: 2 + - Regex: '^"traits\/.*.h"' + Priority: 2 - Regex: '^"metrics\/.*\.h"' Priority: 3 - Regex: '^"framework\/.*\.h"' @@ -108,6 +110,10 @@ IncludeCategories: Priority: 4 - Regex: '^"checkpoint\/.*\.h"' Priority: 4 + - Regex: '^"kernels\/.*\.hpp"' + Priority: 4 + - Regex: '^"kernels\/.*\.h"' + Priority: 4 - Regex: '^"output\/.*\.h"' Priority: 4 - Regex: '^"archetypes\/.*\.h"' diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..788e361f6 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,36 @@ +Checks: > + -*, + bugprone-*, + performance-*, + clang-analyzer-*, + clang-diagnostic-*, + misc-include-cleaner, + misc-const-correctness, + misc-unused-parameters, + misc-unused-using-decls, + cppcoreguidelines-pro-type-member-init, + cppcoreguidelines-slicing, + cppcoreguidelines-virtual-class-destructor, + cppcoreguidelines-prefer-member-initializer, + modernize-use-nullptr, + modernize-use-override, + modernize-use-default-member-init, + modernize-use-emplace, + modernize-loop-convert, + modernize-make-unique, + modernize-make-shared, + modernize-use-using, + readability-container-size-empty, + readability-redundant-*, + readability-simplify-boolean-expr, + readability-misleading-indentation, + readability-inconsistent-declaration-parameter-name, + -misc-const-correctness, + -bugprone-easily-swappable-parameters, + -bugprone-unchecked-optional-access +WarningsAsErrors: "*" +HeaderFilterRegex: "src/.*" +FormatStyle: file +SystemHeaders: false +CheckOptions: + misc-include-cleaner.IgnoreHeaders: 'adios2\.h;adios2/.*;Kokkos_.*\.hpp' diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..262274baf --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/pgens-tests.yml b/.github/workflows/pgens-tests.yml new file mode 100644 index 000000000..c9e07fb0b --- /dev/null +++ b/.github/workflows/pgens-tests.yml @@ -0,0 +1,70 @@ +name: Premerge compilation on CPUs for all pgens + +on: + push: + pull_request: + types: [ready_for_review] + +jobs: + check-commit: + runs-on: ubuntu-24.04 + outputs: + run_tests: ${{ steps.check_message.outputs.run_tests }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Check commit message + id: check_message + run: | + if [[ "${{ github.event_name }}" == "pull_request" && "${{ github.event.action }}" == "ready_for_review" ]]; then + echo "run_tests=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + if git log -1 --pretty=%B | grep -q "RUNPGENS"; then + echo "run_tests=true" >> "$GITHUB_OUTPUT" + else + echo "run_tests=false" >> "$GITHUB_OUTPUT" + fi + pgens: + needs: check-commit + if: needs.check-commit.outputs.run_tests == 'true' + name: PGENS (mpi=${{ matrix.mpi }}) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + mpi: [ON, OFF] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install GCC 14 and build tools + run: | + sudo apt-get update + sudo apt-get install -y gcc-14 g++-14 gfortran-14 build-essential wget libevent-dev libhwloc-dev + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 50 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 50 + sudo update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-14 50 + gcc --version + g++ --version + - name: Install CMake (3.x) + run: | + sudo apt-get install -y cmake + cmake --version + - name: Install OpenMPI 5 + run: | + if [ "${{ matrix.mpi }}" = "ON" ]; then + sudo apt-get install -y libopenmpi-dev openmpi-bin + mpirun --version + fi + - name: Compile all pgens + run: | + if [ "${{ matrix.mpi }}" = "ON" ]; then + export CC=mpicc + export CXX=mpicxx + else + export CC=gcc-14 + export CXX=g++-14 + fi + ./dev/scripts/tests.sh --build build --with_pgens --flags "-D mpi=${{ matrix.mpi }} -D output=OFF" diff --git a/.github/workflows/cpuarch.yml b/.github/workflows/unit-tests.yml similarity index 50% rename from .github/workflows/cpuarch.yml rename to .github/workflows/unit-tests.yml index 672d26395..1a12b589a 100644 --- a/.github/workflows/cpuarch.yml +++ b/.github/workflows/unit-tests.yml @@ -1,7 +1,9 @@ -name: CPU Compilation/Unit Tests +name: Premerge tests on CPUs with different configurations on: push: + pull_request: + types: [ready_for_review] jobs: check-commit: @@ -14,7 +16,11 @@ jobs: - name: Check commit message id: check_message run: | - if git log -1 --pretty=%B | grep -q "CPUTEST"; then + if [[ "${{ github.event_name }}" == "pull_request" && "${{ github.event.action }}" == "ready_for_review" ]]; then + echo "run_tests=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + if git log -1 --pretty=%B | grep -q "RUNTESTS"; then echo "run_tests=true" >> "$GITHUB_OUTPUT" else echo "run_tests=false" >> "$GITHUB_OUTPUT" @@ -22,7 +28,7 @@ jobs: tests: needs: check-commit if: needs.check-commit.outputs.run_tests == 'true' - name: UNIT_TESTS (precision=${{ matrix.precision }}, mpi=${{ matrix.mpi }}, output=${{ matrix.output }}) + name: UNIT_TESTS with (precision=${{ matrix.precision }}, mpi=${{ matrix.mpi }}, output=${{ matrix.output }}) runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -57,9 +63,11 @@ jobs: cmake --version - name: Install OpenMPI 5 run: | - sudo apt-get install -y libopenmpi-dev openmpi-bin - mpirun --version - - name: Configure + if [ "${{ matrix.mpi }}" = "ON" ]; then + sudo apt-get install -y libopenmpi-dev openmpi-bin + mpirun --version + fi + - name: Compile and run all tests run: | if [ "${{ matrix.mpi }}" = "ON" ]; then export CC=mpicc @@ -68,40 +76,4 @@ jobs: export CC=gcc-14 export CXX=g++-14 fi - cmake -B build -D TESTS=ON -D precision=${{ matrix.precision }} -D mpi=${{ matrix.mpi }} -D output=${{ matrix.output }} - - name: Compile - run: cmake --build build -j "$(nproc)" - - name: Run tests - run: ctest --test-dir build --output-on-failure - pgens: - needs: check-commit - if: needs.check-commit.outputs.run_tests == 'true' - name: PGENS (pgen=${{ matrix.pgen }}) - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - pgen: [streaming, turbulence, reconnection, shock, magnetosphere, accretion, wald] - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install GCC 14 and build tools - run: | - sudo apt-get update - sudo apt-get install -y gcc-14 g++-14 gfortran-14 build-essential wget libevent-dev libhwloc-dev - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 50 - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 50 - sudo update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-14 50 - gcc --version - g++ --version - - name: Install CMake (3.x) - run: | - sudo apt-get install -y cmake - cmake --version - - name: Configure - run: | - cmake -B build -D pgen=${{ matrix.pgen }} -D output=OFF - - name: Compile - run: cmake --build build -j "$(nproc)" + ./dev/scripts/tests.sh --build build --with_tests --flags "-D precision=${{ matrix.precision }} -D mpi=${{ matrix.mpi }} -D output=${{ matrix.output }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 52d372f40..e683ed8c5 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ temp*/ logs/ *.log *.bak +temp* +tmp* # Trash files .trash/ @@ -40,6 +42,7 @@ venv/ *.mov *.mp4 !pgens/**/*.png +!pgens/**/*.mp4 # Accidental files *.xc @@ -59,3 +62,6 @@ tags action-token *.vim ignore-* +tombi/ +tidy/ +.claude \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 835fbe5b8..f66f13275 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "extern/plog"] - path = extern/plog - url = https://github.com/SergiusTheBest/plog.git [submodule "extern/adios2"] path = extern/adios2 url = https://github.com/ornladios/ADIOS2.git diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..dd36d3fa3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,122 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is an astrophysical particle-in-cell plasma simulation code which works in arbitrary curvilinear coordinates and supports multiple simulation engines. It is built using the Kokkos performance portability library with C++20. It is parallelized with MPI, and uses the ADIOS2 library for outputting and checkpointing the simulation data. + +## Repository Structure + +``` +entity +├── cmake # additional cmake files +│ ├── adios2Config.cmake # default configurations for in-tree build of adios2 +│ ├── config.cmake # compile-time configuration options +│ ├── defaults.cmake # default values for configurations +│ ├── dependencies.cmake # functions to fetch and build the dependencies +│ ├── kokkosConfig.cmake # default configurations for in-tree build of Kokkos +│ ├── report.cmake # configuration repoting +│ ├── styling.cmake # styling functions +│ └── tests.cmake # root cmake for tests +├── dev # developer-specific tools +│ ├── nix # nix-shells +│ ├── runners # dockerfiles for github runners on different architectures +│ ├── scripts # developer-specific scripts +│ ├── Dockerfile.common # parent docker environment for development +│ ├── Dockerfile.cuda # cuda docker environment +│ ├── Dockerfile.rocm # rocm docker environment +│ ├── welcome.cuda +│ └── welcome.rocm +├── extern # git submodules +│ ├── Kokkos +│ ├── adios2 +│ └── entity-pgens +├── include # included header-only third-party libraries +│ ├── plog +│ └── toml11 +├── minimal # set of minimalist programs for testing MPI/Kokkos/adios2 +├── pgens # problem generators +├── src # main code containing all separate submodules +│ ├── archetypes # archetypes which can be used by the user in problem generators +│ ├── engines # simulation engines +│ ├── framework # main structures, classes and containers +│ ├── global # global definitions and utilities +│ ├── kernels # core kernels (defined as functors) +│ ├── metrics # various metric classes +│ ├── output # functions related to output +│ ├── CMakeLists.txt +│ └── entity.cpp # main entry-point +├── tests # unit tests for all submodules +├── .clang-format # code formatting guidelines for clang-format +├── .clang-tidy # configurations for the clang-tidy +├── .gitattributes +├── .gitignore +├── .gitmodules +├── .taplo.toml # formatting guidelines for toml files +├── CITATION +├── CLAUDE.md +├── CMakeLists.txt # root cmake file +├── CODE_OF_CONDUCT.md +├── LICENSE +├── README.md +├── dependencies.py # deployment scripts on various machines +└── input.example.toml # most complete toml file with all possible input options +``` + +## Testing + +The code is tested using the `./dev/scripts/tests.sh` script which compiles and runs all the unit tests using `ctest`: + +```sh +./dev/scripts/tests.sh --build build_dir --flags "-D mpi=ON" --with_tests +``` + +All the unit tests are inside the `tests/` directory each within the respective subdirectory; e.g., tests for `src/kernels` are in `tests/kernels`. When testing, build the tests both with and without MPI and, ideally, with and without GPU (when available). + +You can also compile all the problem generators: + +```sh +./dev/scripts/tests.sh --build build_dir --flags "-D mpi=ON" --with_pgens +``` + +## Code guidelines + +* Format of the code is enforced using `clang-format` and `cmake-format`. You can run the formatting on all files with `./dev/scripts/format.sh`. + +* Best practices are also enforced using `clang-tidy`; to generate recommendations for all the files, run `./dev/scripts/tidy.sh --build build_dir` where `build_dir` is the directory where the code was built, or for specific files: `./dev/scripts/tidy.sh --build build_dir --files "(file1|file2).cpp"` or only for the changed files: `./dev/scripts/tidy.sh --build build_dir --changed`. The recommendations will be in the `tidy/` directory. + +* Use `const` and `auto` declarations where possible. + +* For real-valued literals, use `ONE`, `ZERO`, `HALF` etc. instead of `1.0`, `0.0`, `0.5` to ensure the compiler will not need to cast. If the value is not defined as a macro, use `static_cast(123.4)`. + +* Use {} in declarations to signify a null (placeholder) value for the given variable: + ```cpp + auto a { -1 }; // <- value of `a` *will* be changed later (-1 is a placeholder) + auto b = -1; // <- value of `b` is known at the time of declaration (but *may* change later) + const auto b = -1; // <- value of `b` is not expected to change later + ``` + +* Each header file has to have a description at the top, consisting of the following fields: + + * `@file` [required] the name of the file (as it should be included in other files) + * `@brief` [required] brief description of what the file contains + * `@implements` list of class/function/macros implementations + * structs/classes in this section have no prefix (templates are marked with <>) + * functions are marked with their return type, e.g. -> void + * type aliases have a prefix type + * enums or enum-like objects are marked with enum + * macros have a prefix macro + * all of the above are also marked with their respective namespaces (if any): namespace:: + * `@cpp`: list of cpp files that implement the header + * `@namespaces`: list of namespaces defined in the file + * `@macros`: list of macros that the file depends on + * `@note` any additional notes (stack as many as necessary) + +* `#ifdef`/`#define` macros should be avoided. Use C++20 concept and `if constexpr ()` expressions to specialize functions and classes instead (ideally, specialize them explicitly). `#ifdef`-s are only acceptable in platform/library-specific parts of the code (e.g., `MPI_ENABLED`, `GPU_ENABLED`, `DEBUG`, etc.), or for major shortcuts. + +* Header files should start with `#ifndef` ... `#define` ... and end with `#endif`; do not use `#pragma` guards. The name of the macro should be the same as the name of the file in uppercase, with underscores instead of dots and slashes. For example, for `global/utils/formatting.h`, the macro should be `GLOBAL_UTILS_FORMATTING_H`. + +* There is no difference between `.h` and `.hpp` files as both indicate C++ header files. As a consistency convention, we use `.h` for common headers which may be included from multiple `.cpp` files (e.g., metrics), while `.hpp` are very specific headers for only a single (or a couple of) .cpp file (e.g. kernels). + +* Do assertions on parameters and quantities whenever possible. Outside the kernels, use `raise::Error(message, HERE)` and `raise::ErrorIf(condition, message, HERE)` to throw exceptions. Inside the kernels, use `raise::KernelError(HERE, message, **args)`. To enable compile-time errors, use `static_assert(condition, message)`. The `HERE` keyword is macro that includes the filename and line number in the error message. \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index fad95b106..cd5f0c258 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(PROJECT_NAME entity) project( ${PROJECT_NAME} - VERSION 1.3.3 + VERSION 1.4.0 LANGUAGES CXX C) add_compile_options("-D ENTITY_VERSION=\"${PROJECT_VERSION}\"") set(hash_cmd "git diff --quiet src/ && echo $(git rev-parse HEAD) ") @@ -28,90 +28,86 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/defaults.cmake) # defaults set(DEBUG - ${default_debug} - CACHE BOOL "Debug mode") + ${default_debug} + CACHE BOOL "Debug mode") set(precision - ${default_precision} - CACHE STRING "Precision") + ${default_precision} + CACHE STRING "Precision") set(deposit - ${default_deposit} - CACHE STRING "Deposit") + ${default_deposit} + CACHE STRING "Deposit") set(shape_order - ${default_shape_order} - CACHE STRING "Shape function") + ${default_shape_order} + CACHE STRING "Shape function") set(pgen - ${default_pgen} - CACHE STRING "Problem generator") + ${default_pgen} + CACHE STRING "Problem generator") -set(gui - ${default_gui} - CACHE BOOL "Use GUI [nttiny]") set(output - ${default_output} - CACHE BOOL "Enable output") + ${default_output} + CACHE BOOL "Enable output") set(mpi - ${default_mpi} - CACHE BOOL "Use MPI") + ${default_mpi} + CACHE BOOL "Use MPI") set(gpu_aware_mpi - ${default_gpu_aware_mpi} - CACHE BOOL "Enable GPU-aware MPI") + ${default_gpu_aware_mpi} + CACHE BOOL "Enable GPU-aware MPI") # -------------------------- Compilation settings -------------------------- # -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(${DEBUG} STREQUAL "OFF") set(CMAKE_BUILD_TYPE - Release - CACHE STRING "CMake build type") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG") + Release + CACHE STRING "CMake build type") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3") else() set(CMAKE_BUILD_TYPE - Debug - CACHE STRING "CMake build type") + Debug + CACHE STRING "CMake build type") set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -DDEBUG -Wall -Wextra -Wno-unknown-pragmas") + "${CMAKE_CXX_FLAGS} -DDEBUG -Wall -Wextra -Wno-unknown-pragmas") endif() # options set(precisions - "single" "double" - CACHE STRING "Precisions") + "single" "double" + CACHE STRING "Precisions") set(deposits - "zigzag" "esirkepov" - CACHE STRING "Deposits") + "zigzag" "esirkepov" + CACHE STRING "Deposits") if(${deposit} STREQUAL "zigzag") set(shape_order - ${default_shape_order} - CACHE STRING "Shape functions") + ${default_shape_order} + CACHE STRING "Shape functions") endif() set(shape_orders - "1;2;3;4;5;6;7;8;9;10;11" - CACHE STRING "Shape orders") + "1;2;3;4;5;6;7;8;9;10;11" + CACHE STRING "Shape orders") include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/config.cmake) # ------------------------- Third-Party Tests ------------------------------ # set(BUILD_TESTING - OFF - CACHE BOOL "Build tests") + OFF + CACHE BOOL "Build tests") # ------------------------ Third-party dependencies ------------------------ # include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/dependencies.cmake) find_or_fetch_dependency(Kokkos FALSE QUIET) -find_or_fetch_dependency(plog TRUE QUIET) set(DEPENDENCIES Kokkos::kokkos) -include_directories(${plog_SRC}/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) # -------------------------------- Main code ------------------------------- # set_precision(${precision}) @@ -133,8 +129,8 @@ else() endif() if(("${Kokkos_DEVICES}" MATCHES "CUDA") - OR ("${Kokkos_DEVICES}" MATCHES "HIP") - OR ("${Kokkos_DEVICES}" MATCHES "SYCL")) + OR ("${Kokkos_DEVICES}" MATCHES "HIP") + OR ("${Kokkos_DEVICES}" MATCHES "SYCL")) set(DEVICE_ENABLED ON) else() set(DEVICE_ENABLED OFF) @@ -152,8 +148,8 @@ if(${mpi}) endif() else() set(gpu_aware_mpi - OFF - CACHE BOOL "Use explicit copy when using MPI + GPU") + OFF + CACHE BOOL "Use explicit copy when using MPI + GPU") endif() endif() @@ -162,29 +158,53 @@ if(${output}) find_or_fetch_dependency(adios2 FALSE QUIET) add_compile_options("-D OUTPUT_ENABLED") if(${mpi}) - set(DEPENDENCIES ${DEPENDENCIES} adios2::cxx11_mpi) + set(DEPENDENCIES ${DEPENDENCIES} adios2::cxx_mpi) else() - set(DEPENDENCIES ${DEPENDENCIES} adios2::cxx11) + set(DEPENDENCIES ${DEPENDENCIES} adios2::cxx) endif() endif() link_libraries(${DEPENDENCIES}) +set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/) +add_subdirectory(${SRC_DIR}/global ${CMAKE_CURRENT_BINARY_DIR}/global) +add_subdirectory(${SRC_DIR}/metrics ${CMAKE_CURRENT_BINARY_DIR}/metrics) +add_subdirectory(${SRC_DIR}/kernels ${CMAKE_CURRENT_BINARY_DIR}/kernels) +add_subdirectory(${SRC_DIR}/archetypes ${CMAKE_CURRENT_BINARY_DIR}/archetypes) +add_subdirectory(${SRC_DIR}/framework ${CMAKE_CURRENT_BINARY_DIR}/framework) +add_subdirectory(${SRC_DIR}/output ${CMAKE_CURRENT_BINARY_DIR}/output) + +# ------------------------------- Main source ------------------------------ # + +if(NOT DEFINED pgens) + set(single_pgen_mode ON) + get_available_pgens(available_pgens) + if(NOT ${pgen} STREQUAL ${default_pgen}) + set(pgen_suffix "") + set_problem_generator(${pgen}) + add_subdirectory(${SRC_DIR}/engines ${CMAKE_CURRENT_BINARY_DIR}/engines) + add_subdirectory(${SRC_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src) + endif() + list(APPEND problem_generators ${available_pgens}) +else() + set(single_pgen_mode OFF) + get_available_pgens(available_pgens) + set(pgens_short "") + foreach(pg ${pgens}) + string(REPLACE "./" "" pg_nodir ${pg}) + string(REPLACE "/" "_" pg_nodir ${pg_nodir}) + set(pgen_suffix "_${pg_nodir}") + set_problem_generator(${pg}) + add_subdirectory(${SRC_DIR}/engines ${CMAKE_CURRENT_BINARY_DIR}/${pg_nodir}/engines) + add_subdirectory(${SRC_DIR} ${CMAKE_CURRENT_BINARY_DIR}/${pg_nodir}/src) + list(APPEND pgens_short ${PGEN}) + endforeach() + list(APPEND problem_generators ${available_pgens}) +endif() + if(TESTS) # ---------------------------------- Tests --------------------------------- # include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/tests.cmake) -elseif(BENCHMARK) - # ------------------------------ Benchmark --------------------------------- # - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/benchmark.cmake) -else() - # ----------------------------------- GUI ---------------------------------- # - if(${gui}) - find_or_fetch_dependency(nttiny FALSE QUIET) - endif() - - # ------------------------------- Main source ------------------------------ # - set_problem_generator(${pgen}) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src src) endif() include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/report.cmake) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index cfd678063..0ff56dab1 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -7,7 +7,7 @@ This code of conduct outlines shared principles and expectations for all partici - If you contribute something to the repository, it becomes part of the project and thus will also be regarded as open-source. # Contributions and Credit -- The only attribution we strongly encourage is a citation of either the code repository, or the corresponding method papers (coming soon). +- The only attribution we strongly encourage is a citation of either the code repository, or the corresponding method papers (see `CITATION`). - All contributions are made voluntarily, and there is no expectation of recognition of isolated individuals. - There's no built-in expectation of credit or authorship for modules or changes pushed to the repository. Anyone is free to use any part of the code with no attribution to the author of any specific module or algorithm. - The code is there for everyone to use, and its only goal is to enable the community to produce exciting science! @@ -24,6 +24,6 @@ This code of conduct outlines shared principles and expectations for all partici # Community and Participation - Everyone is welcome in the community. -- Joining meetings on Zoom, using the Slack workspace, giving feedback, taking part in decision making and planning, or following development doesn't require any special status -- it's open to all. +- Joining online meetings on Zoom/Slack, using the Slack workspace, giving feedback, taking part in decision making and planning, or following development doesn't require any special status -- it's open to all. Finally, while we cannot enforce it, we strongly encourage any projects that build on this code to be open source too. If you build upon this project, we welcome transparency and openness in spirit. You may contribute to the code as much or as little as you like; all effort is appreciated, none is required. diff --git a/README.md b/README.md index 41d5ae280..10d1634dd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ tl;dr: One particle-in-cell code to rule them all. -`Entity` is a community-driven open-source coordinate-agnostic general-relativistic (GR) particle-in-cell (PIC) code written in C++17 specifically targeted to study plasma physics in relativistic astrophysical systems. The main algorithms of the code are written in covariant form, allowing to easily implement arbitrary grid geometries. The code is highly modular, and is written in the architecture-agnostic way using the [`Kokkos`](https://kokkos.org/kokkos-core-wiki/) performance portability library, allowing the code to efficiently use device parallelization on CPU and GPU architectures of different types. The multi-node parallelization is implemented using the `MPI` library, and the data output is done via the [`ADIOS2`](https://github.com/ornladios/ADIOS2) library which supports multiple output formats, including `HDF5` and `BP5`. +`Entity` is a community-driven open-source coordinate-agnostic general-relativistic (GR) particle-in-cell (PIC) code written in C++ specifically targeted to study plasma physics in relativistic astrophysical systems. The main algorithms of the code are written in covariant form, allowing to easily implement arbitrary grid geometries. The code is highly modular, and is written in the architecture-agnostic way using the [`Kokkos`](https://kokkos.org/kokkos-core-wiki/) performance portability library, allowing the code to efficiently use device parallelization on CPU and GPU architectures of different types. The multi-node parallelization is implemented using the `MPI` library, and the data output is done via the [`ADIOS2`](https://github.com/ornladios/ADIOS2) library which supports multiple output formats, including `HDF5` and `BP5`. `Entity` is part of the `Entity toolkit` framework, which also includes a Python library for fast and efficient data analysis and visualization of the simulation data: [`nt2py`](https://pypi.org/project/nt2py/). @@ -12,6 +12,10 @@ Our [detailed documentation](https://entity-toolkit.github.io/) includes everyth ## News +- [Mar 2026]: **single-particle emission** [PR #174](https://github.com/entity-toolkit/entity/pull/174) and [PR #188](https://github.com/entity-toolkit/entity/pull/188) +- [Mar 2026]: **spatial sorting of particles** [PR #181](https://github.com/entity-toolkit/entity/pull/181) +- [Mar 2026]: **external EM & force fields** [PR #183](https://github.com/entity-toolkit/entity/pull/183) +- [Mar 2026]: **examples** of most commonly used custom patterns (see `pgens/examples`) - [Dec 2025]: **high-order** shape functions [PR #109](https://github.com/entity-toolkit/entity/pull/109) and advanced field stencils [PR #103](https://github.com/entity-toolkit/entity/pull/103) are now supported. - [Dec 2025]: **particle tracking** is now fully supported via [PR #144](https://github.com/entity-toolkit/entity/pull/144). - [Nov 2025]: our **method papers** are online: [Special relativistic module](https://ui.adsabs.harvard.edu/abs/2025arXiv251117710H/abstract), [GR module](https://ui.adsabs.harvard.edu/abs/2025arXiv251117701G/abstract)! @@ -24,7 +28,7 @@ Please, see the `CITATION` document for the relevant BibTeX entries if you would Everyone is welcome to join our small yet steadily growing community of code users and developers; regardless of how much you are planning to contribute -- we always welcome fresh ideas and feedback. We hold weekly Slack calls on Mondays at 12pm NY time, and have a dedicated Slack channel where you can be easily added by emailing one of the maintainers (indicated with an asterisk in the list below). Anyone is welcome to join both our **Slack workspace** and the weekly meetings -- please feel free to request access by emailing. -Another way of contacting us is via GitHub issues and/or pull requests. Make sure to check out our [F.A.Q.](https://entity-toolkit.github.io/wiki/content/1-getting-started/9-faq/), as it might help you answer your question. +Another way of contacting us is via GitHub issues and/or pull requests. Make sure to check out our [F.A.Q.](https://entity-toolkit.github.io/wiki/content/2-howto/7-faq/), as it might help you answer your question. > Keep in mind, you are free to use the code in any capacity, and there is absolutely no requirement on our end of including any of the developers in your project/proposal (as highlighted in our Code of Conduct). When contributing, also keep in mind that the code you upload to the repository automatically becomes public and open-source, and the same standards will be applied to it as to the rest of the code. @@ -37,11 +41,16 @@ Maintainers indicated with an arrow. * :tipping_hand_person: Alexander Chernoglazov {[@SChernoglazov](https://github.com/SChernoglazov)} * :tea: Benjamin Crinquand {[@bcrinquand](https://github.com/bcrinquand)} * :bubble_tea: Alisa Galishnikova {[@alisagk](https://github.com/alisagk)} -* :steam_locomotive: Evgeny Gorbunov {[@Alcauchy](https://github.com/Alcauchy)} [-> [haykh.astro [at] gmail](mailto:haykh.astro@gmail.com)] -* :coffee: Hayk Hakobyan {[@haykh](https://github.com/haykh)} [-> [genegorbs [at] gmail](mailto:genegorbs@gmail.com)] +* :sloth: Xingwei Gong {[@xwgong01](https://github.com/xwgong01)} +* :steam_locomotive: Evgeny Gorbunov {[@Alcauchy](https://github.com/Alcauchy)} [-> [genegorbs [at] gmail](mailto:genegorbs@gmail.com)] +* :ant: Camille Granier {[@K1000Granier](https://github.com/K1000Granier)} +* :fried_egg: Michael Grehan {[@mgrehan](https://github.com/mgrehan)} +* :coffee: Hayk Hakobyan {[@haykh](https://github.com/haykh)} [-> [haykh.astro [at] gmail](mailto:haykh.astro@gmail.com)] +* :sunrise_over_mountains: Anuj Kankani {[@AnujKankani](https://github.com/AnujKankani)} * :potato: Jens Mahlmann {[@jmahlmann](https://github.com/jmahlmann)} * :dolphin: Sasha Philippov {[@sashaph](https://github.com/sashaph)} * :radio: Siddhant Solanki {[@sidruns30](https://github.com/sidruns30)} +* :mango: Andrew Sullivan {[@a-sullivan](https://github.com/a-sullivan)} * :shrug: Arno Vanthieghem {[@vanthieg](https://github.com/vanthieg)} * :cat: Muni Zhou {[@munizhou](https://github.com/munizhou)} diff --git a/benchmark/benchmark.cpp b/benchmark/benchmark.cpp deleted file mode 100644 index 98306c92b..000000000 --- a/benchmark/benchmark.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "global.h" - -#include -#include - -auto main(int argc, char* argv[]) -> int { - ntt::GlobalInitialize(argc, argv); - try { - // ... - } catch (const std::exception& e) { - std::cerr << "Error: " << e.what() << std::endl; - GlobalFinalize(); - return 1; - } - GlobalFinalize(); - return 0; -} diff --git a/cmake/benchmark.cmake b/cmake/benchmark.cmake deleted file mode 100644 index fdd8438ea..000000000 --- a/cmake/benchmark.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# cmake-lint: disable=C0103 - -set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) - -add_subdirectory(${SRC_DIR}/global ${CMAKE_CURRENT_BINARY_DIR}/global) -add_subdirectory(${SRC_DIR}/metrics ${CMAKE_CURRENT_BINARY_DIR}/metrics) -add_subdirectory(${SRC_DIR}/kernels ${CMAKE_CURRENT_BINARY_DIR}/kernels) -add_subdirectory(${SRC_DIR}/archetypes ${CMAKE_CURRENT_BINARY_DIR}/archetypes) -add_subdirectory(${SRC_DIR}/framework ${CMAKE_CURRENT_BINARY_DIR}/framework) - -if(${output}) - add_subdirectory(${SRC_DIR}/output ${CMAKE_CURRENT_BINARY_DIR}/output) - add_subdirectory(${SRC_DIR}/checkpoint ${CMAKE_CURRENT_BINARY_DIR}/checkpoint) -endif() - -set(exec benchmark.xc) -set(src ${CMAKE_CURRENT_SOURCE_DIR}/benchmark/benchmark.cpp) - -add_executable(${exec} ${src}) - -set(libs ntt_global ntt_metrics ntt_kernels ntt_archetypes ntt_framework) -if(${output}) - list(APPEND libs ntt_output) -endif() -add_dependencies(${exec} ${libs}) -target_link_libraries(${exec} PRIVATE ${libs} stdc++fs) diff --git a/cmake/config.cmake b/cmake/config.cmake index e9b0de390..19d3629e0 100644 --- a/cmake/config.cmake +++ b/cmake/config.cmake @@ -27,57 +27,61 @@ function(set_shape_order shape_order) endfunction() # ---------------------------- Problem generator --------------------------- # -function(set_problem_generator pgen_name) - if(pgen_name STREQUAL ".") - message(FATAL_ERROR "Problem generator not specified") - endif() +function(get_available_pgens available_pgens) + set(available_pgens "") - file(GLOB_RECURSE PGENS "${CMAKE_CURRENT_SOURCE_DIR}/pgens/**/pgen.hpp") + file(GLOB_RECURSE BASE_PGENS "${CMAKE_CURRENT_SOURCE_DIR}/pgens/**/pgen.hpp") - foreach(PGEN ${PGENS}) + foreach(PGEN ${BASE_PGENS}) get_filename_component(PGEN_NAME ${PGEN} DIRECTORY) string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/pgens/" "" PGEN_NAME ${PGEN_NAME}) - list(APPEND PGEN_NAMES ${PGEN_NAME}) + list(APPEND available_pgens ${PGEN_NAME}) endforeach() - list(FIND PGEN_NAMES ${pgen_name} PGEN_FOUND) + # @TODO: for now, dropping support for Entity pgens file(GLOB_RECURSE + # EXTRA_PGENS "${CMAKE_CURRENT_SOURCE_DIR}/extern/entity-pgens/**/pgen.hpp") + # foreach(EXTRA_PGEN ${EXTRA_PGENS}) get_filename_component(EXTRA_PGEN_NAME + # ${EXTRA_PGEN} DIRECTORY) string(REPLACE + # "${CMAKE_CURRENT_SOURCE_DIR}/extern/entity-pgens/" "" EXTRA_PGEN_NAME + # ${EXTRA_PGEN_NAME}) list(APPEND available_pgens "pgens/${EXTRA_PGEN_NAME}") + # endforeach() - file(GLOB_RECURSE EXTRA_PGENS - "${CMAKE_CURRENT_SOURCE_DIR}/extern/entity-pgens/**/pgen.hpp") - foreach(EXTRA_PGEN ${EXTRA_PGENS}) - get_filename_component(EXTRA_PGEN_NAME ${EXTRA_PGEN} DIRECTORY) - string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/extern/entity-pgens/" "" - EXTRA_PGEN_NAME ${EXTRA_PGEN_NAME}) - list(APPEND PGEN_NAMES "pgens/${EXTRA_PGEN_NAME}") - endforeach() + set(available_pgens + ${available_pgens} + PARENT_SCOPE) +endfunction() + +function(set_problem_generator pgen_name) + if(pgen_name STREQUAL ".") + message(FATAL_ERROR "Problem generator not specified") + endif() + + list(FIND available_pgens ${pgen_name} PGEN_FOUND_IN_AVAILABLE) - if(${PGEN_FOUND} EQUAL -1) - if(${pgen_name} MATCHES "^pgens/") - get_filename_component(pgen_name ${pgen_name} NAME) - set(pgen_path - "${CMAKE_CURRENT_SOURCE_DIR}/extern/entity-pgens/${pgen_name}") - set(pgen_name "pgens/${pgen_name}") + if(${PGEN_FOUND_IN_AVAILABLE} EQUAL -1) + get_filename_component(pgen_path ${pgen_name} ABSOLUTE) + string(REGEX REPLACE ".*/" "" pgen_name ${pgen_name}) + if(${single_pgen_mode}) + set(available_pgens ${pgen_name}) else() - set(pgen_path ${pgen_name}) - get_filename_component(pgen_path ${pgen_path} ABSOLUTE) - string(REGEX REPLACE ".*/" "" pgen_name ${pgen_name}) - list(APPEND PGEN_NAMES ${pgen_name}) + list(APPEND available_pgens ${pgen_name}) endif() else() - set(pgen_path ${CMAKE_CURRENT_SOURCE_DIR}/pgens/${pgen_name}) + set(pgen_path "${CMAKE_CURRENT_SOURCE_DIR}/pgens/${pgen_name}") endif() - file(GLOB_RECURSE PGEN_FILES "${pgen_path}/pgen.hpp") - if(NOT PGEN_FILES) + file(GLOB_RECURSE PGEN_FILE_FOUND "${pgen_path}/pgen.hpp") + if(NOT PGEN_FILE_FOUND) message(FATAL_ERROR "pgen.hpp file not found in ${pgen_path}") endif() - add_library(ntt_pgen INTERFACE) - target_link_libraries(ntt_pgen INTERFACE ntt_global ntt_framework - ntt_archetypes ntt_kernels) + set(PGEN_TARGET ntt_pgen${pgen_suffix}) + add_library(${PGEN_TARGET} INTERFACE) + target_link_libraries(${PGEN_TARGET} INTERFACE ntt_global ntt_framework + ntt_archetypes ntt_kernels) - target_include_directories(ntt_pgen INTERFACE ${pgen_path}) + target_include_directories(${PGEN_TARGET} INTERFACE ${pgen_path}) set(PGEN ${pgen_name} @@ -85,7 +89,7 @@ function(set_problem_generator pgen_name) set(PGEN_FOUND TRUE PARENT_SCOPE) - set(problem_generators - ${PGEN_NAMES} + set(available_pgens + ${available_pgens} PARENT_SCOPE) endfunction() diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 1f3ed3c6a..2024702b5 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -3,14 +3,24 @@ set(Kokkos_REPOSITORY https://github.com/kokkos/kokkos.git CACHE STRING "Kokkos repository") -set(plog_REPOSITORY - https://github.com/SergiusTheBest/plog.git - CACHE STRING "plog repository") +set(Kokkos_TAG + 5.0.1 + CACHE STRING "Kokkos tag") set(adios2_REPOSITORY https://github.com/ornladios/ADIOS2.git CACHE STRING "ADIOS2 repository") +set(adios2_TAG + v2.11.0 + CACHE STRING "ADIOS2 tag") + +set(CONNECTION_CHECKED + FALSE + CACHE BOOL "Whether internet connection has been checked") function(check_internet_connection) + if(CONNECTION_CHECKED) + return() + endif() if(OFFLINE STREQUAL "ON") set(FETCHCONTENT_FULLY_DISCONNECTED ON @@ -35,6 +45,9 @@ function(check_internet_connection) message(STATUS "${Green}Internet connection established.${ColorReset}") endif() endif() + set(CONNECTION_CHECKED + TRUE + CACHE BOOL "Whether internet connection has been checked") endfunction() function(find_or_fetch_dependency package_name header_only mode) @@ -43,6 +56,8 @@ function(find_or_fetch_dependency package_name header_only mode) endif() if(NOT ${package_name}_FOUND) + check_internet_connection() + if(${package_name} STREQUAL "Kokkos") include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/kokkosConfig.cmake) elseif(${package_name} STREQUAL "adios2") @@ -54,16 +69,11 @@ function(find_or_fetch_dependency package_name header_only mode) message(STATUS "${Blue}${package_name} not found. " "Fetching from ${${package_name}_REPOSITORY}${ColorReset}") include(FetchContent) - if(${package_name} STREQUAL "Kokkos") + if(${package_name} STREQUAL "Kokkos" OR ${package_name} STREQUAL "adios2") FetchContent_Declare( ${package_name} GIT_REPOSITORY ${${package_name}_REPOSITORY} - GIT_TAG 4.7.01) - elseif(${package_name} STREQUAL "adios2") - FetchContent_Declare( - ${package_name} - GIT_REPOSITORY ${${package_name}_REPOSITORY} - GIT_TAG v2.10.2) + GIT_TAG ${${package_name}_TAG}) else() FetchContent_Declare(${package_name} GIT_REPOSITORY ${${package_name}_REPOSITORY}) @@ -171,5 +181,3 @@ function(find_or_fetch_dependency package_name header_only mode) ${${package_name}_BUILD_DIR} PARENT_SCOPE) endfunction() - -check_internet_connection() diff --git a/cmake/report.cmake b/cmake/report.cmake index 8f62ac17b..7b145bfc7 100644 --- a/cmake/report.cmake +++ b/cmake/report.cmake @@ -1,31 +1,69 @@ if(${PGEN_FOUND}) - printchoices( - "Problem generator" - "pgen" - "${problem_generators}" - ${PGEN} - "" - "${Blue}" - PGEN_REPORT - 0) -elseif(${TESTS}) + if(${single_pgen_mode}) + printchoices( + "Problem generator" + "pgen" + "${problem_generators}" + "${PGEN}" + "" + "${Blue}" + PGEN_REPORT + 0) + else() + printchoices( + "Problem generators" + "pgens" + "${problem_generators}" + "${pgens_short}" + "" + "${Blue}" + PGEN_REPORT + 0) + endif() +endif() + +if(${TESTS}) set(TEST_NAMES "") foreach(test_dir IN LISTS TEST_DIRECTORIES) get_property( LOCAL_TEST_NAMES - DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${test_dir}/tests + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests/${test_dir} PROPERTY TESTS) list(APPEND TEST_NAMES ${LOCAL_TEST_NAMES}) endforeach() printchoices( - "Test cases" + "Tests" + "TESTS" + "${ON_OFF_VALUES}" + "ON" + "OFF" + "${Green}" + TESTS_REPORT_1 + 46) + printchoices( + "" "" "${TEST_NAMES}" "" "${ColorReset}" "" - TESTS_REPORT + TESTS_REPORT_2 0) + # remove only first line of TESTS_REPORT_2 + string(REPLACE "\n" ";" TESTS_REPORT_2_LIST "${TESTS_REPORT_2}") + list(REMOVE_AT TESTS_REPORT_2_LIST 0) + string(REPLACE ";" "\n" TESTS_REPORT_2 "${TESTS_REPORT_2_LIST}") + set(TESTS_REPORT "${TESTS_REPORT_1}\n${TESTS_REPORT_2}") +else() + printchoices( + "Tests" + "TESTS" + "${ON_OFF_VALUES}" + "OFF" + "OFF" + "${Green}" + TESTS_REPORT + 46) endif() printchoices( @@ -120,9 +158,8 @@ string(APPEND REPORT_TEXT ${DASHED_LINE_SYMBOL} "\n" "Configurations" "\n") if(${PGEN_FOUND}) string(APPEND REPORT_TEXT " " ${PGEN_REPORT} "\n") -elseif(${TESTS}) - string(APPEND REPORT_TEXT " " ${TESTS_REPORT} "\n") endif() +string(APPEND REPORT_TEXT " " ${TESTS_REPORT} "\n") string( APPEND diff --git a/cmake/styling.cmake b/cmake/styling.cmake index daae19c65..949f87271 100644 --- a/cmake/styling.cmake +++ b/cmake/styling.cmake @@ -84,12 +84,9 @@ function( string(APPEND rstring ":") if(${Padding} EQUAL 0) - list(LENGTH "${Choices}" nchoices) - math(EXPR lastchoice "${nchoices} - 1") - set(longest 0) foreach(ch IN LISTS Choices) - string(LENGTH ${ch} clen) + string(LENGTH "${ch}" clen) if(clen GREATER longest) set(longest ${clen}) endif() @@ -102,70 +99,64 @@ function( endif() math(EXPR lastcol "${ncols} - 1") - set(counter 0) + set(col_pos 0) foreach(ch IN LISTS Choices) - if(NOT ${Value} STREQUAL "") - if(${ch} STREQUAL ${Value}) - set(col ${Color}) - else() - set(col ${Dim}) - endif() - else() - set(col ${Dim}) + if(col_pos EQUAL 0) + string(APPEND rstring "\n ") endif() - if(NOT ${Default} STREQUAL "") - if(${ch} STREQUAL ${Default}) - set(col ${Underline}${col}) + set(col "${Dim}") + if(NOT "${Value}" STREQUAL "") + list(FIND Value "${ch}" _idx) + if(_idx GREATER -1) + set(col "${Color}") endif() endif() + if(NOT "${Default}" STREQUAL "" AND "${ch}" STREQUAL "${Default}") + set(col "${Underline}${col}") + endif() - string(LENGTH "${ch}" clen) - math(EXPR PaddingNeeded "${longest} - ${clen} + 4") + string(APPEND rstring "${col}~ ${ch}${ColorReset}") - if(counter EQUAL ${lastcol} AND NOT ${counter} EQUAL ${lastchoice}) - string(APPEND rstring "${col}~ ${ch}${ColorReset}") - else() - if(counter EQUAL 0) - string(APPEND rstring "\n ") - endif() - string(APPEND rstring "${col}~ ${ch}${ColorReset}") - foreach(i RANGE 0 ${PaddingNeeded}) + if(NOT col_pos EQUAL lastcol) + string(LENGTH "${ch}" clen) + math(EXPR pad "${longest} - ${clen} + 4") + foreach(i RANGE 0 ${pad}) string(APPEND rstring " ") endforeach() endif() - math(EXPR counter "(${counter} + 1) % ${ncols}") + math(EXPR col_pos "(${col_pos} + 1) % ${ncols}") endforeach() else() padto("${rstring}" " " ${Padding} rstring) - if(${Value} STREQUAL "ON") - set(col ${Green}) - elseif(${Value} STREQUAL "OFF") - set(col ${Red}) - else() - set(col ${Color}) - endif() - set(new_choices "") + set(parts "") foreach(ch IN LISTS Choices) - set(elem "${ch}") - if((NOT "${Value}" STREQUAL "") AND (${ch} STREQUAL ${Value})) - set(elem "${col}${ch}${ColorReset}") + if("${ch}" STREQUAL "ON") + set(ch_col "${Green}") + elseif("${ch}" STREQUAL "OFF") + set(ch_col "${Red}") else() - set(elem "${Dim}${ch}${ColorReset}") + set(ch_col "${Color}") endif() - if((NOT "${Default}" STREQUAL "") AND (${ch} STREQUAL ${Default})) + + set(elem "${Dim}${ch}${ColorReset}") + if(NOT "${Value}" STREQUAL "") + list(FIND Value "${ch}" _idx) + if(_idx GREATER -1) + set(elem "${ch_col}${ch}${ColorReset}") + endif() + endif() + if(NOT "${Default}" STREQUAL "" AND "${ch}" STREQUAL "${Default}") set(elem "${Underline}${elem}${ColorReset}") endif() - string(APPEND new_choices "${elem};") + + list(APPEND parts "${elem}") endforeach() - string(LENGTH "${new_choices}" nlen) - math(EXPR nlen "${nlen} - 1") - string(SUBSTRING "${new_choices}" 0 ${nlen} new_choices) - set(Choices ${new_choices}) - string(REPLACE ";" "/" Choices "${Choices}") - string(APPEND rstring "${Choices}") + + string(JOIN "/" joined ${parts}) + string(APPEND rstring "${joined}") endif() set(${OutputString} diff --git a/cmake/tests.cmake b/cmake/tests.cmake index 0eb043f70..f226ff45e 100644 --- a/cmake/tests.cmake +++ b/cmake/tests.cmake @@ -1,30 +1,18 @@ include(CTest) enable_testing() -set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) - -add_subdirectory(${SRC_DIR}/global ${CMAKE_CURRENT_BINARY_DIR}/global) -add_subdirectory(${SRC_DIR}/metrics ${CMAKE_CURRENT_BINARY_DIR}/metrics) -add_subdirectory(${SRC_DIR}/kernels ${CMAKE_CURRENT_BINARY_DIR}/kernels) -add_subdirectory(${SRC_DIR}/archetypes ${CMAKE_CURRENT_BINARY_DIR}/archetypes) -add_subdirectory(${SRC_DIR}/framework ${CMAKE_CURRENT_BINARY_DIR}/framework) -add_subdirectory(${SRC_DIR}/output ${CMAKE_CURRENT_BINARY_DIR}/output) +set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests) set(TEST_DIRECTORIES "") -if(NOT ${mpi}) - list(APPEND TEST_DIRECTORIES global) - list(APPEND TEST_DIRECTORIES metrics) - list(APPEND TEST_DIRECTORIES kernels) - list(APPEND TEST_DIRECTORIES archetypes) - list(APPEND TEST_DIRECTORIES framework) -elseif(${mpi} AND ${output}) - list(APPEND TEST_DIRECTORIES framework) -endif() - +list(APPEND TEST_DIRECTORIES global) +list(APPEND TEST_DIRECTORIES metrics) +list(APPEND TEST_DIRECTORIES kernels) +list(APPEND TEST_DIRECTORIES archetypes) +list(APPEND TEST_DIRECTORIES framework) list(APPEND TEST_DIRECTORIES output) foreach(test_dir IN LISTS TEST_DIRECTORIES) - add_subdirectory(${SRC_DIR}/${test_dir}/tests - ${CMAKE_CURRENT_BINARY_DIR}/${test_dir}/tests) + add_subdirectory(${SRC_DIR}/${test_dir} + ${CMAKE_CURRENT_BINARY_DIR}/tests/${test_dir}) endforeach() diff --git a/dependencies.py b/dependencies.py new file mode 100755 index 000000000..114cfd145 --- /dev/null +++ b/dependencies.py @@ -0,0 +1,1198 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import curses +import json +import os +from dataclasses import dataclass, field +from typing import Callable, List, Optional, Tuple + + +# ============================ +# colors: edit these +# ============================ + +# foreground colors (use curses.COLOR_* or -1 for default) +COLOR_TITLE_FG = curses.COLOR_BLUE +COLOR_TEXT_FG = curses.COLOR_WHITE +COLOR_SELECTED_FG = curses.COLOR_WHITE +COLOR_SELECTED_BG = curses.COLOR_BLACK +COLOR_HINT_FG = curses.COLOR_YELLOW +COLOR_OK_FG = curses.COLOR_GREEN +COLOR_ERR_FG = curses.COLOR_RED +COLOR_KEY_FG = curses.COLOR_MAGENTA +COLOR_DIM_FG = curses.COLOR_CYAN + +# pair IDs (must be unique small ints) +PAIR_TITLE = 1 +PAIR_TEXT = 2 +PAIR_SELECTED = 3 +PAIR_HINT = 4 +PAIR_OK = 5 +PAIR_ERR = 6 +PAIR_KEY = 7 +PAIR_DIM = 8 + + +KOKKOS_BACKENDS = ["cpu", "cuda", "hip", "sycl"] +ADIOS2_MPI_MODES = ["non-mpi", "mpi"] + +MESSAGE: str = "" + + +@dataclass +class Settings: + cluster: str = "(custom)" + write_modulefiles: bool = False + overwrite: bool = False + install_prefix: str = os.path.join(os.path.expanduser("~"), ".entity") + + apps: dict = field( + default_factory=lambda: {"Kokkos": False, "adios2": False, "nt2py": False} + ) + + # versions + kokkos_version: str = "5.0.1" + adios2_version: str = "2.11.0" + + # options + kokkos_backend: str = "cpu" + kokkos_arch: str = "" + extra_kokkos_flags: List[str] = field(default_factory=list) + adios2_mpi: str = "non-mpi" + extra_adios2_flags: List[str] = field(default_factory=list) + + module_loads: List[str] = field(default_factory=list) + + def from_json(self, json_str: str) -> None: + data = json.loads(json_str) + self.cluster = data.get("cluster", self.cluster) + self.write_modulefiles = data.get("write_modulefiles", self.write_modulefiles) + self.overwrite = data.get("overwrite", self.overwrite) + self.install_prefix = data.get("install_prefix", self.install_prefix) + self.apps = data.get("dependencies", self.apps) + versions = data.get("versions", {}) + self.kokkos_version = versions.get("Kokkos", self.kokkos_version) + self.adios2_version = versions.get("adios2", self.adios2_version) + options = data.get("options", {}) + self.kokkos_backend = options.get("kokkos_backend", self.kokkos_backend) + self.kokkos_arch = options.get("kokkos_arch", self.kokkos_arch) + self.adios2_mpi = options.get("adios2_mpi", self.adios2_mpi) + self.module_loads = data.get("module_loads", self.module_loads) + + def apps_summary(self) -> str: + chosen = [k for k, v in self.apps.items() if v] + return ", ".join(chosen) if chosen else "(none)" + + def to_json(self) -> str: + return json.dumps( + { + "cluster": self.cluster, + "write_modulefiles": self.write_modulefiles, + "overwrite": self.overwrite, + "install_prefix": self.install_prefix, + "dependencies": self.apps, + "versions": { + "Kokkos": self.kokkos_version, + "adios2": self.adios2_version, + }, + "options": { + "kokkos_backend": self.kokkos_backend, + "kokkos_arch": self.kokkos_arch, + "adios2_mpi": self.adios2_mpi, + }, + "module_loads": self.module_loads, + }, + indent=2, + ) + + +def unindent(script: str) -> str: + script_lines = script.splitlines() + min_indent = min( + (len(line) - len(line.lstrip()) for line in script_lines if line.strip()), + default=0, + ) + trimmed_lines = [line[min_indent:] for line in script_lines] + if trimmed_lines[0] == "": + trimmed_lines = trimmed_lines[1:] + if trimmed_lines[-1] == "": + trimmed_lines = trimmed_lines[:-1] + return "\n".join(trimmed_lines) + + +def InstallKokkosScriptModfile(settings: Settings) -> tuple[str, str]: + if settings.apps.get("Kokkos", False): + prefix = settings.install_prefix + version = settings.kokkos_version + backend = settings.kokkos_backend + arch = settings.kokkos_arch.strip() + modules = "\n".join( + [f"module load {module} && \\" for module in settings.module_loads] + ) + modules_in_module = "\n".join( + [f"module load {module}" for module in settings.module_loads] + ) + src_path = os.path.join(prefix, "src", "kokkos") + install_path = os.path.join(prefix, "kokkos", version, backend, arch.lower() if arch else "") + if os.path.exists(install_path) and not settings.overwrite: + raise FileExistsError( + f"Kokkos install path {install_path} already exists and overwrite is disabled" + ) + + extra_flags = " ".join(["-D " + kf for kf in settings.extra_kokkos_flags]) + cxx_standard = 20 if tuple(map(int, version.split("."))) >= (5, 0, 0) else 17 + + if arch == "": + arch = "NATIVE" + arch = arch.upper() + + script = f""" +# Kokkos installation +{modules} +rm -rf {src_path} && \\ +git clone https://github.com/kokkos/kokkos.git {src_path} && \\ +cd {src_path} && \\ +git checkout {version} && \\ +cmake -B build \\ + -D CMAKE_CXX_STANDARD={cxx_standard} \\ + -D CMAKE_CXX_EXTENSIONS=OFF \\ + -D CMAKE_POSITION_INDEPENDENT_CODE=TRUE \\ + -D Kokkos_ARCH_{arch}=ON {f'-D Kokkos_ENABLE_{backend.upper()}=ON' if backend != 'cpu' else ''} \\ + -D CMAKE_INSTALL_PREFIX={install_path} {extra_flags} && \\ +cmake --build build -j $(nproc) && \\ +cmake --install build""" + + modfile = f""" +#%Module1.0###################################################################### +## +## Kokkos @ {backend} @ {arch} modulefile +## +################################################################################# +proc ModulesHelp {{ }} {{ + puts stderr \"\\tKokkos @ {backend} @ {arch}\\n\" +}} + +module-whatis \"Sets up Kokkos @ {backend} @ {arch}\" + +conflict kokkos +{modules_in_module} + +set basedir {install_path} +prepend-path PATH $basedir/bin +setenv Kokkos_DIR $basedir + +setenv Kokkos_ARCH_{arch} ON +{f'setenv Kokkos_ENABLE_{backend.upper()} ON' if backend != 'cpu' else ''}""" + + return (unindent(script), unindent(modfile)) + + else: + return ("""# skipping Kokkos install""", "") + + +def InstallAdios2Script(settings: Settings) -> tuple[str, str]: + if settings.apps.get("adios2", False): + prefix = settings.install_prefix + version = settings.adios2_version + mpi_mode = settings.adios2_mpi + modules = "\n".join( + [f"module load {module} && \\" for module in settings.module_loads] + ) + modules_in_module = "\n".join( + [f"module load {module}" for module in settings.module_loads] + ) + src_path = os.path.join(prefix, "src", "adios2") + install_path = os.path.join(prefix, "adios2", version, mpi_mode) + if os.path.exists(install_path) and not settings.overwrite: + raise FileExistsError( + f"Adios2 install path {install_path} already exists and overwrite is disabled" + ) + + extra_flags = " ".join(["-D " + af for af in settings.extra_adios2_flags]) + cxx_standard = ( + 20 + if tuple(map(int, settings.kokkos_version.split("."))) >= (5, 0, 0) + else 17 + ) + + with_mpi = "ON" if mpi_mode == "mpi" else "OFF" + + script = f""" +# Adios2 installation +{modules} +rm -rf {src_path} && \\ +git clone https://github.com/ornladios/ADIOS2.git {src_path} && \\ +cd {src_path} && \\ +git checkout v{version} && \\ +cmake -B build \\ + -D CMAKE_CXX_STANDARD={cxx_standard} \\ + -D CMAKE_CXX_EXTENSIONS=OFF \\ + -D CMAKE_POSITION_INDEPENDENT_CODE=TRUE \\ + -D BUILD_SHARED_LIBS=ON \\ + -D ADIOS2_USE_Python=OFF \\ + -D ADIOS2_USE_Fortran=OFF \\ + -D ADIOS2_USE_ZeroMQ=OFF \\ + -D BUILD_TESTING=OFF \\ + -D ADIOS2_BUILD_EXAMPLES=OFF \\ + -D ADIOS2_USE_HDF5=OFF \\ + -D ADIOS2_USE_MPI={with_mpi} \\ + -D CMAKE_INSTALL_PREFIX={install_path} {extra_flags} && \\ +cmake --build build -j $(nproc) && \\ +cmake --install build""" + + modfile = f""" +#%Module1.0###################################################################### +## +## ADIOS2 @ {mpi_mode} modulefile +## +################################################################################# +proc ModulesHelp {{ }} {{ + puts stderr \"\\tADIOS2 @ {mpi_mode}\\n\" +}} + +module-whatis \"Sets up ADIOS2 @ {mpi_mode}\" + +conflict adios2 +{modules_in_module} + +set basedir {install_path} +prepend-path PATH $basedir/bin +setenv ADIOS2_DIR $basedir + +setenv ADIOS2_USE_MPI {with_mpi}""" + + return (unindent(script), unindent(modfile)) + + else: + return ("""# skipping Adios2 install""", "") + + +def InstallNt2pyScript(settings: Settings) -> str: + if settings.apps.get("nt2py", False): + prefix = settings.install_prefix + modules = "\n".join( + [f"module load {module} && \\" for module in settings.module_loads] + ) + install_path = f"{prefix}/.venv" + + script = f""" + # nt2py installation + {modules} + rm -rf {install_path} && \\ + python3 -m venv {install_path} && \\ + source {install_path}/bin/activate && \\ + pip install nt2py && \\ + deactivate + """ + return unindent(script) + else: + return """# skipping nt2py install""" + + +PRESETS = { + "rusty": { + "module_loads": ["openmpi/cuda-4.1.8", "cuda/12.8.0.lua", "gcc/14.2.0.lua"], + "kokkos_backend": "cuda", + "kokkos_arch": "AMPERE80", + "adios2_mpi": "mpi", + }, + "stellar": {"module_loads": []}, + "perlmutter": { + "module_loads": ["gpu/1.0"], + "kokkos_backend": "cuda", + "kokkos_arch": "AMPERE80", + "extra_kokkos_flags": [ + "Kokkos_ENABLE_IMPL_CUDA_MALLOC_ASYNC=OFF", + "CMAKE_CXX_COMPILER=CC", + ], + "extra_adios2_flags": [ + "LIBFABRIC_ROOT=/opt/cray/libfabric/1.15.2.0/", + "MPI_ROOT=/opt/cray/pe/craype/2.7.30", + ], + }, + "lumi": { + "module_loads": ["PrgEnv-cray", "cray-mpich", "craype-accel-amd-gfx90a", "rocm"], + "kokkos_backend": "hip", + "kokkos_arch": "AMD_GFX90A", + "extra_kokkos_flags": [ + "CMAKE_CXX_COMPILER=hipcc", + "AMDGPU_TARGETS=gfx90a", + ], + "extra_adios2_flags": [ + "CMAKE_CXX_COMPILER=CC", + "CMAKE_C_COMPILER=cc" + ] + }, + "frontier": {"module_loads": []}, + "aurora": {"module_loads": []}, +} + + +def apply_preset(s: Settings, name: str) -> None: + s.cluster = name + s.install_prefix = os.path.join(os.path.expanduser("~"), ".entity") + cluster_preset = PRESETS.get(name, {}) + s.apps["Kokkos"] = True + s.apps["adios2"] = True + s.apps["nt2py"] = False + s.write_modulefiles = True + s.overwrite = True + s.module_loads = cluster_preset.get("module_loads", []) + s.kokkos_backend = cluster_preset.get("kokkos_backend", "cpu") + s.kokkos_arch = cluster_preset.get("kokkos_arch", "NATIVE") + s.adios2_mpi = cluster_preset.get("adios2_mpi", "mpi") + s.extra_kokkos_flags = cluster_preset.get("extra_kokkos_flags", []) + s.extra_adios2_flags = cluster_preset.get("extra_adios2_flags", []) + + +def on_install_confirmed(settings: Settings) -> None: + global MESSAGE + os.makedirs(settings.install_prefix, exist_ok=True) + kokkos_script, kokkos_modfile = InstallKokkosScriptModfile(settings) + adios2_script, adios2_modfile = InstallAdios2Script(settings) + with open(os.path.join(settings.install_prefix, "install.sh"), "w") as f: + f.write("#!/usr/bin/env bash\n\n") + f.write(kokkos_script) + f.write("\n\n") + f.write(adios2_script) + f.write("\n") + if settings.write_modulefiles: + os.makedirs(os.path.join(settings.install_prefix, "modules"), exist_ok=True) + if kokkos_modfile != "": + kokkos_modfile_file = os.path.join( + settings.install_prefix, + "modules", + "kokkos", + settings.kokkos_version, + settings.kokkos_backend, + settings.kokkos_arch.strip().lower(), + ) + os.makedirs(os.path.dirname(kokkos_modfile_file), exist_ok=True) + if os.path.exists(kokkos_modfile_file) and not settings.overwrite: + raise FileExistsError( + f"modulefile {kokkos_modfile_file} already exists and overwrite is disabled" + ) + with open(kokkos_modfile_file, "w") as f: + f.write(kokkos_modfile) + if adios2_modfile != "": + adios2_modfile_file = os.path.join( + settings.install_prefix, + "modules", + "adios2", + settings.adios2_version, + settings.adios2_mpi, + ) + os.makedirs(os.path.dirname(adios2_modfile_file), exist_ok=True) + if os.path.exists(adios2_modfile_file) and not settings.overwrite: + raise FileExistsError( + f"modulefile {adios2_modfile_file} already exists and overwrite is disabled" + ) + with open(adios2_modfile_file, "w") as f: + f.write(adios2_modfile) + + os.chmod(os.path.join(settings.install_prefix, "install.sh"), 0o755) + MESSAGE = f"- installation script written to {os.path.join(settings.install_prefix, 'install.sh')}!\n" + MESSAGE += " please read and verify it before running.\n\n" + if settings.write_modulefiles: + MESSAGE += f"- module files have been written to {os.path.join(settings.install_prefix, 'modules')} directory.\n" + MESSAGE += f" add them to your .rc script as `module use --append {os.path.join(settings.install_prefix, 'modules')}`\n\n" + + if settings.apps.get("nt2py", False): + MESSAGE += ( + "- nt2py installed in a new virtual environment at " + f"{os.path.join(settings.install_prefix, '.venv')}.\n" + ) + MESSAGE += " activate it with `source {}/bin/activate`.\n\n".format( + os.path.join(settings.install_prefix, ".venv") + ) + + settings_json = os.path.join(settings.install_prefix, "settings.json") + with open(settings_json, "w") as f: + f.write(settings.to_json()) + return + + +@dataclass +class MenuItem: + label: str + hint: str = "" + right: Optional[Callable[[], str]] = None + on_enter: Optional[Callable[[], None]] = None + on_space: Optional[Callable[[], None]] = None + disabled: Optional[Callable[[], bool]] = None + + +class TuiExitInstall(Exception): + pass + + +class App: + def __init__(self, stdscr): + self.stdscr = stdscr + self.s = Settings() + if os.path.exists(os.path.join(self.s.install_prefix, "settings.json")): + with open(os.path.join(self.s.install_prefix, "settings.json"), "r") as f: + data = json.load(f) + self.s.from_json(json.dumps(data)) + + self.state = "mainmenu" + self.stack: List[Tuple[str, int]] = [] + self.selected = 0 + self.scroll = 0 + self.message = "use arrows or j/k" + + self.mod_sel = 0 + self.mod_scroll = 0 + + self._init_curses() + + def _init_curses(self) -> None: + curses.curs_set(0) + self.stdscr.keypad(True) + curses.noecho() + curses.cbreak() + + if curses.has_colors(): + curses.start_color() + curses.use_default_colors() + curses.init_pair(PAIR_TITLE, COLOR_TITLE_FG, -1) + curses.init_pair(PAIR_TEXT, COLOR_TEXT_FG, -1) + curses.init_pair(PAIR_SELECTED, COLOR_SELECTED_FG, COLOR_SELECTED_BG) + curses.init_pair(PAIR_HINT, COLOR_HINT_FG, -1) + curses.init_pair(PAIR_OK, COLOR_OK_FG, -1) + curses.init_pair(PAIR_ERR, COLOR_ERR_FG, -1) + curses.init_pair(PAIR_KEY, COLOR_KEY_FG, -1) + curses.init_pair(PAIR_DIM, COLOR_DIM_FG, -1) + + def cp(self, pair_id: int) -> int: + return curses.color_pair(pair_id) if curses.has_colors() else 0 + + # ----- formatting helpers ----- + + def checkbox(self, on: bool) -> str: + return "[x]" if on else "[ ]" + + def pill(self, on: bool) -> str: + return "[on]" if on else "[off]" + + def kokkos_right(self) -> str: + arch = self.s.kokkos_arch.strip() or "-" + return f"{self.s.kokkos_version} · {self.s.kokkos_backend} · {arch}" + + def adios2_right(self) -> str: + return f"{self.s.adios2_version} · {self.s.adios2_mpi}" + + # ----- nav stack ----- + + def push(self, st: str) -> None: + self.stack.append((self.state, self.selected)) + self.state = st + self.selected = 0 + self.scroll = 0 + self.message = "" + + def pop(self) -> None: + if self.stack: + self.state, self.selected = self.stack.pop() + else: + self.state, self.selected = "mainmenu", 0 + self.scroll = 0 + self.message = "" + + # ----- drawing ----- + + def add(self, y: int, x: int, s: str, attr: int = 0) -> None: + try: + self.stdscr.addstr(y, x, s, attr) + except curses.error: + pass + + def hline(self, y: int) -> None: + _, w = self.stdscr.getmaxyx() + try: + self.stdscr.hline(y, 0, curses.ACS_HLINE, max(0, w - 1)) + except curses.error: + pass + + def draw_keybar(self, y: int, x: int, pairs: List[Tuple[str, str]]) -> None: + cur_x = x + for key, action in pairs: + self.add(y, cur_x, key, self.cp(PAIR_KEY) | curses.A_BOLD) + cur_x += len(key) + self.add(y, cur_x, " ", self.cp(PAIR_DIM)) + cur_x += 1 + self.add(y, cur_x, action, self.cp(PAIR_HINT)) + cur_x += len(action) + self.add(y, cur_x, " ", self.cp(PAIR_DIM)) + cur_x += 3 + + def breadcrumb(self) -> str: + if self.state == "mainmenu": + return "mainmenu" + if self.state == "custom": + return "mainmenu › custom install" + if self.state == "dependencies": + return "mainmenu › custom install › dependencies" + if self.state == "versions": + return "mainmenu › custom install › versions" + if self.state == "options": + return "mainmenu › custom install › options" + if self.state == "cluster": + return "mainmenu › cluster-specific" + if self.state == "preset_applied": + return f"mainmenu › cluster-specific › {self.s.cluster}" + return "mainmenu" + + def draw_menu(self, title: str, prompt: str, items: List[MenuItem]) -> None: + self.stdscr.erase() + h, w = self.stdscr.getmaxyx() + + self.add(0, 2, title, self.cp(PAIR_TITLE) | curses.A_BOLD) + bc = self.breadcrumb() + self.add(0, max(2, w - 2 - len(bc)), bc, self.cp(PAIR_DIM)) + + self.draw_keybar( + 1, + 2, + [ + ("↑/↓/j/k", "move"), + ("enter", "select"), + ("space", "toggle/cycle"), + ("b", "back"), + ("q", "quit"), + ], + ) + self.hline(2) + + status1 = f"cluster: {self.s.cluster} write modulefiles: {self.pill(self.s.write_modulefiles)} module loads: {len(self.s.module_loads)}" + status2 = ( + f"prefix: {self.s.install_prefix} dependencies: {self.s.apps_summary()}" + ) + self.add(3, 2, status1[: w - 4], self.cp(PAIR_TEXT)) + self.add(4, 2, status2[: w - 4], self.cp(PAIR_TEXT)) + self.hline(5) + + self.add(6, 2, prompt[: w - 4], self.cp(PAIR_TEXT) | curses.A_BOLD) + + list_y = 8 + footer_h = 3 + view_h = max(1, h - list_y - footer_h) + n = len(items) + + if n == 0: + self.add(list_y, 2, "(empty)", self.cp(PAIR_HINT)) + else: + self.selected = max(0, min(self.selected, n - 1)) + + if self.selected < self.scroll: + self.scroll = self.selected + if self.selected >= self.scroll + view_h: + self.scroll = self.selected - view_h + 1 + self.scroll = max(0, min(self.scroll, max(0, n - view_h))) + + shown = items[self.scroll : self.scroll + view_h] + + for i, it in enumerate(shown): + idx = self.scroll + i + sel = idx == self.selected + dis = bool(it.disabled and it.disabled()) + + row_attr = ( + self.cp(PAIR_SELECTED) | curses.A_BOLD + if sel + else (self.cp(PAIR_DIM) if dis else self.cp(PAIR_TEXT)) + ) + self.add(list_y + i, 2, f" {it.label}"[: w - 4], row_attr) + + if it.right: + rt = (it.right() or "").strip() + if rt: + rt = rt[: max(0, w - 6)] + x = max(2, w - 2 - len(rt)) + rt_attr = ( + row_attr + if sel + else (self.cp(PAIR_HINT) if not dis else self.cp(PAIR_DIM)) + ) + self.add(list_y + i, x, rt, rt_attr) + + if sel and it.hint: + self.add( + list_y + i, + min(w - 4, 30), + f" {it.hint}"[: w - 4], + self.cp(PAIR_HINT), + ) + + self.hline(h - 3) + msg = self.message or "" + if msg: + is_err = msg.startswith("error") + attr = (self.cp(PAIR_ERR) if is_err else self.cp(PAIR_OK)) | curses.A_BOLD + self.add(h - 2, 2, msg[: w - 4], attr) + self.stdscr.refresh() + + # ----- modals ----- + + def input_box(self, title: str, prompt: str, initial: str) -> Optional[str]: + h, w = self.stdscr.getmaxyx() + win_h, win_w = 9, min(86, max(46, w - 6)) + top, left = max(0, (h - win_h) // 2), max(0, (w - win_w) // 2) + + win = curses.newwin(win_h, win_w, top, left) + win.keypad(True) + win.border() + + win.addstr(1, 2, title[: win_w - 4], self.cp(PAIR_TITLE) | curses.A_BOLD) + win.addstr(2, 2, prompt[: win_w - 4], self.cp(PAIR_TEXT)) + + buf = list(initial) + curses.curs_set(1) + + while True: + win.addstr(4, 2, " " * (win_w - 4), self.cp(PAIR_TEXT)) + text = "".join(buf) + if len(text) > win_w - 4: + text = text[-(win_w - 4) :] + win.addstr(4, 2, text, self.cp(PAIR_TEXT) | curses.A_BOLD) + win.addstr(6, 2, "enter=ok esc=cancel", self.cp(PAIR_DIM)) + win.refresh() + + ch = win.getch() + if ch == 27: + curses.curs_set(0) + return None + if ch in (curses.KEY_ENTER, 10, 13): + curses.curs_set(0) + return "".join(buf).strip() + if ch in (curses.KEY_BACKSPACE, 127, 8): + if buf: + buf.pop() + elif 32 <= ch <= 126: + buf.append(chr(ch)) + + def confirm_install(self) -> bool: + arch = self.s.kokkos_arch.strip() or "-" + lines = [ + f"cluster: {self.s.cluster}", + f"overwrite existing files: {self.pill(self.s.overwrite)}", + f"write modulefiles: {self.pill(self.s.write_modulefiles)}", + f"module loads: {len(self.s.module_loads)}", + f"prefix: {self.s.install_prefix}", + f"dependencies: {self.s.apps_summary()}", + f"kokkos: {self.s.kokkos_version} · {self.s.kokkos_backend} · {arch}", + f"adios2: {self.s.adios2_version} · {self.s.adios2_mpi}", + "", + "confirm install?", + ] + + h, w = self.stdscr.getmaxyx() + win_h, win_w = min(16, max(10, h - 6)), min(94, max(52, w - 6)) + top, left = max(0, (h - win_h) // 2), max(0, (w - win_w) // 2) + + win = curses.newwin(win_h, win_w, top, left) + win.keypad(True) + win.border() + win.addstr(1, 2, "confirm", self.cp(PAIR_TITLE) | curses.A_BOLD) + + y = 3 + for ln in lines[: win_h - 6]: + win.addstr(y, 2, ln[: win_w - 4], self.cp(PAIR_TEXT)) + y += 1 + + win.addstr(win_h - 3, 2, "y=yes n=no", self.cp(PAIR_DIM)) + win.refresh() + + while True: + ch = win.getch() + if ch in (ord("y"), ord("Y")): + return True + if ch in (ord("n"), ord("N"), 27): + return False + + # ----- helpers ----- + + def cycle(self, current: str, options: List[str]) -> str: + if current not in options: + return options[0] + i = options.index(current) + return options[(i + 1) % len(options)] + + # ----- module editor ----- + + def module_editor(self) -> None: + while True: + self.stdscr.erase() + h, w = self.stdscr.getmaxyx() + + self.add(0, 2, "module lines", self.cp(PAIR_TITLE) | curses.A_BOLD) + self.draw_keybar( + 1, + 2, + [ + ("↑/↓/j/k", "move"), + ("enter", "edit"), + ("a", "add"), + ("d", "delete"), + ("u/m", "reorder"), + ("b", "back"), + ], + ) + self.hline(2) + + self.add(3, 2, f"lines: {len(self.s.module_loads)}", self.cp(PAIR_TEXT)) + self.hline(4) + + lines = self.s.module_loads + n = len(lines) + list_y = 6 + view_h = max(1, h - list_y - 3) + + if n == 0: + self.add(list_y, 2, "(empty) press a to add", self.cp(PAIR_HINT)) + else: + self.mod_sel = max(0, min(self.mod_sel, n - 1)) + if self.mod_sel < self.mod_scroll: + self.mod_scroll = self.mod_sel + if self.mod_sel >= self.mod_scroll + view_h: + self.mod_scroll = self.mod_sel - view_h + 1 + self.mod_scroll = max(0, min(self.mod_scroll, max(0, n - view_h))) + + shown = lines[self.mod_scroll : self.mod_scroll + view_h] + for i, ln in enumerate(shown): + idx = self.mod_scroll + i + sel = idx == self.mod_sel + attr = ( + self.cp(PAIR_SELECTED) | curses.A_BOLD + if sel + else self.cp(PAIR_TEXT) + ) + self.add(list_y + i, 2, f" {ln}"[: w - 4], attr) + + self.hline(h - 3) + self.add( + h - 2, + 2, + "tip: example: cuda/12.9"[: w - 4], + self.cp(PAIR_HINT), + ) + self.stdscr.refresh() + + ch = self.stdscr.getch() + if ch in (ord("q"), ord("Q"), ord("b"), 8, 127): + return + + n = len(self.s.module_loads) + self.mod_sel = 0 if n == 0 else max(0, min(self.mod_sel, n - 1)) + + if ch in (curses.KEY_UP, ord("k"), ord("K")) and n: + self.mod_sel = (self.mod_sel - 1) % n + continue + if ch in (curses.KEY_DOWN, ord("j"), ord("J")) and n: + self.mod_sel = (self.mod_sel + 1) % n + continue + + if ch in (ord("a"), ord("A")): + val = self.input_box("add module line", "example: cuda/12.9", "") + if val: + self.s.module_loads.append(val) + self.mod_sel = len(self.s.module_loads) - 1 + continue + + if ch in (ord("d"), ord("D")): + if n == 0: + continue + val = self.input_box("delete line", "type 'delete' to confirm:", "") + if val == "delete": + del self.s.module_loads[self.mod_sel] + self.mod_sel = max( + 0, min(self.mod_sel, len(self.s.module_loads) - 1) + ) + continue + + if ch in (ord("u"), ord("U")): + if n >= 2 and self.mod_sel > 0: + i = self.mod_sel + self.s.module_loads[i - 1], self.s.module_loads[i] = ( + self.s.module_loads[i], + self.s.module_loads[i - 1], + ) + self.mod_sel -= 1 + continue + + if ch in (ord("m"), ord("M")): + if n >= 2 and self.mod_sel < n - 1: + i = self.mod_sel + self.s.module_loads[i + 1], self.s.module_loads[i] = ( + self.s.module_loads[i], + self.s.module_loads[i + 1], + ) + self.mod_sel += 1 + continue + + if ch in (curses.KEY_ENTER, 10, 13): + if n == 0: + continue + cur = self.s.module_loads[self.mod_sel] + val = self.input_box("edit module line", "edit the selected line:", cur) + if val is not None and val.strip(): + self.s.module_loads[self.mod_sel] = val.strip() + continue + + # ----- menus ----- + + def versions_menu(self) -> Tuple[str, str, List[MenuItem]]: + def edit_kokkos(): + val = self.input_box( + "kokkos version", "enter version/tag:", self.s.kokkos_version + ) + if val: + self.s.kokkos_version = val.strip() + + def edit_adios2(): + val = self.input_box( + "adios2 version", "enter version/tag:", self.s.adios2_version + ) + if val: + self.s.adios2_version = val.strip() + + return ( + "versions", + "set versions:", + [ + MenuItem( + "kokkos version", + "enter to edit", + right=lambda: self.s.kokkos_version, + on_enter=edit_kokkos, + ), + MenuItem( + "adios2 version", + "enter to edit", + right=lambda: self.s.adios2_version, + on_enter=edit_adios2, + ), + MenuItem("back", "return", on_enter=self.pop), + ], + ) + + def options_menu(self) -> Tuple[str, str, List[MenuItem]]: + def cycle_kokkos(): + self.s.kokkos_backend = self.cycle(self.s.kokkos_backend, KOKKOS_BACKENDS) + + def edit_kokkos_arch(): + val = self.input_box( + "kokkos arch", "enter arch text (free-form):", self.s.kokkos_arch + ) + if val is not None: + self.s.kokkos_arch = val.strip() + + def cycle_adios2(): + self.s.adios2_mpi = self.cycle(self.s.adios2_mpi, ADIOS2_MPI_MODES) + + return ( + "options", + "set build options:", + [ + MenuItem( + "kokkos backend", + "space cycles: cpu/cuda/hip/sycl", + right=lambda: self.s.kokkos_backend, + on_enter=cycle_kokkos, + on_space=cycle_kokkos, + disabled=lambda: not self.s.apps.get("Kokkos", False), + ), + MenuItem( + "kokkos arch", + "enter to edit (optional)", + right=lambda: (self.s.kokkos_arch.strip() or "-"), + on_enter=edit_kokkos_arch, + disabled=lambda: not self.s.apps.get("Kokkos", False), + ), + MenuItem( + "adios2 mpi", + "space cycles: non-mpi/mpi", + right=lambda: self.s.adios2_mpi, + on_enter=cycle_adios2, + on_space=cycle_adios2, + disabled=lambda: not self.s.apps.get("adios2", False), + ), + MenuItem("back", "return", on_enter=self.pop), + ], + ) + + def menu_main(self) -> Tuple[str, str, List[MenuItem]]: + return ( + "entity deps", + "main menu:", + [ + MenuItem( + "custom install", + "edit settings then install", + on_enter=lambda: self.push("custom"), + ), + MenuItem( + "cluster-specific", + "apply a cluster-specific preset (editable)", + on_enter=lambda: self.push("cluster"), + ), + MenuItem("exit", "", on_enter=lambda: setattr(self, "state", "exit")), + ], + ) + + def menu_custom(self) -> Tuple[str, str, List[MenuItem]]: + def toggle_write_modulefiles(): + self.s.write_modulefiles = not self.s.write_modulefiles + + def toggle_overwrite(): + self.s.overwrite = not self.s.overwrite + + def edit_prefix(): + val = self.input_box( + "install location", "enter install prefix:", self.s.install_prefix + ) + if val: + self.s.install_prefix = os.path.expanduser(val.strip()) + + def go_apps(): + self.push("dependencies") + + def go_versions(): + self.push("versions") + + def go_options(): + self.push("options") + + def do_install(): + if not self.confirm_install(): + self.message = "cancelled." + return + on_install_confirmed(self.s) + raise TuiExitInstall + + return ( + "custom install", + "settings:", + [ + MenuItem( + "overwrite existing files", + "whether to overwrite existing files", + right=lambda: "enabled" if self.s.overwrite else "disabled", + on_enter=toggle_overwrite, + on_space=toggle_overwrite, + ), + MenuItem( + "write modulefiles", + "whether to create module files", + right=lambda: "enabled" if self.s.write_modulefiles else "disabled", + on_enter=toggle_write_modulefiles, + on_space=toggle_write_modulefiles, + ), + MenuItem( + "module load lines", + "add/remove modules to load", + right=lambda: f"{len(self.s.module_loads)} entry(s)", + on_enter=self.module_editor, + ), + MenuItem( + "install location", + "root location where modules and dependencies are installed", + right=lambda: self.s.install_prefix, + on_enter=edit_prefix, + ), + MenuItem( + "dependencies to install", + "select which dependencies to install", + right=lambda: self.s.apps_summary(), + on_enter=go_apps, + ), + MenuItem( + "versions", + "edit dependency versions", + right=lambda: " · ".join( + [ + a + for (a, ae) in zip( + [ + f"kokkos {self.s.kokkos_version}", + f"adios2 {self.s.adios2_version}", + ], + [ + self.s.apps.get(app, False) + for app in ["Kokkos", "adios2"] + ], + ) + if ae + ] + ), + on_enter=go_versions, + ), + MenuItem( + "options", + "pick backends/architectures/mpi", + right=lambda: " · ".join( + [ + a + for (a, ae) in zip( + [ + f"kokkos {self.s.kokkos_backend}/{self.s.kokkos_arch.strip() or '-'}", + f"adios2 {self.s.adios2_mpi}", + ], + [ + self.s.apps.get(app, False) + for app in ["Kokkos", "adios2"] + ], + ) + if ae + ] + ), + on_enter=go_options, + ), + MenuItem("install", "", on_enter=do_install), + MenuItem("back", "", on_enter=self.pop), + ], + ) + + def menu_apps(self) -> Tuple[str, str, List[MenuItem]]: + def toggle(k: str): + self.s.apps[k] = not self.s.apps.get(k, False) + + return ( + "dependencies", + "select the dependencies:", + [ + MenuItem( + f"{self.checkbox(self.s.apps.get('Kokkos', False))} kokkos", + "", + on_enter=lambda: toggle("Kokkos"), + on_space=lambda: toggle("Kokkos"), + right=self.kokkos_right, + ), + MenuItem( + f"{self.checkbox(self.s.apps.get('adios2', False))} adios2", + "", + on_enter=lambda: toggle("adios2"), + on_space=lambda: toggle("adios2"), + right=self.adios2_right, + ), + MenuItem( + f"{self.checkbox(self.s.apps.get('nt2py', False))} nt2py", + "", + on_enter=lambda: toggle("nt2py"), + on_space=lambda: toggle("nt2py"), + ), + MenuItem("back", "", on_enter=self.pop), + ], + ) + + def menu_cluster(self) -> Tuple[str, str, List[MenuItem]]: + def choose(name: str): + print ("CALLING:", name) + apply_preset(self.s, name) + self.push("custom") + + return ( + "cluster-specific", + "pick a preset:", + [MenuItem(cluster, "apply preset", on_enter=lambda c=cluster: choose(c)) for cluster in list(PRESETS.keys())] + [MenuItem("back", "", on_enter=self.pop)], + ) + + def get_menu(self) -> Tuple[str, str, List[MenuItem]]: + if self.state == "mainmenu": + return self.menu_main() + if self.state == "custom": + return self.menu_custom() + if self.state == "dependencies": + return self.menu_apps() + if self.state == "versions": + return self.versions_menu() + if self.state == "options": + return self.options_menu() + if self.state == "cluster": + return self.menu_cluster() + self.state = "mainmenu" + return self.menu_main() + + # ----- navigation ----- + + def is_disabled(self, it: MenuItem) -> bool: + return bool(it.disabled and it.disabled()) + + def move_sel(self, items: List[MenuItem], delta: int) -> None: + if not items: + return + n = len(items) + start = self.selected + for _ in range(n): + self.selected = (self.selected + delta) % n + if not self.is_disabled(items[self.selected]): + return + self.selected = start + + def activate(self, items: List[MenuItem], enter: bool) -> None: + if not items: + return + it = items[self.selected] + if self.is_disabled(it): + self.message = "error: option disabled." + return + fn = it.on_enter if enter else it.on_space + if fn: + fn() + + # ----- loop ----- + + def run(self) -> None: + while True: + if self.state == "exit": + return + + title, prompt, items = self.get_menu() + self.draw_menu(title, prompt, items) + + ch = self.stdscr.getch() + + if ch in (ord("q"), ord("Q")): + self.state = "exit" + continue + + if ch in (ord("b"), 8, 127): + self.pop() + continue + + if ch in (curses.KEY_UP, ord("k"), ord("K")): + self.move_sel(items, -1) + continue + + if ch in (curses.KEY_DOWN, ord("j"), ord("J")): + self.move_sel(items, +1) + continue + + if ch in (curses.KEY_ENTER, 10, 13): + self.activate(items, enter=True) + continue + + if ch == ord(" "): + self.activate(items, enter=False) + continue + + +def _wrapper_capture(stdscr) -> None: + app = App(stdscr) + try: + app.run() + except TuiExitInstall: + raise + + +if __name__ == "__main__": + try: + curses.wrapper(_wrapper_capture) + raise SystemExit(0) + except TuiExitInstall: + print(MESSAGE) + raise SystemExit(0) + except KeyboardInterrupt: + raise SystemExit(130) diff --git a/dev/nix/adios2.nix b/dev/nix/adios2.nix index a3890b788..810c228dc 100644 --- a/dev/nix/adios2.nix +++ b/dev/nix/adios2.nix @@ -6,9 +6,9 @@ let name = "adios2"; - version = "2.10.2"; + version = "2.11.0"; cmakeFlags = { - CMAKE_CXX_STANDARD = "17"; + CMAKE_CXX_STANDARD = "20"; CMAKE_CXX_EXTENSIONS = "OFF"; CMAKE_POSITION_INDEPENDENT_CODE = "TRUE"; BUILD_SHARED_LIBS = "ON"; @@ -30,7 +30,7 @@ stdenv.mkDerivation { src = pkgs.fetchgit { url = "https://github.com/ornladios/ADIOS2/"; rev = "v${version}"; - sha256 = "sha256-NVyw7xoPutXeUS87jjVv1YxJnwNGZAT4QfkBLzvQbwg="; + sha256 = "sha256-yHPI///17poiCEb7Luu5qfqxTWm9Nh+o9r57mZT26U0="; }; nativeBuildInputs = with pkgs; [ diff --git a/dev/nix/kokkos.nix b/dev/nix/kokkos.nix index 7d86e665b..f51d2c685 100644 --- a/dev/nix/kokkos.nix +++ b/dev/nix/kokkos.nix @@ -7,25 +7,26 @@ let name = "kokkos"; - pversion = "4.7.01"; + pversion = "5.0.1"; compilerPkgs = { "HIP" = with pkgs.rocmPackages; [ - llvm.rocm-merged-llvm + clang rocm-core clr rocthrust rocprim rocminfo rocm-smi + pkgs.clang-tools ]; "CUDA" = with pkgs.cudaPackages; [ - llvmPackages_18.clang-tools + pkgs.clang-tools cudatoolkit cuda_cudart pkgs.gcc13 ]; "NONE" = [ - pkgs.llvmPackages_18.clang-tools + pkgs.clang-tools pkgs.gcc13 ]; }; @@ -39,8 +40,8 @@ let "HIP" = [ "-D Kokkos_ENABLE_HIP=ON" "-D Kokkos_ARCH_${getArch { }}=ON" - "-D AMDGPU_TARGETS=${builtins.replaceStrings [ "amd_" ] [ "" ] (pkgs.lib.toLower (getArch { }))}" - "-D CMAKE_CXX_COMPILER=hipcc" + "-D GPU_TARGETS=${builtins.replaceStrings [ "amd_" ] [ "" ] (pkgs.lib.toLower (getArch { }))}" + "-D CMAKE_CXX_COMPILER=clang++" ]; "CUDA" = [ "-D Kokkos_ENABLE_CUDA=ON" @@ -56,7 +57,7 @@ pkgs.stdenv.mkDerivation rec { src = pkgs.fetchgit { url = "https://github.com/kokkos/kokkos/"; rev = "${pversion}"; - sha256 = "sha256-MgphOsKE8umgYxVQZzex+elgvDDC09JaMCoU5YXaLco="; + sha256 = "sha256-ChpwGBwE7sNovjdAM/iCeOqqwGufKxAh5vQ3qK6aFBU="; }; nativeBuildInputs = with pkgs; [ @@ -78,7 +79,7 @@ pkgs.stdenv.mkDerivation rec { configurePhase = '' cmake -B build -D CMAKE_BUILD_TYPE=Release \ - -D CMAKE_CXX_STANDARD=17 \ + -D CMAKE_CXX_STANDARD=20 \ -D CMAKE_CXX_EXTENSIONS=OFF \ -D CMAKE_POSITION_INDEPENDENT_CODE=TRUE \ ${pkgs.lib.concatStringsSep " " cmakeExtraFlags.${gpu}} \ diff --git a/dev/nix/shell.nix b/dev/nix/shell.nix index 0d4cc9119..addd470fa 100644 --- a/dev/nix/shell.nix +++ b/dev/nix/shell.nix @@ -12,7 +12,7 @@ let gpuUpper = pkgs.lib.toUpper gpu; archUpper = pkgs.lib.toUpper arch; - name = "entity-dev"; + name = "nt2"; adios2Pkg = (pkgs.callPackage ./adios2.nix { inherit pkgs mpi hdf5; }); kokkosPkg = ( pkgs.callPackage ./kokkos.nix { @@ -29,15 +29,18 @@ let CC = "gcc"; }; HIP = { - CXX = "hipcc"; - CC = "hipcc"; + CXX = "clang++"; + CC = "clang"; }; CUDA = { }; }; }; in pkgs.mkShell { - name = "${name}-env"; + name = + "${name}" + + (if gpu != "NONE" then "-${pkgs.lib.toLower gpu}" else "") + + (if mpi then "-mpi" else ""); nativeBuildInputs = with pkgs; [ zlib cmake @@ -45,8 +48,7 @@ pkgs.mkShell { adios2Pkg kokkosPkg - python312 - python312Packages.jupyter + python314 cmake-format cmake-lint diff --git a/dev/scripts/format.sh b/dev/scripts/format.sh new file mode 100755 index 000000000..e12bef2ec --- /dev/null +++ b/dev/scripts/format.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +verify=false + +for arg in "$@"; do + case $arg in + --verify) verify=true ;; + esac +done + +if $verify; then + diff_output="" + + if command -v cmake-format &>/dev/null; then + while IFS= read -r -d '' f; do + if ! diff -q <(cmake-format "$f") "$f" &>/dev/null; then + diff_output+=" $f\n" + fi + done < <(find cmake/ src/ minimal/ tests/ -type f \( -name "*.cmake" -o -name "*.txt" \) -print0) + fi + + if command -v clang-format &>/dev/null; then + while IFS= read -r -d '' f; do + if ! clang-format --style=file --dry-run --Werror "$f" &>/dev/null; then + diff_output+=" $f\n" + fi + done < <(find pgens/ src/ minimal/ tests/ -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \) -print0) + fi + + if [ -n "$diff_output" ]; then + echo "Formatting check failed. The following files need formatting:" + printf "$diff_output" + exit 1 + else + echo "All files are properly formatted." + fi +else + if command -v cmake-format &>/dev/null; then + find cmake/ src/ minimal/ tests/ -type f -name "*.cmake" -o -name "*.txt" | xargs cmake-format -i + fi + + if command -v clang-format &>/dev/null; then + find pgens/ src/ minimal/ tests/ -type f -name "*.cpp" -o -name "*.hpp" -o -name "*.h" | xargs clang-format --style=file -i + fi +fi diff --git a/dev/scripts/tests.sh b/dev/scripts/tests.sh new file mode 100755 index 000000000..65d22b209 --- /dev/null +++ b/dev/scripts/tests.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +build_dir="" +extra_flags="" +nproc=$(nproc) +with_pgens=false +run_pgens=false + +make_plots=false + +with_tests=false +run_tests=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --build) + if [[ -z "${2:-}" || "$2" == --* ]]; then + echo "Error: --build requires a value" + exit 1 + fi + build_dir="$2" + shift 2 + ;; + --with_pgens) + with_pgens=true + run_pgens=true + if [[ "$2" == "compile" ]]; then + run_pgens=false + shift 2 + else + shift + fi + ;; + --with_tests) + with_tests=true + run_tests=true + if [[ "$2" == "compile" ]]; then + run_tests=false + shift 2 + else + shift + fi + ;; + --make_plots) + make_plots=true + shift + ;; + --flags) + extra_flags="$2" + shift 2 + ;; + --nproc) + if [[ -z "${2:-}" || "$2" == --* ]]; then + echo "Error: --nproc requires a value" + exit 1 + fi + nproc="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 --build [--flags ] [--with_pgens ] [--with_tests ] [--make_plots] [--nproc ]" + exit 1 + ;; + esac +done + +if [ "${with_pgens}" = false ] && [ "${with_tests}" = false ]; then + + echo "Error: At least one of --with_pgens or --with_tests must be specified" + exit 1 + +fi + +if [ "${make_plots}" = true ] && [ "${run_pgens}" = false ]; then + + echo "Error: --make_plots requires --with_pgens to be specified" + exit 1 + +fi + +if [ "${make_plots}" = true ]; then + + if ! python3 -c "import nt2" &>/dev/null; then + echo "Error: nt2py python package is not installed. Please install it with 'pip install nt2py' and try again or remove --make_plots." + exit 1 + fi + + if ! command -v ffmpeg &>/dev/null; then + echo "Error: ffmpeg is not installed. Please install it and try again or remove --make_plots." + exit 1 + fi + +fi + +if [ "${build_dir}" != "" ]; then + + pgens=$(find pgens/ -mindepth 2 -name "pgen.hpp" -exec dirname {} \; | sed 's|^pgens/||' | paste -sd ";" -) + example_pgens=$(find examples/ -name "pgen.hpp" -exec dirname {} \; | sed 's|^examples/|./examples/|' | paste -sd ";" -) + tutorial_pgens=$(find tutorials/ -name "pgen.hpp" -exec dirname {} \; | sed 's|^tutorials/|./tutorials/|' | paste -sd ";" -) + all_pgens="${pgens};${example_pgens};${tutorial_pgens}" + + if [ "${with_pgens}" = true ]; then + extra_flags="${extra_flags} -D pgens=${all_pgens}" + fi + + if [ "${with_tests}" = true ]; then + extra_flags="${extra_flags} -D TESTS=ON" + fi + + ( + cmake -B ${build_dir} ${extra_flags} && + cmake --build ${build_dir} -j ${nproc} + ) || exit 1 + + if [ "${run_tests}" = true ]; then + ctest --test-dir ${build_dir} --output-on-failure + fi + + if [ "${run_pgens}" = true ]; then + temp_dir="$(realpath "${build_dir}")/runs" + + for pgen in $(echo ${example_pgens} | tr ";" "\n"); do + if [[ "${pgen}" == *"particle_update"* ]]; then + continue + fi + pgen_alt=$(echo "${pgen}" | sed 's|./examples/|examples_|') + mkdir -p "${temp_dir}/${pgen_alt}" + cp "${build_dir}/${pgen_alt}/src/entity_${pgen_alt}.xc" "${temp_dir}/${pgen_alt}/" + find "${pgen}" -type f -name "*.py" -exec cp {} "${temp_dir}/${pgen_alt}/" \; + find "${pgen}" -type f -name "*.toml" -exec cp {} "${temp_dir}/${pgen_alt}/" \; + + inputs=$(find "${temp_dir}/${pgen_alt}" -type f -name "*.toml") + + for toml_file in ${inputs}; do + toml_basename=$(basename "${toml_file}") + echo "Running pgen: ${pgen} with input ${toml_basename}" + if [[ "${extra_flags}" == *"mpi=ON"* ]]; then + (cd "${temp_dir}/${pgen_alt}" && mpiexec -n 2 ./entity_${pgen_alt}.xc -input "${toml_basename}") + else + (cd "${temp_dir}/${pgen_alt}" && ./entity_${pgen_alt}.xc -input "${toml_basename}") + fi + done + if [ "${make_plots}" = true ]; then + python_scripts=$(find "${temp_dir}/${pgen_alt}" -type f -name "*.py") + for py_file in ${python_scripts}; do + py_basename=$(basename "${py_file}") + echo "Running python script: ${py_basename} for pgen ${pgen}" + (cd "${temp_dir}/${pgen_alt}" && python3 "${py_basename}") + done + fi + done + + if [ "${make_plots}" = true ]; then + mkdir -p "${temp_dir}/results" + find "${temp_dir}" -maxdepth 2 -type f \( -name "*.png" -o -name "*.mp4" \) -exec mv {} "${temp_dir}/results/" \; + fi + fi + + if [ "${make_plots}" = true ]; then + echo "Plots and videos generated by the pgens have been saved to ${temp_dir}/results/" + fi + +else + + echo "Error: --build must be specified" + exit 1 + +fi diff --git a/dev/scripts/tidy.sh b/dev/scripts/tidy.sh new file mode 100755 index 000000000..785401b48 --- /dev/null +++ b/dev/scripts/tidy.sh @@ -0,0 +1,291 @@ +#!/usr/bin/env bash +set -u + +build_dir="" +file_filter="" +fast_mode=false +use_changed=false +changed_ref="" +verify=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --build) + if [[ -z "${2:-}" || "$2" == --* ]]; then + echo "Error: --build requires a value" + exit 1 + fi + build_dir="$2" + shift 2 + ;; + --files) + if [[ -z "${2:-}" || "$2" == --* ]]; then + echo "Error: --files requires a value" + exit 1 + fi + file_filter="$2" + shift 2 + ;; + --changed) + use_changed=true + if [[ -n "${2:-}" && "$2" != --* ]]; then + changed_ref="$2" + shift 2 + else + shift + fi + ;; + --fast) + # Skip clang-analyzer-* (inter-procedural analysis; ~5-10x slower than other checks) + fast_mode=true + shift + ;; + --verify) + verify=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 --build [--files ] [--changed []] [--fast] [--verify]" + exit 1 + ;; + esac +done + +if [[ -z "$build_dir" ]]; then + echo "Error: --build is required" + exit 1 +fi + +if [[ "$use_changed" == true && -n "$file_filter" ]]; then + echo "Error: --changed and --files are mutually exclusive" + exit 1 +fi + +out_dir="tidy" +project_root="$(pwd)" +allowed_re="^${project_root}/(src|pgens)/" +jobs="$(nproc 2>/dev/null || sysctl -n hw.ncpu)" + +# Use cltcache if available for incremental runs (pip install cltcache) +# cltcache requires explicit compiler flags via '--'; it does not support -p . +# We preprocess compile_commands.json once at startup to extract per-file flags. +tidy_bin="clang-tidy" +tidy_prefix="" +_cltcache="" +if command -v cltcache &>/dev/null; then + _cltcache="cltcache" +elif [[ -x "${project_root}/.venv/bin/cltcache" ]]; then + _cltcache="${project_root}/.venv/bin/cltcache" +fi +if [[ -n "$_cltcache" ]]; then + tidy_prefix="$_cltcache" + echo "Using cltcache" +fi + +extra_checks="" +if [[ "$fast_mode" == true ]]; then + extra_checks="--checks=-clang-analyzer-*" + echo "Fast mode: skipping clang-analyzer-*" +fi + +# Build file_filter from git diff when --changed is given. +# .cpp files match directly; changed headers match all .cpp files in the same directory +# (since clang-tidy has no include graph, this is the best available heuristic). +if [[ "$use_changed" == true ]]; then + if [[ -n "$changed_ref" ]]; then + diff_files=$(git diff --name-only "${changed_ref}...HEAD" 2>/dev/null || true) + else + diff_files=$(git diff --name-only HEAD 2>/dev/null || true) + fi + + diff_files=$(echo "$diff_files" | grep -E '\.(cpp|h|hpp)$' || true) + + if [[ -z "$diff_files" ]]; then + if [[ -z "$changed_ref" ]]; then + echo "No changed .cpp/.h files in working tree. Try --changed (e.g. --changed master)." + else + echo "No changed .cpp/.h files vs ${changed_ref}." + fi + exit 0 + fi + + file_filter=$( + { + echo "$diff_files" | grep '\.cpp$' | while IFS= read -r f; do + printf '%s\n' "${project_root}/${f}" | sed 's/[.]/\\./g' + done + echo "$diff_files" | grep -E '\.(h|hpp)$' | while IFS= read -r f; do + dir="${project_root}/$(dirname "$f")" + printf '%s/[^/]+\\.cpp\n' "$(printf '%s' "$dir" | sed 's/[.]/\\./g')" + done + } | sort -u | paste -sd'|' - + ) +fi + +rm -rf "$out_dir" +mkdir -p "$out_dir" + +if [[ -n "$file_filter" ]]; then + file_list=$(jq -r '.[].file' "$build_dir/compile_commands.json" | sort -u | grep -E "$file_filter" || true) + if [[ -z "$file_list" ]]; then + echo "No files matched: $file_filter" + exit 1 + fi + echo "Matched files:" + echo "$file_list" | sed 's/^/ /' +else + file_list=$(jq -r '.[].file' "$build_dir/compile_commands.json" | sort -u) +fi + +total=$(echo "$file_list" | wc -l | tr -d ' ') + +# Precompute per-file compiler flags so cltcache can receive them via '--'. +# Uses shlex to handle shell-quoted flags (e.g. "-D FOO=\"bar\"") correctly. +# Strips flags irrelevant to compilation: -o, -c, -MF/-MT/-MQ/-MD/-MMD/-MP, +# and the source file path itself. +flags_db="" +if [[ -n "$tidy_prefix" ]]; then + flags_db=$(mktemp /tmp/tidy_flags_XXXXXX.json) + python3 - "$build_dir/compile_commands.json" >"$flags_db" <<'PYEOF' +import json, shlex, sys + +SKIP_NEXT = {"-o", "-MF", "-MT", "-MQ"} +SKIP_SELF = {"-MD", "-MMD", "-MP"} + +data = json.load(open(sys.argv[1])) +result = {} +for e in data: + args = shlex.split(e.get("command", "")) + src = e["file"] + filtered, skip = [], False + for a in args[1:]: # drop compiler binary + if skip: + skip = False + continue + if a in SKIP_NEXT: + skip = True + continue + if a in SKIP_SELF: + continue + filtered.append(a) + result[src] = filtered + +print(json.dumps(result)) +PYEOF +fi + +# Temp dir: each job touches a file here when done — race-condition-free counter +progress_dir=$(mktemp -d /tmp/tidy_progress_XXXXXX) + +# Write a helper script so each parallel job writes its own per-diagnostic-file logs +# without routing through a single serial process +tmpscript=$(mktemp /tmp/tidy_run_XXXXXX.sh) + +cleanup() { + rm -f "$tmpscript" "$flags_db" + rm -rf "$progress_dir" +} +trap cleanup EXIT + +cat >"$tmpscript" <<'ENDSCRIPT' +#!/usr/bin/env bash +file="$1" +build_dir="$2" +out_dir="$3" +project_root="$4" +tidy_bin="$5" +tidy_prefix="${6:-}" +extra_checks="${7:-}" +progress_dir="${8:-}" +flags_db="${9:-}" +allowed_re="^${project_root}/(src|pgens)/" + +tmpout=$(mktemp /tmp/tidy_out_XXXXXX) +trap 'rm -f "$tmpout"' EXIT + +if [[ -n "$tidy_prefix" && -n "$flags_db" ]]; then + # cltcache requires explicit compiler flags via '--' + mapfile -t compile_args < <(jq -r --arg f "$file" '(.[$f] // [])[]' "$flags_db") + if [[ ${#compile_args[@]} -gt 0 ]]; then + # shellcheck disable=SC2086 + $tidy_prefix "$tidy_bin" --quiet $extra_checks "$file" -- "${compile_args[@]}" > "$tmpout" 2>&1 || true + else + # File not in flags db; fall back to -p (no caching for this file) + "$tidy_bin" --quiet -p "$build_dir" $extra_checks "$file" > "$tmpout" 2>&1 || true + fi +else + # shellcheck disable=SC2086 + $tidy_prefix "$tidy_bin" --quiet -p "$build_dir" $extra_checks "$file" > "$tmpout" 2>&1 || true +fi + +# Route each diagnostic to the log for the file it occurs in (not the analyzed file) +awk -v out="$out_dir" -v root="$project_root/" -v allowed="$allowed_re" ' + /^\/?[^:]+:[0-9]+:[0-9]+:/ { + match($0, /^[^:]+/) + current_file = substr($0, 1, RLENGTH) + if (current_file !~ allowed) { + current_file = "" + next + } + if (index(current_file, root) == 1) { + current_file = substr(current_file, length(root) + 1) + } + logfile = out "/" current_file ".log" + cmd = "mkdir -p \"$(dirname \"" logfile "\")\"" + system(cmd) + print $0 >> logfile + next + } + current_file != "" { + logfile = out "/" current_file ".log" + print $0 >> logfile + } +' "$tmpout" + +[[ -n "$progress_dir" ]] && touch "$progress_dir/$$.$RANDOM" +ENDSCRIPT +chmod +x "$tmpscript" + +# Progress bar — runs in background, polls the counter dir every 0.2s +bar_full="########################################" +bar_empty="----------------------------------------" +( + while true; do + done_n=$(find "$progress_dir" -maxdepth 1 -type f 2>/dev/null | wc -l | tr -d ' ') + filled=$((done_n * 40 / total)) + printf "\r[%s%s] %d/%d" \ + "${bar_full:0:$filled}" "${bar_empty:0:$((40 - filled))}" "$done_n" "$total" + [[ "$done_n" -ge "$total" ]] && break + sleep 0.2 + done + printf "\n" +) & +progress_pid=$! + +echo "$file_list" | xargs -P "$jobs" -I{} \ + "$tmpscript" {} "$build_dir" "$out_dir" "$project_root" "$tidy_bin" "$tidy_prefix" "$extra_checks" "$progress_dir" "$flags_db" + +kill "$progress_pid" 2>/dev/null || true +wait "$progress_pid" 2>/dev/null || true +printf "\r[%s] %d/%d\n" "$bar_full" "$total" "$total" + +# Dedup (parallel jobs may write overlapping header diagnostics from different TUs) +find "$out_dir" -name '*.log' | while read -r log; do + awk '!seen[$0]++' "$log" >"$log.tmp" && mv "$log.tmp" "$log" + [[ ! -s "$log" ]] && rm -f "$log" +done + +if [[ "$verify" == true ]]; then + logs=$(find "$out_dir" -name '*.log' | sort) + if [[ -z "$logs" ]]; then + echo "OK: no warnings or errors." + exit 0 + else + echo "FAILED: clang-tidy reported issues in:" + echo "$logs" | sed 's/^/ /' + exit 1 + fi +else + echo "Done. Logs written to $out_dir/" +fi diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..9309c4713 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,44 @@ +# Examples + +Problem generators in this directory are just examples demonstrating how to use some of the features of the Entity. + +- `custom_energy_distribution`: demonstrates how to initialize a uniform distribution of particles with two different custom-defined energy (velocity) distributions (per each species). + + image + +- `custom_spatial_distribution`: example of using the non-uniform plasma injector with a custom spatial distirbution. + + https://github.com/user-attachments/assets/bea0c290-e7e4-4ec7-b360-68ce76beab5b + +- `match_fix_field_boundaries`: example of setting matching and/or fixed (coordinate-independent) field boundaries for the electromagnetic fields. + + https://github.com/user-attachments/assets/a1b9ea22-34ce-474f-a9b0-49789a2e52b3 + +- `custom_emission`: simple example where two particles are initialized on gyrating trajectories probabilistically emitting photons while the total energy is conserved. + + https://github.com/user-attachments/assets/6c5c399a-bbab-4b93-be5f-35fed1959fcc + +- `external_fields`: example of using external fields for supplying a time-varying and species-dependent magnetic/electric fields and/or force-field imposed on particles. + + image + +- `atmosphere`: setting up a gravitationally bound "atmosphere" with a constant particle replenisher (the plot also highlights the importance of having a constant replenisher and a gravity force acting on the particles; both are enabled by default when using the `ATMOSPHERE` particle boundary conditions). + + atmosphere + +- `replenish_injector`: demonstration of how to use the replenish injector to periodically inject new plasma to a target density (both uniform and non-uniform) in `CustomPostStep`. + + https://github.com/user-attachments/assets/4955f05e-4795-439f-a4a5-8196e42e987b + + https://github.com/user-attachments/assets/ebc303ed-7b21-4f97-9822-d81124fdf962 + +- `piston`: implementing a moving piston which continuously pushes the plasma. + + https://github.com/user-attachments/assets/7d2dfc39-5de4-49cb-9d55-be8700cbcaee + +- `moving_window`: demonstrates the domain being updated to follow certain features in your simulation in one of the direction (jerkiness is due to updates taking place every `n`-th timestep, if you limit plotting to a specific physical interval, this goes away). + + https://github.com/user-attachments/assets/00edb648-af80-4d37-b1c2-c49872227d47 + + + diff --git a/examples/atmosphere/atmosphere.py b/examples/atmosphere/atmosphere.py new file mode 100644 index 000000000..45b167386 --- /dev/null +++ b/examples/atmosphere/atmosphere.py @@ -0,0 +1,36 @@ +import nt2 +import matplotlib.pyplot as plt +import numpy as np + +plt.rcParams["text.usetex"] = True +plt.rcParams["figure.dpi"] = 300 +plt.rcParams["figure.figsize"] = (6, 2) +plt.rcParams["font.family"] = "serif" + +data = nt2.Data("atmosphere") + +data.fields.coords["xh"] = ( + data.fields.coords["x"] / data.attrs["grid.boundaries.atmosphere.height"] +) + +t = 1 + +data.fields.N_1_2.sel(t=t, method="nearest").plot(x="xh", lw=1) + +xs = np.linspace(0, 20, 100) +plt.plot( + xs, + data.attrs["grid.boundaries.atmosphere.density"] * np.exp(-xs), + label=r"$n_{\rm max} \exp{\{-x/h\}}$", + lw=1, + ls="--", + c="k", +) +plt.title(rf"$t={{{t:.2f}}}$") +plt.ylabel(r"$n_-+n_+$") +plt.xlabel(r"$x/h$") +plt.xlim(0, 20) +plt.yscale("log") +plt.ylim(1e-2, 20) + +plt.savefig("atmosphere.png", bbox_inches="tight") diff --git a/examples/atmosphere/atmosphere.toml b/examples/atmosphere/atmosphere.toml new file mode 100644 index 000000000..1de21ee8b --- /dev/null +++ b/examples/atmosphere/atmosphere.toml @@ -0,0 +1,64 @@ +[simulation] + name = "atmosphere" + engine = "srpic" + runtime = 1.0 + +[grid] + resolution = [2048] + extent = [[0.0, 2.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["ATMOSPHERE", "FIXED"]] + particles = [["ATMOSPHERE", "REFLECT"]] + + [grid.boundaries.atmosphere] + temperature = 0.1 + density = 10.0 + height = 0.1 + species = [1, 2] + ds = 0.7 + +[scales] + larmor0 = 0.01 + skindepth0 = 0.01 + +[algorithms] + + [algorithms.deposit] + enable = false + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 128.0 + + [[particles.species]] + label = "e+" + mass = 1.0 + charge = 1.0 + maxnpart = 1e5 + + [[particles.species]] + label = "e-" + mass = 1.0 + charge = -1.0 + maxnpart = 1e5 + +[output] + interval = 10 + + [output.fields] + quantities = ["N_1_2"] + + [output.particles] + enable = false + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/atmosphere/pgen.hpp b/examples/atmosphere/pgen.hpp new file mode 100644 index 000000000..b20dae18e --- /dev/null +++ b/examples/atmosphere/pgen.hpp @@ -0,0 +1,32 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "traits/pgen.h" + +#include "framework/domain/metadomain.h" + +namespace user { + using namespace ntt; + + template + struct PGen { + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { + ::traits::pgen::compatible_with {} + }; + + PGen(const SimulationParams& /*params*/, + const Metadomain& /*metadomain*/) {} + }; + +} // namespace user + +#endif diff --git a/examples/custom_emission/custom_emission.py b/examples/custom_emission/custom_emission.py new file mode 100644 index 000000000..ae5d9b3be --- /dev/null +++ b/examples/custom_emission/custom_emission.py @@ -0,0 +1,51 @@ +import nt2 +import matplotlib.pyplot as plt +import numpy as np + +plt.rcParams["text.usetex"] = True + +data = nt2.Data("custom_emission") + + +def plot(t, data): + fig = plt.figure(figsize=(8, 4), dpi=150) + ax1 = fig.add_subplot(121) + prtls = data.particles.sel(t=slice(t - 0.5, t)).load() + prtls.plot.scatter( + ax=ax1, + x="x", + y="y", + color=["r" if sp == 1 else "b" for sp in prtls["sp"]], + ec=None, + alpha=(1 - (t - prtls["t"]) / 0.5) ** 2, + s=(1 - (t - prtls["t"]) / 0.5) * 5, + ) + ax1.set(xlabel=r"$x$", ylabel=r"$y$", xlim=(-1, 1), ylim=(-1, 1), aspect=1) + + ax2 = fig.add_subplot(122) + prtls1 = data.particles.sel(sp=1, t=slice(None, t)).load() + prtls2 = data.particles.sel(sp=2, t=slice(None, t)).load() + e1 = ( + prtls1.assign( + e=np.sqrt(1 + prtls1["ux"] ** 2 + prtls1["uy"] ** 2 + prtls1["uz"] ** 2) + ) + .groupby("t", as_index=False)["e"] + .sum() + ) + e2 = ( + prtls2.assign( + e=np.sqrt(prtls2["ux"] ** 2 + prtls2["uy"] ** 2 + prtls2["uz"] ** 2) + ) + .groupby("t", as_index=False)["e"] + .sum() + ) + e = e1.merge(e2, on="t", how="outer", suffixes=("_1", "_2")).fillna(0) + ax2.plot(e.t, e.e_1, label="emitters", c="r") + ax2.plot(e.t, e.e_2, label="emitted", c="b") + ax2.plot(e.t, e.e_1 + e.e_2, label="total", c="k") + ax2.set(xlabel=r"$t$", ylabel=r"total energy", xlim=(0, 5), ylim=(0, 3)) + ax2.axvline(t, color="gray", ls="--") + ax2.legend(loc="center left") + + +data.makeMovie(plot, framerate=30) diff --git a/examples/custom_emission/custom_emission.toml b/examples/custom_emission/custom_emission.toml new file mode 100644 index 000000000..850fb9ccb --- /dev/null +++ b/examples/custom_emission/custom_emission.toml @@ -0,0 +1,68 @@ +[simulation] + name = "custom_emission" + engine = "srpic" + runtime = 5.0 + +[grid] + resolution = [64, 64] + extent = [[-1.0, 1.0], [-1.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["PERIODIC"], ["PERIODIC"]] + particles = [["PERIODIC"], ["PERIODIC"]] + +[scales] + larmor0 = 0.25 + skindepth0 = 1.0 + +[algorithms] + + [algorithms.deposit] + enable = false + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 1.0 + + [[particles.species]] + label = "p" + mass = 1.0 + charge = 1.0 + maxnpart = 1e2 + emission = "custom" + + [[particles.species]] + label = "ph" + mass = 0.0 + charge = 0.0 + maxnpart = 1e4 + +[setup] + x1_arr = [0.0, 0.0] + x2_arr = [0.0, 0.0] + x3_arr = [0.0, 0.0] + ux1_arr = [0.0, 0.0] + ux2_arr = [1.0, -1.0] + ux3_arr = [0.0, 0.0] + emission_probability = 0.05 + +[output] + interval = 10 + + [output.fields] + quantities = ["N_1", "N_2", "B"] + + [output.particles] + enable = true + stride = 1 + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/custom_emission/pgen.hpp b/examples/custom_emission/pgen.hpp new file mode 100644 index 000000000..2ae082148 --- /dev/null +++ b/examples/custom_emission/pgen.hpp @@ -0,0 +1,236 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "arch/kokkos_aliases.h" +#include "traits/pgen.h" +#include "traits/policies.h" + +#include "archetypes/particle_injector.h" +#include "framework/domain/metadomain.h" +#include "kernels/injectors.hpp" + +#include + +#include +#include +#include + +namespace user { + using namespace ntt; + + template + struct InitFields { + Inline auto bx3(const coord_t&) const -> real_t { + return ONE; + } + }; + + template + struct RandomEmission { + struct Payload { + real_t photon_energy { ZERO }; + }; + + random_number_pool_t random_pool; + const real_t probability; + + array_t inj_idx { "inj_idx" }; + const npart_t inj_offset; + + array_t inj_i1, inj_i2, inj_i3; + array_t inj_dx1, inj_dx2, inj_dx3; + array_t inj_ux1, inj_ux2, inj_ux3; + array_t inj_phi; + array_t inj_weight; + array_t inj_tag; + array_t inj_pld_i; + + RandomEmission(random_number_pool_t& random_pool, + real_t probability, + npart_t inj_offset, + array_t& inj_i1, + array_t& inj_i2, + array_t& inj_i3, + array_t& inj_dx1, + array_t& inj_dx2, + array_t& inj_dx3, + array_t& inj_ux1, + array_t& inj_ux2, + array_t& inj_ux3, + array_t& inj_phi, + array_t& inj_weight, + array_t& inj_tag, + array_t& inj_pld_i) + : random_pool { random_pool } + , probability { probability } + , inj_offset { inj_offset } + , inj_i1 { inj_i1 } + , inj_i2 { inj_i2 } + , inj_i3 { inj_i3 } + , inj_dx1 { inj_dx1 } + , inj_dx2 { inj_dx2 } + , inj_dx3 { inj_dx3 } + , inj_ux1 { inj_ux1 } + , inj_ux2 { inj_ux2 } + , inj_ux3 { inj_ux3 } + , inj_phi { inj_phi } + , inj_weight { inj_weight } + , inj_tag { inj_tag } + , inj_pld_i { inj_pld_i } {} + + Inline auto shouldEmit(const coord_t&, + const coord_t&, + const vec_t& u_Ph, + const vec_t&, + const vec_t&, + vec_t& delta_u_Ph, + Payload& payload) const -> Kokkos::pair { + auto generator = random_pool.get_state(); + const auto rnd = Random(generator); + random_pool.free_state(generator); + if (rnd < probability) { + delta_u_Ph[0] = -static_cast(0.1) * u_Ph[0]; + delta_u_Ph[1] = -static_cast(0.1) * u_Ph[1]; + delta_u_Ph[2] = -static_cast(0.1) * u_Ph[2]; + + const auto uSqr = NORM_SQR(u_Ph[0], u_Ph[1], u_Ph[2]); + const auto gammaSqr = ONE + uSqr; + const auto delta_uSqr = NORM_SQR(delta_u_Ph[0], + delta_u_Ph[1], + delta_u_Ph[2]); + const auto u_dot_delta_u = DOT(u_Ph[0], + u_Ph[1], + u_Ph[2], + delta_u_Ph[0], + delta_u_Ph[1], + delta_u_Ph[2]); + payload.photon_energy = math::sqrt(gammaSqr) * + (math::sqrt(ONE + delta_uSqr / gammaSqr + + TWO * u_dot_delta_u / gammaSqr) - + ONE); + return { true, true }; + } + return { false, false }; + } + + Inline auto emit(const tuple_t& xi_Cd, + const tuple_t& dxi_Cd, + const vec_t& direction, + real_t, + real_t, + const Payload& payload) const -> void { + const auto inj_index = Kokkos::atomic_fetch_add(&inj_idx(), 1); + kernel::InjectParticle( + inj_offset + inj_index, + inj_i1, + inj_i2, + inj_i3, + inj_dx1, + inj_dx2, + inj_dx3, + inj_ux1, + inj_ux2, + inj_ux3, + inj_phi, + inj_weight, + inj_tag, + inj_pld_i, + xi_Cd, + dxi_Cd, + { payload.photon_energy * direction[0], + payload.photon_energy * direction[1], + payload.photon_energy * direction[2] }); + } + + auto emitted_species_indices() const -> std::vector { + return { 2u }; + } + + auto numbers_injected() const -> std::vector { + auto inj_idx_h = Kokkos::create_mirror_view(inj_idx); + Kokkos::deep_copy(inj_idx_h, inj_idx); + return { inj_idx_h() }; + } + }; + + template + struct PGen { + static constexpr auto D { M::Dim }; + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { + ::traits::pgen::compatible_with {} + }; + + const SimulationParams& params; + const Metadomain& metadomain; + + const real_t emission_probability; + + InitFields init_flds {}; + + PGen(const SimulationParams& p, const Metadomain& metadomain) + : params { p } + , metadomain { metadomain } + , emission_probability { params.template get( + "setup.emission_probability") } { + static_assert(EmissionPolicyClass, M>, "RandomEmission does not satisfy the requirements of an emission policy"); + } + + auto EmissionPolicy(simtime_t, spidx_t, Domain& domain) const + -> RandomEmission { + return RandomEmission { + domain.random_pool(), emission_probability, + domain.species[1].npart(), domain.species[1].i1, + domain.species[1].i2, domain.species[1].i3, + domain.species[1].dx1, domain.species[1].dx2, + domain.species[1].dx3, domain.species[1].ux1, + domain.species[1].ux2, domain.species[1].ux3, + domain.species[1].phi, domain.species[1].weight, + domain.species[1].tag, domain.species[1].pld_i + }; + } + + void InitPrtls(Domain& domain) { + const auto empty = std::vector {}; + const auto x1_arr = params.template get>( + "setup.x1_arr", + empty); + const auto x2_arr = params.template get>( + "setup.x2_arr", + empty); + const auto x3_arr = params.template get>( + "setup.x3_arr", + empty); + const auto ux1_arr = params.template get>( + "setup.ux1_arr", + empty); + const auto ux2_arr = params.template get>( + "setup.ux2_arr", + empty); + const auto ux3_arr = params.template get>( + "setup.ux3_arr", + empty); + + std::map> data_arr { + { "x1", x1_arr }, + { "x2", x2_arr }, + { "x3", x3_arr }, + { "ux1", ux1_arr }, + { "ux2", ux2_arr }, + { "ux3", ux3_arr } + }; + arch::InjectGlobally(metadomain, domain, 1u, data_arr); + } + }; + +} // namespace user + +#endif diff --git a/examples/custom_energy_distribution/custom_energy_distribution.py b/examples/custom_energy_distribution/custom_energy_distribution.py new file mode 100644 index 000000000..b561531b7 --- /dev/null +++ b/examples/custom_energy_distribution/custom_energy_distribution.py @@ -0,0 +1,22 @@ +import nt2 +import matplotlib.pyplot as plt +import numpy as np + +plt.rcParams["text.usetex"] = True + +data = nt2.Data("custom_energy_distribution") + +fig = plt.figure(figsize=(10, 5), dpi=150) +ax1 = fig.add_subplot(1, 2, 1) +ax1.set_facecolor("k") +data.particles.sel(sp=1).isel(t=0).phase_plot(ax=ax1, cmap="inferno") +ax1.set(xlabel="$x$", ylabel="$u_x$", ylim=(-1.5, 1.5)) + +ax2 = fig.add_subplot(1, 2, 2) +ax2.set_facecolor("k") +data.particles.sel(sp=2).isel(t=0).phase_plot( + ax=ax2, y_quantity=lambda df: df["uy"], cmap="inferno" +) +ax2.set(xlabel="$x$", ylabel="$u_y$", ylim=(-1.5, 1.5)) + +plt.savefig("custom_energy_distribution.png", bbox_inches="tight") diff --git a/examples/custom_energy_distribution/custom_energy_distribution.toml b/examples/custom_energy_distribution/custom_energy_distribution.toml new file mode 100644 index 000000000..ed850ba26 --- /dev/null +++ b/examples/custom_energy_distribution/custom_energy_distribution.toml @@ -0,0 +1,63 @@ +[simulation] + name = "custom_energy_distribution" + engine = "srpic" + runtime = 0.1 + +[grid] + resolution = [64, 64] + extent = [[-1.0, 1.0], [-1.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["PERIODIC"], ["PERIODIC"]] + particles = [["PERIODIC"], ["PERIODIC"]] + +[scales] + larmor0 = 1.0 + skindepth0 = 1.0 + +[algorithms] + + [algorithms.deposit] + enable = false + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 16.0 + + [[particles.species]] + label = "e+" + mass = 1.0 + charge = 1.0 + maxnpart = 1e5 + + [[particles.species]] + label = "e-" + mass = 1.0 + charge = -1.0 + maxnpart = 1e5 + +[setup] + + temperature = 1e-1 + drift_amplitude = 0.5 + +[output] + interval = 1 + + [output.fields] + enable = false + + [output.particles] + enable = true + stride = 1 + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/custom_energy_distribution/pgen.hpp b/examples/custom_energy_distribution/pgen.hpp new file mode 100644 index 000000000..623ec0d54 --- /dev/null +++ b/examples/custom_energy_distribution/pgen.hpp @@ -0,0 +1,102 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "traits/pgen.h" + +#include "archetypes/energy_dist.h" +#include "archetypes/particle_injector.h" +#include "framework/domain/metadomain.h" + +namespace user { + using namespace ntt; + + template + struct CustomDistribution_1 { + static constexpr auto D = Dim; + + CustomDistribution_1(random_number_pool_t& random_pool, + real_t temperature, + real_t drift_amplitude, + real_t box_size) + : random_pool { random_pool } + , temperature { temperature } + , drift_amplitude { drift_amplitude } + , kx { static_cast(constant::TWO_PI) / box_size } {} + + // the only requirement for the energy distribution is to have this operator() + // that takes in the particle position and velocity (by reference) and + // modifies (sets) the velocity according to the desired distribution + Inline void operator()(const coord_t& x_Ph, vec_t& v) const { + // sample a static 3D maxwellian + drift in x1 direction with sinusoidal spatial dependence + // @NOTE: for relativistic drift, use the built-in drifting Maxwellian + arch::energy_dist::JuttnerSinge(v, temperature, random_pool); + v[0] += drift_amplitude * math::sin(x_Ph[0] * kx); + } + + random_number_pool_t random_pool; + const real_t temperature, drift_amplitude, kx; + }; + + template + struct CustomDistribution_2 { + static constexpr auto D = Dim; + + CustomDistribution_2(real_t drift_amplitude) + : drift_amplitude { drift_amplitude } {} + + Inline void operator()(const coord_t& x_Ph, vec_t& v) const { + // zero temperature + counterstreaming drifts in x2 + v[1] = drift_amplitude * ((x_Ph[0] < ZERO) ? ONE : -ONE); + } + + const real_t drift_amplitude; + }; + + template + struct PGen { + + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { + ::traits::pgen::compatible_with {} + }; + + const SimulationParams& params; + const Metadomain& metadomain; + + PGen(const SimulationParams& p, const Metadomain& m) + : params { p } + , metadomain { m } {} + + void InitPrtls(Domain& domain) { + const auto temperature = params.template get("setup.temperature"); + const auto drift_amplitude = params.template get( + "setup.drift_amplitude"); + const auto box_size = metadomain.mesh().extent(in::x1).second - + metadomain.mesh().extent(in::x1).first; + const auto edist1 = CustomDistribution_1 { domain.random_pool(), + temperature, + drift_amplitude, + box_size }; + const auto edist2 = CustomDistribution_2 { drift_amplitude }; + + // distributions are then passed to the particle injector function + arch::InjectUniform( + params, + domain, + { 1u, 2u }, + { edist1, edist2 }, + ONE); + } + }; + +} // namespace user + +#endif diff --git a/examples/custom_particle_update/particle_update.toml b/examples/custom_particle_update/particle_update.toml new file mode 100644 index 000000000..017ba82bd --- /dev/null +++ b/examples/custom_particle_update/particle_update.toml @@ -0,0 +1,82 @@ +[simulation] + name = "_reflect" + engine = "srpic" + runtime = 100.0 + +[grid] + resolution = [256, 256] + extent = [[0.0, 1.0], [0.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["CONDUCTOR", "CONDUCTOR"], ["PERIODIC"]] + particles = [["PERIODIC"], ["PERIODIC"]] + +[scales] + larmor0 = 1.0 + skindepth0 = 1.0 + +[algorithms] + current_filters = 8 + + [algorithms.timestep] + CFL = 0.7071 + +[particles] + ppc0 = 10.0 + + [[particles.species]] + label = "e-" + mass = 1.0 + charge = -1.0 + maxnpart = 1e2 + + [[particles.species]] + label = "e+" + mass = 1.0 + charge = 1.0 + maxnpart = 1e2 + +[setup] + x1_e = [0.9] + x2_e = [0.8] + x3_e = [0.0] + ux1_e = [3.162] + ux2_e = [2.0] + ux3_e = [0.0] + + x1_i = [0.1] + x2_i = [0.2] + x3_i = [0.0] + ux1_i = [-3.162] + ux2_i = [-2.0] + ux3_i = [0.0] + + temperature = 0.04 + temperature_gradient = 10.0 + +[output] + format = "BPFile" + interval_time = 0.04 + + [output.fields] + quantities = ["E"] + + [output.particles] + enable = true + stride = 1 + + [output.spectra] + enable = false + + [output.debug] + ghosts = false + +[checkpoint] + keep = 1 + +[diagnostics] + log_level = "WARNING" + blocking_timers = true diff --git a/examples/custom_particle_update/pgen.hpp b/examples/custom_particle_update/pgen.hpp new file mode 100644 index 000000000..30e464099 --- /dev/null +++ b/examples/custom_particle_update/pgen.hpp @@ -0,0 +1,247 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "traits/pgen.h" + +#include "archetypes/energy_dist.h" +#include "archetypes/particle_injector.h" +#include "framework/containers/particles.h" +#include "framework/domain/domain.h" +#include "framework/domain/metadomain.h" +#include "kernels/pushers/context.h" + +#include + +#define from_Xi_to_i(XI, I) \ + { \ + (I) = static_cast(((XI) + 1)) - 1; \ + } + +#define from_Xi_to_i_di(XI, I, DI) \ + { \ + from_Xi_to_i((XI), (I)); \ + (DI) = static_cast((XI)) - static_cast(I); \ + } + +#define i_di_to_Xi(I, DI) (static_cast((I)) + static_cast((DI))) + +namespace user { + using namespace ntt; + + template + struct PGen { + static constexpr auto D { M::Dim }; + // compatibility traits for the problem generator + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { + ::traits::pgen::compatible_with {} + }; + + const SimulationParams& params; + + const Metadomain& metadomain; + + const real_t temperature, temperature_gradient; + + PGen(const SimulationParams& p, const Metadomain& m) + : params { p } + , metadomain { m } + , temperature { params.template get("setup.temperature", 0.0) } + , temperature_gradient { + params.template get("setup.temperature_gradient", 0.0) + } {} + + void InitPrtls(Domain& local_domain) { + const auto empty = std::vector {}; + const auto x1_e = params.template get>("setup.x1_e", + empty); + const auto x2_e = params.template get>("setup.x2_e", + empty); + const auto x3_e = params.template get>("setup.x3_e", + empty); + const auto phi_e = params.template get>("setup.phi_e", + empty); + const auto ux1_e = params.template get>("setup.ux1_e", + empty); + const auto ux2_e = params.template get>("setup.ux2_e", + empty); + const auto ux3_e = params.template get>("setup.ux3_e", + empty); + + const auto x1_i = params.template get>("setup.x1_i", + empty); + const auto x2_i = params.template get>("setup.x2_i", + empty); + const auto x3_i = params.template get>("setup.x3_i", + empty); + const auto phi_i = params.template get>("setup.phi_i", + empty); + const auto ux1_i = params.template get>("setup.ux1_i", + empty); + const auto ux2_i = params.template get>("setup.ux2_i", + empty); + const auto ux3_i = params.template get>("setup.ux3_i", + empty); + std::map> data_e { + { "x1", x1_e }, + { "x2", x2_e }, + { "ux1", ux1_e }, + { "ux2", ux2_e }, + { "ux3", ux3_e } + }; + std::map> data_i { + { "x1", x1_i }, + { "x2", x2_i }, + { "ux1", ux1_i }, + { "ux2", ux2_i }, + { "ux3", ux3_i } + }; + if constexpr (M::CoordType == Coord::Cartesian or D == Dim::_3D) { + data_e["x3"] = x3_e; + data_i["x3"] = x3_i; + } else if constexpr (D == Dim::_2D) { + data_e["phi"] = phi_e; + data_i["phi"] = phi_i; + } + + arch::InjectGlobally(metadomain, local_domain, (spidx_t)1, data_e); + arch::InjectGlobally(metadomain, local_domain, (spidx_t)2, data_i); + } + + auto FixFieldsConst(const bc_in&, const em&) const -> std::pair { + return { ZERO, false }; + } + + struct CustomPrtlUpdate { + random_number_pool_t pool; + real_t temp_cold, temp_hot; + real_t xmin, xmax; + + CustomPrtlUpdate(random_number_pool_t& pool, + real_t temp_cold, + real_t temp_hot, + real_t xmin, + real_t xmax) + : pool { pool } + , temp_cold { temp_cold } + , temp_hot { temp_hot } + , xmin { xmin } + , xmax { xmax } {} + + Inline void operator()(prtlidx_t p, + const kernel::sr::PusherContext& ctx, + const kernel::sr::PusherBoundaries&, + const ntt::ParticleArrays& particles, + const M& metric) const { + + const auto x_Cd = static_cast(particles.i1(p)) + + static_cast(particles.dx1(p)); + const auto x_Ph = metric.template convert<1, Crd::Cd, Crd::XYZ>(x_Cd); + vec_t v { ZERO }; + const coord_t x_dummy { ZERO }; + + // step 1: calculate the particle 3 velocity + const real_t gamma_p = math::sqrt(ONE + SQR(particles.ux1(p)) + + SQR(particles.ux2(p)) + + SQR(particles.ux3(p))); + + const real_t beta_x_p = math::abs(particles.ux1(p)) / gamma_p; + const real_t xp_prev = i_di_to_Xi(particles.i1_prev(p), + particles.dx1_prev(p)); + + // Reflecting boundary that resamples velocity + if (x_Ph < xmin) { + arch::energy_dist::JuttnerSinge(v, temp_cold, pool); + + // calculate the time for the particle to reach the wall + const int delta_i1_to_wall = particles.i1_prev(p); + const prtldx_t delta_dx1_to_wall = particles.dx1_prev(p); + const real_t dx_to_wall = i_di_to_Xi(delta_i1_to_wall, delta_dx1_to_wall); + const real_t dt_to_wall = dx_to_wall / + metric.template transform<1, Idx::XYZ, Idx::U>( + x_dummy, + beta_x_p); + + // update the velocity to the post-collision value (reflection with new speed sampled from Juttner distribution) + particles.ux1(p) = math::abs(v[0]); + particles.ux2(p) = v[1]; + particles.ux3(p) = v[2]; + + // calculate remaining time after the collision + const real_t remaining_dt = ctx.dt - dt_to_wall; + const real_t remaining_dt_inv_energy = remaining_dt / + math::sqrt( + ONE + SQR(particles.ux1(p)) + + SQR(particles.ux2(p)) + + SQR(particles.ux3(p))); + + // update the position to the post-collision value (reflection with new speed sampled from Juttner distribution) + particles.i1(p) = 0; + particles.dx1(p) = metric.template transform<1, Idx::XYZ, Idx::U>( + x_dummy, + particles.ux1(p)) * + remaining_dt_inv_energy; + + } else if (x_Ph > xmax) { + arch::energy_dist::JuttnerSinge(v, temp_hot, pool); + + // step 2: calculate the time for the particle to reach the piston + const int delta_i1_to_wall = ctx.ni1 - 1 - particles.i1_prev(p); + const prtldx_t delta_dx1_to_wall = ONE - particles.dx1_prev(p); + const real_t dx_to_wall = i_di_to_Xi(delta_i1_to_wall, delta_dx1_to_wall); + const real_t dt_to_wall = dx_to_wall / + metric.template transform<1, Idx::XYZ, Idx::U>( + x_dummy, + beta_x_p); + + // update the velocity to the post-collision value (reflection with new speed sampled from Juttner distribution) + particles.ux1(p) = -math::abs(v[0]); + particles.ux2(p) = v[1]; + particles.ux3(p) = v[2]; + + // step 3: calculate remaining time after the collision + const real_t remaining_dt = ctx.dt - dt_to_wall; + const real_t remaining_dt_inv_energy = remaining_dt / + math::sqrt( + ONE + SQR(particles.ux1(p)) + + SQR(particles.ux2(p)) + + SQR(particles.ux3(p))); + + // update the position to the post-collision value (reflection with new speed sampled from Juttner distribution) + particles.i1(p) = ctx.ni1 - 2; + particles.dx1(p) = ONE - metric.template transform<1, Idx::XYZ, Idx::U>( + x_dummy, + math::abs(particles.ux1(p))) * + remaining_dt_inv_energy; + } + } + }; + + template + auto CustomParticleUpdate(simtime_t /*time*/, spidx_t sp, D& domain) const + -> CustomPrtlUpdate { + return CustomPrtlUpdate { + domain.random_pool(), + temperature / domain.species[sp - 1].mass(), // sp is 1-indexed + temperature_gradient * temperature / domain.species[sp - 1].mass(), + metadomain.mesh().extent(in::x1).first, // xmin + metadomain.mesh().extent(in::x1).second // xmax + }; + } + }; + +} // namespace user + +#undef from_Xi_to_i_di +#undef from_Xi_to_i +#undef i_di_to_Xi + +#endif // PROBLEM_GENERATOR_H diff --git a/examples/custom_spatial_distribution/custom_spatial_distribution.py b/examples/custom_spatial_distribution/custom_spatial_distribution.py new file mode 100644 index 000000000..f554dbf8c --- /dev/null +++ b/examples/custom_spatial_distribution/custom_spatial_distribution.py @@ -0,0 +1,15 @@ +import nt2 +import matplotlib.pyplot as plt + +data = nt2.Data("custom_spatial_distribution") + +def plot(t, data): + plt.rcParams["text.usetex"] = True + plt.rcParams["figure.dpi"] = 150 + data.fields.N_1_2.sel(t=t).plot(vmin=0, vmax=1.5) + plt.gca().set(xlabel=r"$x$", ylabel=r"$y$", title=rf"$t={{{t:.2f}}}$") + plt.gcf().axes[1].set_ylabel(r"$n_1+n_2$") + plt.gca().set_aspect(1) + plt.tight_layout() + +data.makeMovie(plot, framerate=30) diff --git a/examples/custom_spatial_distribution/custom_spatial_distribution.toml b/examples/custom_spatial_distribution/custom_spatial_distribution.toml new file mode 100644 index 000000000..710758ce1 --- /dev/null +++ b/examples/custom_spatial_distribution/custom_spatial_distribution.toml @@ -0,0 +1,57 @@ +[simulation] + name = "custom_spatial_distribution" + engine = "srpic" + runtime = 2.0 + +[grid] + resolution = [128, 128] + extent = [[-1.5, 1.5], [-1.0, 2.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["PERIODIC"], ["PERIODIC"]] + particles = [["REFLECT", "REFLECT"], ["REFLECT", "REFLECT"]] + +[scales] + larmor0 = 1.0 + skindepth0 = 1.0 + +[algorithms] + + [algorithms.deposit] + enable = false + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 16.0 + + [[particles.species]] + label = "e+" + mass = 1.0 + charge = 1.0 + maxnpart = 1e6 + + [[particles.species]] + label = "e-" + mass = 1.0 + charge = -1.0 + maxnpart = 1e6 + +[output] + interval = 1 + + [output.fields] + quantities = ["N_1_2"] + + [output.particles] + stride = 10 + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/custom_spatial_distribution/pgen.hpp b/examples/custom_spatial_distribution/pgen.hpp new file mode 100644 index 000000000..486db0168 --- /dev/null +++ b/examples/custom_spatial_distribution/pgen.hpp @@ -0,0 +1,71 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "traits/pgen.h" + +#include "archetypes/energy_dist.h" +#include "archetypes/particle_injector.h" +#include "framework/domain/metadomain.h" + +namespace user { + using namespace ntt; + + template + struct CustomSpatialDistribution { + static constexpr auto D = Dim; + + CustomSpatialDistribution() {} + + // the only requirement for the spatial distribution is to have this operator() + // that takes in a position and returns the number density in that region (in units of n0) + Inline auto operator()(const coord_t& x_Ph) const -> real_t { + const auto ndens = ONE - + (SQR(x_Ph[1] - math::pow(math::abs(x_Ph[0]), THIRD)) + + SQR(x_Ph[0])); + if (ndens < ZERO) { + return ZERO; + } + return ndens; + } + }; + + template + struct PGen { + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { + ::traits::pgen::compatible_with {} + }; + const SimulationParams& params; + + PGen(const SimulationParams& p, const Metadomain& /*metadomain*/) + : params { p } {} + + void InitPrtls(Domain& domain) { + const auto sdist = CustomSpatialDistribution {}; + const auto edist = arch::energy_dist::Maxwellian( + domain.random_pool(), + static_cast(0.1)); + + // distributions are then passed to the nonuniform particle injector + // function (same energy distribution is used for both species) + arch::InjectNonUniform( + params, + domain, + { 1u, 2u }, + { edist, edist }, + sdist, + ONE); + } + }; + +} // namespace user + +#endif diff --git a/examples/external_fields/external_fields.py b/examples/external_fields/external_fields.py new file mode 100644 index 000000000..85cf72fbe --- /dev/null +++ b/examples/external_fields/external_fields.py @@ -0,0 +1,23 @@ +import nt2 +import matplotlib.pyplot as plt +import numpy as np + +plt.rcParams["text.usetex"] = True + +data = nt2.Data("external_fields") +prtls = data.particles.load() + +fig = plt.figure(figsize=(10, 5), dpi=150) +ax = fig.add_subplot(111) + +ax.scatter( + prtls["x"], + prtls["y"], + s=1, + color=np.choose(prtls["sp"].array - 1, ["r", "g", "b"]), + ec=None, + alpha=(100 - prtls["t"].array) / 100, +) +ax.set(xlim=(-1, 1), ylim=(-1, 1), xlabel=r"$x$", ylabel=r"$y$", aspect=1) + +plt.savefig("external_fields.png", bbox_inches="tight") diff --git a/examples/external_fields/external_fields.toml b/examples/external_fields/external_fields.toml new file mode 100644 index 000000000..c6881f763 --- /dev/null +++ b/examples/external_fields/external_fields.toml @@ -0,0 +1,90 @@ +[simulation] + name = "external_fields" + engine = "srpic" + runtime = 5.0 + +[grid] + resolution = [64, 64] + extent = [[-1.0, 1.0], [-1.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["PERIODIC"], ["PERIODIC"]] + particles = [["PERIODIC"], ["PERIODIC"]] + +[scales] + larmor0 = 0.25 + skindepth0 = 1.0 + +[algorithms] + + [algorithms.deposit] + enable = false + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 1.0 + + [[particles.species]] + label = "p1" + mass = 1.0 + charge = 1.0 + maxnpart = 1e2 + + [[particles.species]] + label = "p2" + mass = 1.0 + charge = 1.0 + maxnpart = 1e2 + + [[particles.species]] + label = "p3" + mass = 1.0 + charge = 1.0 + maxnpart = 1e2 + +[setup] + prtls = [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + -0.5, + 0.0, + 0.0, + 0.0, + 0.0, + ], + [ + 0.5, + 0.0, + 0.0, + 0.0, + 0.0, + ], + ] + prtl_species = [1, 2, 3] + +[output] + interval = 1 + + [output.fields] + enable = false + + [output.particles] + enable = true + stride = 1 + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/external_fields/pgen.hpp b/examples/external_fields/pgen.hpp new file mode 100644 index 000000000..ac4f6faed --- /dev/null +++ b/examples/external_fields/pgen.hpp @@ -0,0 +1,142 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "traits/pgen.h" + +#include "archetypes/particle_injector.h" +#include "framework/domain/metadomain.h" +#include "framework/parameters/parameters.h" + +#include + +#include +#include +#include + +namespace user { + using namespace ntt; + + template + struct ExtFields { + ExtFields(simtime_t time, spidx_t sp) : time { time }, sp { sp } {} + + /* + * Particle's equation of motion is: + * + * du / dt = f_ext + (q / mc) * ((E + E_ext) + v x (B + B_ext)) + * + * in dimensionless terms: + * + * du / dt = f_ext + (q / m) / (q0 / m0) * omegaB0 * ((e + e_ext) + v x (b + b_ext)) + * + * - f_ext is the external force-field (acceleration) defined here + * - E and B are interpolated fields from the grid + * - e = E / B0 and b = B / B0 + * - e_ext = E_ext / B0 and b_ext = B_ext / B0 are the dimensionless external fields defined here + * + */ + + // f_ext: external force-field (acceleration): + Inline auto fx1(const coord_t&) const -> real_t { + return (sp % 2u == 0u) ? -HALF : HALF; + } + + // b_ext: external magnetic field: + Inline auto bx3(const coord_t&) const -> real_t { + return ONE + 0.2 * time; + } + + const simtime_t time; + const spidx_t sp; + }; + + template + struct PGen { + static constexpr auto D { M::Dim }; + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { + ::traits::pgen::compatible_with {} + }; + + const SimulationParams& params; + const Metadomain& metadomain; + + PGen(const SimulationParams& p, const Metadomain& m) + : params { p } + , metadomain { m } {} + + /* + * @returns a pair of (apply_external_fields, external_fields) + * + * @note apply_external_fields is true for species other than 1 (i.e., 2 and 3 in this case) + */ + auto ExternalFields(simtime_t time, spidx_t sp, const Domain& /*domain*/) const + -> std::pair> { + // apply only to species 2 and 3 + return { + sp != 1u, + ExtFields { time, sp } + }; + } + + void InitPrtls(Domain& domain) { + const auto prtls = params.template get>>( + "setup.prtls"); + const auto prtl_species = params.template get>( + "setup.prtl_species"); + raise::ErrorIf(prtl_species.size() != prtls.size(), + "setup.prtls_species should be a vector of the same size " + "as setup.prtls", + HERE); + if (prtls.size() > 0u) { + raise::ErrorIf(prtls[0].size() != 3u + static_cast(D), + "setup.prtls should be a vector of vectors of size 3+D", + HERE); + for (auto p = 0u; p < prtls.size(); ++p) { + const auto& prtl = prtls[p]; + const auto prtl_spec = prtl_species[p]; + std::map> data_arr; + data_arr["x1"] = { prtl[0] }; + if constexpr (D == Dim::_2D or D == Dim::_3D) { + data_arr["x2"] = { prtl[1] }; + } + if constexpr (D == Dim::_3D) { + data_arr["x3"] = { prtl[2] }; + } + if constexpr (D == Dim::_1D) { + data_arr["ux1"] = { prtl[1] }; + data_arr["ux2"] = { prtl[2] }; + data_arr["ux3"] = { prtl[3] }; + } + if constexpr (D == Dim::_2D) { + data_arr["ux1"] = { prtl[2] }; + data_arr["ux2"] = { prtl[3] }; + data_arr["ux3"] = { prtl[4] }; + } + if constexpr (D == Dim::_3D) { + data_arr["ux1"] = { prtl[3] }; + data_arr["ux2"] = { prtl[4] }; + data_arr["ux3"] = { prtl[5] }; + } + raise::ErrorIf( + prtl_spec <= 0 or prtl_spec > domain.species.size(), + "setup.prtl_species should be a vector of integers between 1 and " + "the number of species in the simulation", + HERE); + arch::InjectGlobally(metadomain, domain, prtl_spec, data_arr); + } + } + } + }; + +} // namespace user + +#endif diff --git a/examples/match_fix_field_boundaries/fix_field_boundaries.toml b/examples/match_fix_field_boundaries/fix_field_boundaries.toml new file mode 100644 index 000000000..ebcb9579c --- /dev/null +++ b/examples/match_fix_field_boundaries/fix_field_boundaries.toml @@ -0,0 +1,49 @@ +[simulation] + name = "fix_field_boundaries" + engine = "srpic" + runtime = 2.0 + +[grid] + resolution = [2048] + extent = [[0.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["FIXED", "FIXED"]] + particles = [["REFLECT", "ABSORB"]] + +[scales] + larmor0 = 1.0 + skindepth0 = 1.0 + +[algorithms] + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 1.0 + +[setup] + + amplitude = 1.0 + omega = 500.0 + t_transition = 0.1 + t_duration = 0.25 + +[output] + interval_time = 0.01 + + [output.fields] + quantities = ["E", "B"] + + [output.particles] + enable = false + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/match_fix_field_boundaries/match_field_boundaries.toml b/examples/match_fix_field_boundaries/match_field_boundaries.toml new file mode 100644 index 000000000..97c476042 --- /dev/null +++ b/examples/match_fix_field_boundaries/match_field_boundaries.toml @@ -0,0 +1,52 @@ +[simulation] + name = "match_field_boundaries" + engine = "srpic" + runtime = 2.0 + +[grid] + resolution = [2048] + extent = [[0.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["FIXED", "MATCH"]] + particles = [["REFLECT", "ABSORB"]] + + [grid.boundaries.match] + ds = 0.05 + +[scales] + larmor0 = 1.0 + skindepth0 = 1.0 + +[algorithms] + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 1.0 + +[setup] + + amplitude = 1.0 + omega = 500.0 + t_transition = 0.1 + t_duration = 0.25 + +[output] + interval_time = 0.01 + + [output.fields] + quantities = ["E", "B"] + + [output.particles] + enable = false + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/match_fix_field_boundaries/match_fix_field_boundaries.py b/examples/match_fix_field_boundaries/match_fix_field_boundaries.py new file mode 100644 index 000000000..ae591a093 --- /dev/null +++ b/examples/match_fix_field_boundaries/match_fix_field_boundaries.py @@ -0,0 +1,76 @@ +import nt2 +import matplotlib.pyplot as plt + +data_fix = nt2.Data("fix_field_boundaries") +data_match = nt2.Data("match_field_boundaries") + + +def plot(t, _): + plt.rcParams["text.usetex"] = True + omega = data_fix.attrs["setup.omega"] + + data_fix.fields.coords["xo"] = data_fix.fields.coords["x"] * omega + data_match.fields.coords["xo"] = data_match.fields.coords["x"] * omega + xmax = data_match.fields.coords["xo"].max().values[()] + ds = data_match.attrs["grid.boundaries.match.ds"][0] * omega + + fig = plt.figure(figsize=(10, 5), dpi=200) + ax1 = fig.add_subplot(211) + data_fix.fields.Ez.sel(t=t, method="nearest").plot(ax=ax1, label="$E_z$", x="xo") + data_fix.fields.By.sel(t=t, method="nearest").plot(ax=ax1, label="$B_y$", x="xo") + + ax2 = fig.add_subplot(212) + data_match.fields.Ez.sel(t=t, method="nearest").plot(ax=ax2, label="$E_z$", x="xo") + data_match.fields.By.sel(t=t, method="nearest").plot(ax=ax2, label="$B_y$", x="xo") + + for ax in [ax1, ax2]: + ax.set( + xlim=(0, omega), + ylim=(-1.1, 1.1), + ylabel="", + title=None, + ) + + ax1.set(xticklabels=[], xlabel="") + ax2.set(xlabel=r"$x$ [$c / \omega$]") + ax1.set(title=rf"$\omega t={omega * t:.1f}$") + + for ax, label in [(ax1, "FIXED"), (ax2, "MATCH")]: + ax.text( + 0.99, + 0.94, + r"\texttt{" + label + "}", + transform=ax.transAxes, + ha="right", + va="top", + fontsize=8, + ) + + plt.subplots_adjust(hspace=0.35) + + mid_y = (ax1.get_position().y0 + ax2.get_position().y1) / 2 + right_x = ax1.get_position().x1 + + handles, labels = ax1.get_legend_handles_labels() + leg = fig.legend( + handles, + labels, + loc="center right", + bbox_to_anchor=(right_x, mid_y), + bbox_transform=fig.transFigure, + ncol=1, + frameon=True, + ) + leg.set_zorder(10) + ax2.fill_between( + [xmax - ds, xmax], + -1.1, + 1.1, + color="grey", + alpha=0.3, + transform=ax2.get_xaxis_transform(), + ) + plt.tight_layout() + + +data_fix.makeMovie(plot, framerate=15) diff --git a/examples/match_fix_field_boundaries/pgen.hpp b/examples/match_fix_field_boundaries/pgen.hpp new file mode 100644 index 000000000..5740ff5e4 --- /dev/null +++ b/examples/match_fix_field_boundaries/pgen.hpp @@ -0,0 +1,104 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "traits/pgen.h" + +#include "framework/domain/metadomain.h" + +namespace user { + using namespace ntt; + + template + struct ZeroFields { + Inline auto ex2(const coord_t&) const -> real_t { + return ZERO; + } + + Inline auto ex3(const coord_t&) const -> real_t { + return ZERO; + } + + Inline auto bx1(const coord_t&) const -> real_t { + return ZERO; + } + }; + + template + struct PGen { + static constexpr auto D { M::Dim }; + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { ::traits::pgen::compatible_with {} }; + + const real_t amplitude, omega; + const real_t t_transition, t_duration; + + PGen(const SimulationParams& p, const Metadomain&) + : amplitude { p.template get("setup.amplitude", ONE) } + , omega { p.template get("setup.omega", ONE) } + , t_transition { p.template get("setup.t_transition") } + , t_duration { p.template get("setup.t_duration") } {} + + /** + * Sets up the driving field on the left boundary. + * + * @param bc_in Direction of the boundary (only be used for one side, so no need to check) + * @param comp Electromagnetic component to set + * + * @note Because the wave is only set on the boundary, no coordinate dependency is needed. + * + * @note The fields are normalized to B0 (nominal magnetic field) + * @note Launching an Ez x By wave from the left boundary + * @note else-statement will only be hit if right boundary is also FIXED + * + * @return Pair of (value to set, whether to set it or not) + */ + auto FixFieldsConst(simtime_t time, const bc_in& bc, em comp) const + -> std::pair { + if (bc == bc_in::Mx1) { + const auto phase { time * omega }; + real_t ampl { ZERO }; + if (time < t_transition) { + ampl = (time / t_transition); + } else if (time < t_transition + t_duration) { + ampl = ONE; + } else { + ampl = math::max( + ONE - (static_cast(time) - t_transition - t_duration) / + t_transition, + ZERO); + } + ampl *= amplitude; + + if (comp == em::ex3) { + return { -ampl * math::cos(phase), true }; + } else if (comp == em::bx2) { + return { math::sin(phase) * ampl, true }; + } else { + return { ZERO, true }; + } + } else { + return { ZERO, true }; + } + } + + /* + * Enough to only enforce Ey, Ez and Bx to zero on the right boundary + * + * @note Only called if one of the boundaries is MATCH + */ + auto MatchFields(simtime_t) const -> ZeroFields { + return ZeroFields {}; + } + }; + +} // namespace user + +#endif diff --git a/examples/moving_window/moving_window.py b/examples/moving_window/moving_window.py new file mode 100644 index 000000000..234dc7892 --- /dev/null +++ b/examples/moving_window/moving_window.py @@ -0,0 +1,40 @@ +import nt2 +import matplotlib.pyplot as plt + + +def plot(t, data): + plt.rcParams["text.usetex"] = True + plt.rcParams["font.family"] = "serif" + plt.rcParams["figure.dpi"] = 250 + ax = plt.gca() + data.fields.N_1_2.sel(t=t, method="nearest").plot( + vmin=0, vmax=1.5, ax=ax, cmap="inferno" + ) + plt.gcf().axes[1].set_ylabel(r"$n_-+n_+$") + + ax.annotate( + "", + xy=(0.28, 0.08), + xycoords="axes fraction", + xytext=(0.08, 0.08), + textcoords="axes fraction", + arrowprops=dict(arrowstyle="-|>", color="#e05c2e", lw=1.8), + ) + ax.text( + 0.095, + 0.095, + r"$v_0$", + transform=ax.transAxes, + color="#e05c2e", + fontsize=12, + va="center", + ) + ax.set( + xlabel=r"$x$", + ylabel=r"$y$", + title=rf"$t={{{t:.2f}}}$", + ) + + +moving_window = nt2.Data("moving_window") +moving_window.makeMovie(plot, framerate=10) diff --git a/examples/moving_window/moving_window.toml b/examples/moving_window/moving_window.toml new file mode 100644 index 000000000..1e07cf5e2 --- /dev/null +++ b/examples/moving_window/moving_window.toml @@ -0,0 +1,57 @@ +[simulation] + name = "moving_window" + engine = "srpic" + runtime = 2.0 + +[grid] + resolution = [128, 128] + extent = [[0.0, 1.0], [0.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["PERIODIC"], ["PERIODIC"]] + particles = [["ABSORB", "ABSORB"], ["PERIODIC"]] + +[scales] + larmor0 = 0.01 + skindepth0 = 0.1 + +[algorithms] + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 8.0 + + [[particles.species]] + label = "e+" + mass = 1.0 + charge = 1.0 + maxnpart = 1e6 + + [[particles.species]] + label = "e-" + mass = 1.0 + charge = -1.0 + maxnpart = 1e6 + +[setup] + window_velocity = 0.2 + +[output] + interval = 10 + + [output.fields] + quantities = ["N_1_2"] + + [output.particles] + enable = false + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/moving_window/pgen.hpp b/examples/moving_window/pgen.hpp new file mode 100644 index 000000000..58d4ff103 --- /dev/null +++ b/examples/moving_window/pgen.hpp @@ -0,0 +1,114 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "arch/kokkos_aliases.h" +#include "traits/pgen.h" + +#include "archetypes/energy_dist.h" +#include "archetypes/moving_window.h" +#include "archetypes/particle_injector.h" +#include "framework/domain/domain.h" +#include "framework/domain/metadomain.h" +#include "framework/parameters/parameters.h" + +namespace user { + using namespace ntt; + + template + struct NonUniformDensity { + Inline auto operator()(const coord_t& x_Ph) const -> real_t { + // example of a non-uniform target density that peaks at the center of the domain + real_t r2 { ZERO }; + for (auto d = 0u; d < D; ++d) { + r2 += SQR(x_Ph[d] - 0.5); + } + return math::exp(-r2 / SQR(static_cast(0.1))); + // ^ + // characteristic width of the density profile + } + }; + + template + struct PGen { + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { + ::traits::pgen::compatible_with {} + }; + const SimulationParams& params; + Metadomain& metadomain; + + struct MovingWindow { + int pos_i { -1 }; + int init_pos_i { -1 }; + real_t pos_di { ZERO }; + real_t vel_Cd { ZERO }; + + void init(const M& metric, real_t global_x, real_t velocity) { + const auto pos_Cd = metric.template convert<1, Crd::Ph, Crd::Cd>(global_x); + pos_i = static_cast(pos_Cd + 1) - 1; + init_pos_i = pos_i; + pos_di = pos_Cd - static_cast(pos_i); + vel_Cd = metric.template transform<1, Idx::XYZ, Idx::U>({}, velocity); + } + + void update(real_t dt, + int shift, + Metadomain& metadomain, + Domain& domain) { + pos_di += vel_Cd * dt; + pos_i += static_cast(pos_di >= ONE); + pos_di -= static_cast(pos_di >= ONE); + if ((pos_i - init_pos_i) >= shift) { + // move the window and all fields and particles in it + arch::MoveWindow(domain, metadomain, shift); + // update window index for next update + pos_i -= shift; + } + } + }; + + MovingWindow moving_window; + const real_t dt; + + PGen(const SimulationParams& p, Metadomain& m) + : params { p } + , metadomain { m } + , dt { params.template get("algorithms.timestep.dt") } {} + + void InitPrtls(Domain& domain) { + const auto window_velocity = params.template get( + "setup.window_velocity", + ZERO); + moving_window.init(domain.mesh.metric, ZERO, window_velocity); + // inject particles + const auto energy_dist = arch::energy_dist::Maxwellian( + domain.random_pool(), + 0.001, + { window_velocity, ZERO, ZERO }); + + const auto density_profile = NonUniformDensity {}; + arch::InjectNonUniform( + params, + domain, + { 1, 2 }, + { energy_dist, energy_dist }, + density_profile, + ONE); + } + + void CustomPostStep(timestep_t /*step*/, simtime_t /*time*/, Domain& domain) { + moving_window.update(dt, N_GHOSTS, metadomain, domain); + } + }; + +} // namespace user + +#endif diff --git a/examples/piston/pgen.hpp b/examples/piston/pgen.hpp new file mode 100644 index 000000000..9274374e2 --- /dev/null +++ b/examples/piston/pgen.hpp @@ -0,0 +1,111 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "arch/kokkos_aliases.h" +#include "traits/pgen.h" + +#include "archetypes/energy_dist.h" +#include "archetypes/particle_injector.h" +#include "archetypes/piston.h" +#include "framework/containers/particles.h" +#include "framework/domain/domain.h" +#include "framework/domain/metadomain.h" +#include "framework/parameters/parameters.h" +#include "kernels/pushers/context.h" + +namespace user { + using namespace ntt; + + template + struct NonUniformDensity { + Inline auto operator()(const coord_t& x_Ph) const -> real_t { + // example of a non-uniform target density that peaks at the center of the domain + real_t r2 { ZERO }; + for (auto d = 0u; d < D; ++d) { + r2 += SQR(x_Ph[d] - 0.5); + } + return math::exp(-r2 / SQR(static_cast(0.2))); + // ^ + // characteristic width of the density profile + } + }; + + template + struct PGen { + + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { + ::traits::pgen::compatible_with {} + }; + + const SimulationParams& params; + Metadomain& metadomain; + const real_t piston_velocity; + + PGen(const SimulationParams& p, Metadomain& m) + : params { p } + , metadomain { m } + , piston_velocity { params.template get("setup.piston_velocity", + 0.0) } {} + + void InitPrtls(Domain& domain) { + // inject particles + const auto energy_dist = arch::energy_dist::Maxwellian( + domain.random_pool(), + 0.2); // <-- target temperature for injection + + const auto density_profile = NonUniformDensity {}; + arch::InjectNonUniform( + params, + domain, + { 1, 2 }, + { energy_dist, energy_dist }, + density_profile, + ONE); + } + + struct CustomPrtlUpdate { + real_t x_piston; // current position of piston + real_t v_piston; + real_t global_xmax; + bool is_left; + bool massive; + + Inline void operator()(prtlidx_t p, + const kernel::sr::PusherContext& ctx, + const kernel::sr::PusherBoundaries&, + const ParticleArrays& particles, + const M& metric) const { + + real_t piston_pos; + if (x_piston > global_xmax) { + // piston has moved beyond the domain, set position to a large value so that no particles interact with it + piston_pos = global_xmax; + } else { + piston_pos = x_piston; + } + + arch::Piston(p, ctx.dt, particles, metric, piston_pos, v_piston, massive); + } + }; + + template + auto CustomParticleUpdate(simtime_t time, spidx_t /*sp*/, DOM& /*domain*/) const { + return CustomPrtlUpdate { piston_velocity * static_cast(time), + piston_velocity, + metadomain.mesh().extent(in::x1).second, + true, + true }; + }; + }; +} // namespace user + +#endif diff --git a/examples/piston/piston.py b/examples/piston/piston.py new file mode 100644 index 000000000..2f8c03152 --- /dev/null +++ b/examples/piston/piston.py @@ -0,0 +1,25 @@ +import nt2 +import matplotlib.pyplot as plt + + +def plot(t, data): + plt.rcParams["text.usetex"] = True + plt.rcParams["font.family"] = "serif" + plt.rcParams["figure.dpi"] = 250 + ax = plt.gca() + data.fields.N_1_2.sel(t=t, method="nearest").plot( + vmin=0, vmax=1.5, ax=ax, cmap="inferno" + ) + plt.gcf().axes[1].set_ylabel(r"$n_-+n_+$") + + ax.set( + xlabel=r"$x$", + ylabel=r"$y$", + title=rf"$t={{{t:.2f}}}$", + xlim=(0, 1), + ylim=(0, 1), + ) + + +piston = nt2.Data("piston") +piston.makeMovie(plot, framerate=10) diff --git a/examples/piston/piston.toml b/examples/piston/piston.toml new file mode 100644 index 000000000..0b5273afa --- /dev/null +++ b/examples/piston/piston.toml @@ -0,0 +1,57 @@ +[simulation] + name = "piston" + engine = "srpic" + runtime = 2.0 + +[grid] + resolution = [128, 128] + extent = [[0.0, 1.0], [0.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["PERIODIC"], ["PERIODIC"]] + particles = [["ABSORB", "ABSORB"], ["PERIODIC"]] + +[scales] + larmor0 = 0.01 + skindepth0 = 0.1 + +[algorithms] + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 8.0 + + [[particles.species]] + label = "e+" + mass = 1.0 + charge = 1.0 + maxnpart = 1e6 + + [[particles.species]] + label = "e-" + mass = 1.0 + charge = -1.0 + maxnpart = 1e6 + +[setup] + piston_velocity = 0.5 + +[output] + interval = 10 + + [output.fields] + quantities = ["N_1_2"] + + [output.particles] + enable = false + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/replenish_injector/nonuniform_replenish.toml b/examples/replenish_injector/nonuniform_replenish.toml new file mode 100644 index 000000000..77afab39b --- /dev/null +++ b/examples/replenish_injector/nonuniform_replenish.toml @@ -0,0 +1,57 @@ +[simulation] + name = "nonuniform_replenish" + engine = "srpic" + runtime = 2.0 + +[grid] + resolution = [128, 128] + extent = [[0.0, 1.0], [0.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["PERIODIC"], ["PERIODIC"]] + particles = [["ABSORB", "ABSORB"], ["PERIODIC"]] + +[scales] + larmor0 = 0.01 + skindepth0 = 0.1 + +[algorithms] + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 8.0 + + [[particles.species]] + label = "e+" + mass = 1.0 + charge = 1.0 + maxnpart = 1e6 + + [[particles.species]] + label = "e-" + mass = 1.0 + charge = -1.0 + maxnpart = 1e6 + +[setup] + target_density = "nonuniform" + +[output] + interval = 10 + + [output.fields] + quantities = ["N_1_2"] + + [output.particles] + enable = false + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/examples/replenish_injector/pgen.hpp b/examples/replenish_injector/pgen.hpp new file mode 100644 index 000000000..8081f8f03 --- /dev/null +++ b/examples/replenish_injector/pgen.hpp @@ -0,0 +1,120 @@ +#ifndef PROBLEM_GENERATOR_H +#define PROBLEM_GENERATOR_H + +#include "enums.h" +#include "global.h" + +#include "arch/kokkos_aliases.h" +#include "traits/pgen.h" + +#include "archetypes/energy_dist.h" +#include "archetypes/particle_injector.h" +#include "archetypes/spatial_dist.h" +#include "archetypes/utils.h" +#include "framework/domain/metadomain.h" + +namespace user { + using namespace ntt; + + template + struct EMFields { + Inline auto ex2(const coord_t&) const -> real_t { + return 0.8; + } + + Inline auto bx3(const coord_t&) const -> real_t { + return ONE; + } + }; + + template + struct NonUniformTargetDensity { + Inline auto operator()(const coord_t& x_Ph) const -> real_t { + // example of a non-uniform target density that peaks at the center of the domain + real_t r2 { ZERO }; + for (auto d = 0u; d < D; ++d) { + r2 += SQR(x_Ph[d] - HALF); + } + return math::exp(-r2 / SQR(static_cast(0.2))); + // ^ + // characteristic width of the density profile + } + }; + + template + struct PGen { + static constexpr auto D { M::Dim }; + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; + static constexpr auto dimensions { + ::traits::pgen::compatible_with {} + }; + + const SimulationParams& params; + + EMFields init_flds; + + const std::string target_density; + + PGen(const SimulationParams& p, const Metadomain& /*metadomain*/) + : params { p } + , target_density { params.template get( + "setup.target_density") } {} + + void CustomPostStep(timestep_t step, simtime_t /*time*/, Domain& domain) { + if (step % 100u != 0u) { + return; + } + // perform replenishment and injection every 100 timesteps + + // compute the density of species #1 and #2 + // and save in the field buffer (index 0) + arch::ComputeMomentWithSpecies(params, + domain, + { 1u, 2u }, + domain.fields.buff); + + const auto energy_dist = arch::energy_dist::Maxwellian( + domain.random_pool(), + 0.2); // <-- target temperature for injection + if (target_density == "uniform") { + // pass the computed density to the replenisher + const auto replenish_sdist = arch::spatial_dist::ReplenishUniform( + domain.mesh.metric, + domain.fields.buff, + 0u, // <-- index in buff where the density is stored + ONE); // <-- target density for replenishment + arch::InjectNonUniform( + params, + domain, + { 1, 2 }, + { energy_dist, energy_dist }, + replenish_sdist, + ONE); + } else { + const auto target_density_profile = NonUniformTargetDensity {}; + const auto replenish_sdist = + arch::spatial_dist::Replenish( + domain.mesh.metric, + domain.fields.buff, + 0u, // <-- index in buff where the density is stored + target_density_profile, + ONE); // <-- target density for replenishment + arch::InjectNonUniform( + params, + domain, + { 1, 2 }, + { energy_dist, energy_dist }, + replenish_sdist, + ONE); + } + } + }; + +} // namespace user + +#endif diff --git a/examples/replenish_injector/replenish_injector.py b/examples/replenish_injector/replenish_injector.py new file mode 100644 index 000000000..dad9f8f16 --- /dev/null +++ b/examples/replenish_injector/replenish_injector.py @@ -0,0 +1,66 @@ +import nt2 +import matplotlib.pyplot as plt + + +def plot(t, data): + plt.rcParams["text.usetex"] = True + plt.rcParams["font.family"] = "serif" + plt.rcParams["figure.dpi"] = 250 + ax = plt.gca() + data.fields.N_1_2.sel(t=t, method="nearest").plot( + vmin=0, vmax=1.5, ax=ax, cmap="inferno" + ) + plt.gcf().axes[1].set_ylabel(r"$n_-+n_+$") + + ax.annotate( + "", + xy=(0.08, 0.75), + xycoords="axes fraction", + xytext=(0.08, 0.55), + textcoords="axes fraction", + arrowprops=dict(arrowstyle="-|>", color="#e05c2e", lw=1.8), + ) + ax.text( + 0.095, + 0.65, + r"$E_y$", + transform=ax.transAxes, + color="#e05c2e", + fontsize=12, + va="center", + ) + + circle = plt.Circle( + (0.08, 0.35), + 0.035, + transform=ax.transAxes, + fill=False, + color="#2e80e0", + lw=1.8, + clip_on=False, + ) + ax.add_patch(circle) + ax.plot(0.08, 0.35, ".", transform=ax.transAxes, color="#2e80e0", markersize=6) + ax.text( + 0.125, + 0.35, + r"$B_z$", + transform=ax.transAxes, + color="#2e80e0", + fontsize=12, + va="center", + ) + ax.set( + xlabel=r"$x$", + ylabel=r"$y$", + title=rf"$t={{{t:.2f}}}$", + xlim=(0, 1), + ylim=(0, 1), + ) + + +data_uniform_replenish = nt2.Data("uniform_replenish") +data_uniform_replenish.makeMovie(plot, framerate=10) + +data_nonuniform_replenish = nt2.Data("nonuniform_replenish") +data_nonuniform_replenish.makeMovie(plot, framerate=10) diff --git a/examples/replenish_injector/uniform_replenish.toml b/examples/replenish_injector/uniform_replenish.toml new file mode 100644 index 000000000..37977d08e --- /dev/null +++ b/examples/replenish_injector/uniform_replenish.toml @@ -0,0 +1,57 @@ +[simulation] + name = "uniform_replenish" + engine = "srpic" + runtime = 2.0 + +[grid] + resolution = [128, 128] + extent = [[0.0, 1.0], [0.0, 1.0]] + + [grid.metric] + metric = "minkowski" + + [grid.boundaries] + fields = [["PERIODIC"], ["PERIODIC"]] + particles = [["ABSORB", "ABSORB"], ["PERIODIC"]] + +[scales] + larmor0 = 0.01 + skindepth0 = 0.1 + +[algorithms] + + [algorithms.timestep] + CFL = 0.5 + +[particles] + ppc0 = 8.0 + + [[particles.species]] + label = "e+" + mass = 1.0 + charge = 1.0 + maxnpart = 1e6 + + [[particles.species]] + label = "e-" + mass = 1.0 + charge = -1.0 + maxnpart = 1e6 + +[setup] + target_density = "uniform" + +[output] + interval = 10 + + [output.fields] + quantities = ["N_1_2"] + + [output.particles] + enable = false + + [output.spectra] + enable = false + +[checkpoint] + keep = 0 diff --git a/extern/Kokkos b/extern/Kokkos index 1b1383c60..37f70304d 160000 --- a/extern/Kokkos +++ b/extern/Kokkos @@ -1 +1 @@ -Subproject commit 1b1383c6001f3bfe9fe309ca923c2d786600cc79 +Subproject commit 37f70304dc3676691af88d3ac3ba50cddbfa337f diff --git a/extern/adios2 b/extern/adios2 index a19dad6ce..1ef0b5797 160000 --- a/extern/adios2 +++ b/extern/adios2 @@ -1 +1 @@ -Subproject commit a19dad6cecb00319825f20fd9f455ebbab903d34 +Subproject commit 1ef0b5797aeb8a1cc1bb36ec4089eaec19a2eea0 diff --git a/extern/plog b/extern/plog deleted file mode 160000 index e21baecd4..000000000 --- a/extern/plog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e21baecd4753f14da64ede979c5a19302618b752 diff --git a/include/plog/Appenders/AndroidAppender.h b/include/plog/Appenders/AndroidAppender.h new file mode 100644 index 000000000..722488ab2 --- /dev/null +++ b/include/plog/Appenders/AndroidAppender.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include + +namespace plog +{ + template + class PLOG_LINKAGE_HIDDEN AndroidAppender : public IAppender + { + public: + AndroidAppender(const char* tag) : m_tag(tag) + { + } + + virtual void write(const Record& record) PLOG_OVERRIDE + { + std::string str = Formatter::format(record); + + __android_log_print(toPriority(record.getSeverity()), m_tag, "%s", str.c_str()); + } + + private: + static android_LogPriority toPriority(Severity severity) + { + switch (severity) + { + case fatal: + return ANDROID_LOG_FATAL; + case error: + return ANDROID_LOG_ERROR; + case warning: + return ANDROID_LOG_WARN; + case info: + return ANDROID_LOG_INFO; + case debug: + return ANDROID_LOG_DEBUG; + case verbose: + return ANDROID_LOG_VERBOSE; + default: + return ANDROID_LOG_UNKNOWN; + } + } + + private: + const char* const m_tag; + }; +} diff --git a/include/plog/Appenders/ArduinoAppender.h b/include/plog/Appenders/ArduinoAppender.h new file mode 100644 index 000000000..276af323f --- /dev/null +++ b/include/plog/Appenders/ArduinoAppender.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include + +namespace plog +{ + template + class PLOG_LINKAGE_HIDDEN ArduinoAppender : public IAppender + { + public: + ArduinoAppender(Stream &stream) : m_stream(stream) + { + } + + virtual void write(const Record &record) PLOG_OVERRIDE + { + m_stream.print(Formatter::format(record).c_str()); + } + + private: + Stream &m_stream; + }; +} diff --git a/include/plog/Appenders/ColorConsoleAppender.h b/include/plog/Appenders/ColorConsoleAppender.h new file mode 100644 index 000000000..24bbc7d90 --- /dev/null +++ b/include/plog/Appenders/ColorConsoleAppender.h @@ -0,0 +1,108 @@ +#pragma once +#include +#include + +namespace plog +{ + template + class PLOG_LINKAGE_HIDDEN ColorConsoleAppender : public ConsoleAppender + { + public: +#ifdef _WIN32 +# ifdef _MSC_VER +# pragma warning(suppress: 26812) // Prefer 'enum class' over 'enum' +# endif + ColorConsoleAppender(OutputStream outStream = streamStdOut) + : ConsoleAppender(outStream) + , m_originalAttr() + { + if (this->m_isatty) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(this->m_outputHandle, &csbiInfo); + + m_originalAttr = csbiInfo.wAttributes; + } + } +#else + ColorConsoleAppender(OutputStream outStream = streamStdOut) + : ConsoleAppender(outStream) + {} +#endif + + virtual void write(const Record& record) PLOG_OVERRIDE + { + util::nstring str = Formatter::format(record); + util::MutexLock lock(this->m_mutex); + + setColor(record.getSeverity()); + this->writestr(str); + resetColor(); + } + + protected: + void setColor(Severity severity) + { + if (this->m_isatty) + { + switch (severity) + { +#ifdef _WIN32 + case fatal: + SetConsoleTextAttribute(this->m_outputHandle, foreground::kRed | foreground::kGreen | foreground::kBlue | foreground::kIntensity | background::kRed); // white on red background + break; + + case error: + SetConsoleTextAttribute(this->m_outputHandle, static_cast(foreground::kRed | foreground::kIntensity | (m_originalAttr & 0xf0))); // red + break; + + case warning: + SetConsoleTextAttribute(this->m_outputHandle, static_cast(foreground::kRed | foreground::kGreen | foreground::kIntensity | (m_originalAttr & 0xf0))); // yellow + break; + + case debug: + case verbose: + SetConsoleTextAttribute(this->m_outputHandle, static_cast(foreground::kGreen | foreground::kBlue | foreground::kIntensity | (m_originalAttr & 0xf0))); // cyan + break; +#else + case fatal: + this->m_outputStream << "\x1B[97m\x1B[41m"; // white on red background + break; + + case error: + this->m_outputStream << "\x1B[91m"; // red + break; + + case warning: + this->m_outputStream << "\x1B[93m"; // yellow + break; + + case debug: + case verbose: + this->m_outputStream << "\x1B[96m"; // cyan + break; +#endif + default: + break; + } + } + } + + void resetColor() + { + if (this->m_isatty) + { +#ifdef _WIN32 + SetConsoleTextAttribute(this->m_outputHandle, m_originalAttr); +#else + this->m_outputStream << "\x1B[0m\x1B[0K"; +#endif + } + } + + private: +#ifdef _WIN32 + WORD m_originalAttr; +#endif + }; +} diff --git a/include/plog/Appenders/ConsoleAppender.h b/include/plog/Appenders/ConsoleAppender.h new file mode 100644 index 000000000..a8925a049 --- /dev/null +++ b/include/plog/Appenders/ConsoleAppender.h @@ -0,0 +1,83 @@ +#pragma once +#include +#include +#include +#include + +namespace plog +{ + enum OutputStream + { + streamStdOut, + streamStdErr + }; + + template + class PLOG_LINKAGE_HIDDEN ConsoleAppender : public IAppender + { + public: +#ifdef _WIN32 +# ifdef _MSC_VER +# pragma warning(suppress: 26812) // Prefer 'enum class' over 'enum' +# endif + ConsoleAppender(OutputStream outStream = streamStdOut) + : m_isatty(!!_isatty(_fileno(outStream == streamStdOut ? stdout : stderr))) + , m_outputStream(outStream == streamStdOut ? std::cout : std::cerr) + , m_outputHandle() + { + if (m_isatty) + { + m_outputHandle = GetStdHandle(outStream == streamStdOut ? stdHandle::kOutput : stdHandle::kErrorOutput); + } + } +#else + ConsoleAppender(OutputStream outStream = streamStdOut) + : m_isatty(!!isatty(fileno(outStream == streamStdOut ? stdout : stderr))) + , m_outputStream(outStream == streamStdOut ? std::cout : std::cerr) + {} +#endif + + virtual void write(const Record& record) PLOG_OVERRIDE + { + util::nstring str = Formatter::format(record); + util::MutexLock lock(m_mutex); + + writestr(str); + } + + protected: + void writestr(const util::nstring& str) + { +#ifdef _WIN32 + if (m_isatty) + { + const std::wstring& wstr = util::toWide(str); + WriteConsoleW(m_outputHandle, wstr.c_str(), static_cast(wstr.size()), NULL, NULL); + } + else + { +# if PLOG_CHAR_IS_UTF8 + m_outputStream << str << std::flush; +# else + m_outputStream << util::toNarrow(str, codePage::kActive) << std::flush; +# endif + } +#else + m_outputStream << str << std::flush; +#endif + } + + private: +#ifdef __BORLANDC__ + static int _isatty(int fd) { return ::isatty(fd); } +#endif + + protected: + util::Mutex m_mutex; + const bool m_isatty; + std::ostream& m_outputStream; +#ifdef _WIN32 + HANDLE m_outputHandle; +#endif + }; +} diff --git a/include/plog/Appenders/DebugOutputAppender.h b/include/plog/Appenders/DebugOutputAppender.h new file mode 100644 index 000000000..5d7c95ef2 --- /dev/null +++ b/include/plog/Appenders/DebugOutputAppender.h @@ -0,0 +1,16 @@ +#pragma once +#include +#include + +namespace plog +{ + template + class PLOG_LINKAGE_HIDDEN DebugOutputAppender : public IAppender + { + public: + virtual void write(const Record& record) PLOG_OVERRIDE + { + OutputDebugStringW(util::toWide(Formatter::format(record)).c_str()); + } + }; +} diff --git a/include/plog/Appenders/DynamicAppender.h b/include/plog/Appenders/DynamicAppender.h new file mode 100644 index 000000000..f078c08cc --- /dev/null +++ b/include/plog/Appenders/DynamicAppender.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include + +namespace plog +{ + class PLOG_LINKAGE_HIDDEN DynamicAppender : public IAppender + { + public: + DynamicAppender& addAppender(IAppender* appender) + { + assert(appender != this); + + util::MutexLock lock(m_mutex); + m_appenders.insert(appender); + + return *this; + } + + DynamicAppender& removeAppender(IAppender* appender) + { + util::MutexLock lock(m_mutex); + m_appenders.erase(appender); + + return *this; + } + + virtual void write(const Record& record) PLOG_OVERRIDE + { + util::MutexLock lock(m_mutex); + + for (std::set::iterator it = m_appenders.begin(); it != m_appenders.end(); ++it) + { + (*it)->write(record); + } + } + + private: + mutable util::Mutex m_mutex; + std::set m_appenders; + }; +} diff --git a/include/plog/Appenders/EventLogAppender.h b/include/plog/Appenders/EventLogAppender.h new file mode 100644 index 000000000..3b7065be1 --- /dev/null +++ b/include/plog/Appenders/EventLogAppender.h @@ -0,0 +1,117 @@ +#pragma once +#include +#include + +namespace plog +{ + template + class PLOG_LINKAGE_HIDDEN EventLogAppender : public IAppender + { + public: + EventLogAppender(const util::nchar* sourceName) : m_eventSource(RegisterEventSourceW(NULL, util::toWide(sourceName).c_str())) + { + } + + ~EventLogAppender() + { + DeregisterEventSource(m_eventSource); + } + + virtual void write(const Record& record) PLOG_OVERRIDE + { + util::nstring str = Formatter::format(record); + + write(record.getSeverity(), util::toWide(str).c_str()); + } + + private: + void write(Severity severity, const wchar_t* str) + { + const wchar_t* logMessagePtr[] = { str }; + + ReportEventW(m_eventSource, logSeverityToType(severity), static_cast(severity), 0, NULL, 1, 0, logMessagePtr, NULL); + } + + static WORD logSeverityToType(plog::Severity severity) + { + switch (severity) + { + case plog::fatal: + case plog::error: + return eventLog::kErrorType; + + case plog::warning: + return eventLog::kWarningType; + + case plog::info: + case plog::debug: + case plog::verbose: + default: + return eventLog::kInformationType; + } + } + + private: + HANDLE m_eventSource; + }; + + class EventLogAppenderRegistry + { + public: + static bool add(const util::nchar* sourceName, const util::nchar* logName = PLOG_NSTR("Application")) + { + std::wstring logKeyName; + std::wstring sourceKeyName; + getKeyNames(sourceName, logName, sourceKeyName, logKeyName); + + HKEY sourceKey; + if (0 != RegCreateKeyExW(hkey::kLocalMachine, sourceKeyName.c_str(), 0, NULL, 0, regSam::kSetValue, NULL, &sourceKey, NULL)) + { + return false; + } + + const DWORD kTypesSupported = eventLog::kErrorType | eventLog::kWarningType | eventLog::kInformationType; + RegSetValueExW(sourceKey, L"TypesSupported", 0, regType::kDword, reinterpret_cast(&kTypesSupported), sizeof(kTypesSupported)); + + const wchar_t kEventMessageFile[] = L"%windir%\\Microsoft.NET\\Framework\\v4.0.30319\\EventLogMessages.dll;%windir%\\Microsoft.NET\\Framework\\v2.0.50727\\EventLogMessages.dll"; + RegSetValueExW(sourceKey, L"EventMessageFile", 0, regType::kExpandSz, reinterpret_cast(kEventMessageFile), sizeof(kEventMessageFile) - sizeof(*kEventMessageFile)); + + RegCloseKey(sourceKey); + return true; + } + + static bool exists(const util::nchar* sourceName, const util::nchar* logName = PLOG_NSTR("Application")) + { + std::wstring logKeyName; + std::wstring sourceKeyName; + getKeyNames(sourceName, logName, sourceKeyName, logKeyName); + + HKEY sourceKey; + if (0 != RegOpenKeyExW(hkey::kLocalMachine, sourceKeyName.c_str(), 0, regSam::kQueryValue, &sourceKey)) + { + return false; + } + + RegCloseKey(sourceKey); + return true; + } + + static void remove(const util::nchar* sourceName, const util::nchar* logName = PLOG_NSTR("Application")) + { + std::wstring logKeyName; + std::wstring sourceKeyName; + getKeyNames(sourceName, logName, sourceKeyName, logKeyName); + + RegDeleteKeyW(hkey::kLocalMachine, sourceKeyName.c_str()); + RegDeleteKeyW(hkey::kLocalMachine, logKeyName.c_str()); + } + + private: + static void getKeyNames(const util::nchar* sourceName, const util::nchar* logName, std::wstring& sourceKeyName, std::wstring& logKeyName) + { + const std::wstring kPrefix = L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\"; + logKeyName = kPrefix + util::toWide(logName); + sourceKeyName = logKeyName + L"\\" + util::toWide(sourceName); + } + }; +} diff --git a/include/plog/Appenders/IAppender.h b/include/plog/Appenders/IAppender.h new file mode 100644 index 000000000..56b7827ae --- /dev/null +++ b/include/plog/Appenders/IAppender.h @@ -0,0 +1,16 @@ +#pragma once +#include +#include + +namespace plog +{ + class PLOG_LINKAGE IAppender + { + public: + virtual ~IAppender() + { + } + + virtual void write(const Record& record) = 0; + }; +} diff --git a/include/plog/Appenders/RollingFileAppender.h b/include/plog/Appenders/RollingFileAppender.h new file mode 100644 index 000000000..3b667287a --- /dev/null +++ b/include/plog/Appenders/RollingFileAppender.h @@ -0,0 +1,148 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace plog +{ + template > + class PLOG_LINKAGE_HIDDEN RollingFileAppender : public IAppender + { + public: + RollingFileAppender(const util::nchar* fileName, size_t maxFileSize = 0, int maxFiles = 0) + : m_fileSize() + , m_maxFileSize() + , m_maxFiles(maxFiles) + , m_firstWrite(true) + { + setFileName(fileName); + setMaxFileSize(maxFileSize); + } + +#if defined(_WIN32) && !PLOG_CHAR_IS_UTF8 + RollingFileAppender(const char* fileName, size_t maxFileSize = 0, int maxFiles = 0) + : m_fileSize() + , m_maxFileSize() + , m_maxFiles(maxFiles) + , m_firstWrite(true) + { + setFileName(fileName); + setMaxFileSize(maxFileSize); + } +#endif + + virtual void write(const Record& record) PLOG_OVERRIDE + { + util::MutexLock lock(m_mutex); + + if (m_firstWrite) + { + openLogFile(); + m_firstWrite = false; + } + else if (m_maxFiles > 0 && m_fileSize > m_maxFileSize && static_cast(-1) != m_fileSize) + { + rollLogFiles(); + } + + size_t bytesWritten = m_file.write(Converter::convert(Formatter::format(record))); + + if (static_cast(-1) != bytesWritten) + { + m_fileSize += bytesWritten; + } + } + + void setFileName(const util::nchar* fileName) + { + util::MutexLock lock(m_mutex); + + util::splitFileName(fileName, m_fileNameNoExt, m_fileExt); + + m_file.close(); + m_firstWrite = true; + } + +#if defined(_WIN32) && !PLOG_CHAR_IS_UTF8 + void setFileName(const char* fileName) + { + setFileName(util::toWide(fileName).c_str()); + } +#endif + + void setMaxFiles(int maxFiles) + { + m_maxFiles = maxFiles; + } + + void setMaxFileSize(size_t maxFileSize) + { + m_maxFileSize = (std::max)(maxFileSize, static_cast(1000)); // set a lower limit for the maxFileSize + } + + void rollLogFiles() + { + m_file.close(); + + util::nstring lastFileName = buildFileName(m_maxFiles - 1); + util::File::unlink(lastFileName); + + for (int fileNumber = m_maxFiles - 2; fileNumber >= 0; --fileNumber) + { + util::nstring currentFileName = buildFileName(fileNumber); + util::nstring nextFileName = buildFileName(fileNumber + 1); + + util::File::rename(currentFileName, nextFileName); + } + + openLogFile(); + m_firstWrite = false; + } + + private: + void openLogFile() + { + m_fileSize = m_file.open(buildFileName()); + + if (0 == m_fileSize) + { + size_t bytesWritten = m_file.write(Converter::header(Formatter::header())); + + if (static_cast(-1) != bytesWritten) + { + m_fileSize += bytesWritten; + } + } + } + + util::nstring buildFileName(int fileNumber = 0) + { + util::nostringstream ss; + ss << m_fileNameNoExt; + + if (fileNumber > 0) + { + ss << '.' << fileNumber; + } + + if (!m_fileExt.empty()) + { + ss << '.' << m_fileExt; + } + + return ss.str(); + } + + private: + util::Mutex m_mutex; + util::File m_file; + size_t m_fileSize; + size_t m_maxFileSize; + int m_maxFiles; + util::nstring m_fileExt; + util::nstring m_fileNameNoExt; + bool m_firstWrite; + }; +} diff --git a/include/plog/Converters/NativeEOLConverter.h b/include/plog/Converters/NativeEOLConverter.h new file mode 100644 index 000000000..a395e4e89 --- /dev/null +++ b/include/plog/Converters/NativeEOLConverter.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include + +namespace plog +{ + template + class NativeEOLConverter : public NextConverter + { +#ifdef _WIN32 + public: + static std::string header(const util::nstring& str) + { + return NextConverter::header(fixLineEndings(str)); + } + + static std::string convert(const util::nstring& str) + { + return NextConverter::convert(fixLineEndings(str)); + } + + private: + static util::nstring fixLineEndings(const util::nstring& str) + { + util::nstring output; + output.reserve(str.length() * 2); // the worst case requires 2x chars + + for (size_t i = 0; i < str.size(); ++i) + { + util::nchar ch = str[i]; + + if (ch == PLOG_NSTR('\n')) + { + output.push_back(PLOG_NSTR('\r')); + } + + output.push_back(ch); + } + + return output; + } +#endif + }; +} diff --git a/include/plog/Converters/UTF8Converter.h b/include/plog/Converters/UTF8Converter.h new file mode 100644 index 000000000..9de5a6303 --- /dev/null +++ b/include/plog/Converters/UTF8Converter.h @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace plog +{ + class UTF8Converter + { + public: + static std::string header(const util::nstring& str) + { + const char kBOM[] = "\xEF\xBB\xBF"; + + return std::string(kBOM) + convert(str); + } + +#if PLOG_CHAR_IS_UTF8 + static const std::string& convert(const util::nstring& str) + { + return str; + } +#else + static std::string convert(const util::nstring& str) + { + return util::toNarrow(str, codePage::kUTF8); + } +#endif + }; +} diff --git a/include/plog/Formatters/CsvFormatter.h b/include/plog/Formatters/CsvFormatter.h new file mode 100644 index 000000000..282c57f19 --- /dev/null +++ b/include/plog/Formatters/CsvFormatter.h @@ -0,0 +1,57 @@ +#pragma once +#include +#include +#include + +namespace plog +{ + template + class CsvFormatterImpl + { + public: + static util::nstring header() + { + return PLOG_NSTR("Date;Time;Severity;TID;This;Function;Message\n"); + } + + static util::nstring format(const Record& record) + { + tm t; + useUtcTime ? util::gmtime_s(&t, &record.getTime().time) : util::localtime_s(&t, &record.getTime().time); + + util::nostringstream ss; + ss << t.tm_year + 1900 << PLOG_NSTR("/") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_mon + 1 << PLOG_NSTR("/") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_mday << PLOG_NSTR(";"); + ss << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_hour << PLOG_NSTR(":") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_min << PLOG_NSTR(":") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_sec << PLOG_NSTR(".") << std::setfill(PLOG_NSTR('0')) << std::setw(3) << static_cast (record.getTime().millitm) << PLOG_NSTR(";"); + ss << severityToString(record.getSeverity()) << PLOG_NSTR(";"); + ss << record.getTid() << PLOG_NSTR(";"); + ss << record.getObject() << PLOG_NSTR(";"); + ss << record.getFunc() << PLOG_NSTR("@") << record.getLine() << PLOG_NSTR(";"); + + util::nstring message = record.getMessage(); + + if (message.size() > kMaxMessageSize) + { + message.resize(kMaxMessageSize); + message.append(PLOG_NSTR("...")); + } + + util::nistringstream split(message); + util::nstring token; + + while (!split.eof()) + { + std::getline(split, token, PLOG_NSTR('"')); + ss << PLOG_NSTR("\"") << token << PLOG_NSTR("\""); + } + + ss << PLOG_NSTR("\n"); + + return ss.str(); + } + + static const size_t kMaxMessageSize = 32000; + }; + + class CsvFormatter : public CsvFormatterImpl {}; + class CsvFormatterUtcTime : public CsvFormatterImpl {}; +} diff --git a/include/plog/Formatters/FuncMessageFormatter.h b/include/plog/Formatters/FuncMessageFormatter.h new file mode 100644 index 000000000..aa57806b8 --- /dev/null +++ b/include/plog/Formatters/FuncMessageFormatter.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include + +namespace plog +{ + class FuncMessageFormatter + { + public: + static util::nstring header() + { + return util::nstring(); + } + + static util::nstring format(const Record& record) + { + util::nostringstream ss; + ss << record.getFunc() << PLOG_NSTR("@") << record.getLine() << PLOG_NSTR(": ") << record.getMessage() << PLOG_NSTR("\n"); + + return ss.str(); + } + }; +} diff --git a/include/plog/Formatters/MessageOnlyFormatter.h b/include/plog/Formatters/MessageOnlyFormatter.h new file mode 100644 index 000000000..b2db5a5a0 --- /dev/null +++ b/include/plog/Formatters/MessageOnlyFormatter.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include + +namespace plog +{ + class MessageOnlyFormatter + { + public: + static util::nstring header() + { + return util::nstring(); + } + + static util::nstring format(const Record& record) + { + util::nostringstream ss; + ss << record.getMessage() << PLOG_NSTR("\n"); + + return ss.str(); + } + }; +} diff --git a/include/plog/Formatters/TxtFormatter.h b/include/plog/Formatters/TxtFormatter.h new file mode 100644 index 000000000..48e2d50b8 --- /dev/null +++ b/include/plog/Formatters/TxtFormatter.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include + +namespace plog +{ + template + class TxtFormatterImpl + { + public: + static util::nstring header() + { + return util::nstring(); + } + + static util::nstring format(const Record& record) + { + tm t; + useUtcTime ? util::gmtime_s(&t, &record.getTime().time) : util::localtime_s(&t, &record.getTime().time); + + util::nostringstream ss; + ss << t.tm_year + 1900 << "-" << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_mon + 1 << PLOG_NSTR("-") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_mday << PLOG_NSTR(" "); + ss << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_hour << PLOG_NSTR(":") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_min << PLOG_NSTR(":") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_sec << PLOG_NSTR(".") << std::setfill(PLOG_NSTR('0')) << std::setw(3) << static_cast (record.getTime().millitm) << PLOG_NSTR(" "); + ss << std::setfill(PLOG_NSTR(' ')) << std::setw(5) << std::left << severityToString(record.getSeverity()) << PLOG_NSTR(" "); + ss << PLOG_NSTR("[") << record.getTid() << PLOG_NSTR("] "); + ss << PLOG_NSTR("[") << record.getFunc() << PLOG_NSTR("@") << record.getLine() << PLOG_NSTR("] "); + ss << record.getMessage() << PLOG_NSTR("\n"); + + return ss.str(); + } + }; + + class TxtFormatter : public TxtFormatterImpl {}; + class TxtFormatterUtcTime : public TxtFormatterImpl {}; +} diff --git a/include/plog/Helpers/AscDump.h b/include/plog/Helpers/AscDump.h new file mode 100644 index 000000000..18b9a6f4f --- /dev/null +++ b/include/plog/Helpers/AscDump.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include + +namespace plog +{ + class AscDump + { + public: + AscDump(const void* ptr, size_t size) + : m_ptr(static_cast(ptr)) + , m_size(size) + { + } + + friend util::nostringstream& operator<<(util::nostringstream& stream, const AscDump& ascDump); + + private: + const char* m_ptr; + size_t m_size; + }; + + inline util::nostringstream& operator<<(util::nostringstream& stream, const AscDump& ascDump) + { + for (size_t i = 0; i < ascDump.m_size; ++i) + { + stream << (std::isprint(ascDump.m_ptr[i]) ? ascDump.m_ptr[i] : '.'); + } + + return stream; + } + + inline AscDump ascdump(const void* ptr, size_t size) { return AscDump(ptr, size); } + + template + inline AscDump ascdump(const Container& container) { return AscDump(container.data(), container.size() * sizeof(*container.data())); } + + template + inline AscDump ascdump(const T (&arr)[N]) { return AscDump(arr, N * sizeof(*arr)); } +} diff --git a/include/plog/Helpers/HexDump.h b/include/plog/Helpers/HexDump.h new file mode 100644 index 000000000..b0493d707 --- /dev/null +++ b/include/plog/Helpers/HexDump.h @@ -0,0 +1,79 @@ +#pragma once +#include +#include + +namespace plog +{ + class HexDump + { + public: + HexDump(const void* ptr, size_t size) + : m_ptr(static_cast(ptr)) + , m_size(size) + , m_group(8) + , m_digitSeparator(" ") + , m_groupSeparator(" ") + { + } + + HexDump& group(size_t group) + { + m_group = group; + return *this; + } + + HexDump& separator(const char* digitSeparator) + { + m_digitSeparator = digitSeparator; + return *this; + } + + HexDump& separator(const char* digitSeparator, const char* groupSeparator) + { + m_digitSeparator = digitSeparator; + m_groupSeparator = groupSeparator; + return *this; + } + + friend util::nostringstream& operator<<(util::nostringstream& stream, const HexDump&); + + private: + const unsigned char* m_ptr; + size_t m_size; + size_t m_group; + const char* m_digitSeparator; + const char* m_groupSeparator; + }; + + inline util::nostringstream& operator<<(util::nostringstream& stream, const HexDump& hexDump) + { + stream << std::hex << std::setfill(PLOG_NSTR('0')); + + for (size_t i = 0; i < hexDump.m_size;) + { + stream << std::setw(2) << static_cast(hexDump.m_ptr[i]); + + if (++i < hexDump.m_size) + { + if (hexDump.m_group > 0 && i % hexDump.m_group == 0) + { + stream << hexDump.m_groupSeparator; + } + else + { + stream << hexDump.m_digitSeparator; + } + } + } + + return stream; + } + + inline HexDump hexdump(const void* ptr, size_t size) { return HexDump(ptr, size); } + + template + inline HexDump hexdump(const Container& container) { return HexDump(container.data(), container.size() * sizeof(*container.data())); } + + template + inline HexDump hexdump(const T (&arr)[N]) { return HexDump(arr, N * sizeof(*arr)); } +} diff --git a/include/plog/Helpers/PrintVar.h b/include/plog/Helpers/PrintVar.h new file mode 100644 index 000000000..465e1938f --- /dev/null +++ b/include/plog/Helpers/PrintVar.h @@ -0,0 +1,24 @@ +#pragma once + +#define PLOG_IMPL_PRINT_VAR_1(a1) #a1 ": " << a1 +#define PLOG_IMPL_PRINT_VAR_2(a1, a2) PLOG_IMPL_PRINT_VAR_1(a1) PLOG_IMPL_PRINT_VAR_TAIL(a2) +#define PLOG_IMPL_PRINT_VAR_3(a1, a2, a3) PLOG_IMPL_PRINT_VAR_2(a1, a2) PLOG_IMPL_PRINT_VAR_TAIL(a3) +#define PLOG_IMPL_PRINT_VAR_4(a1, a2, a3, a4) PLOG_IMPL_PRINT_VAR_3(a1, a2, a3) PLOG_IMPL_PRINT_VAR_TAIL(a4) +#define PLOG_IMPL_PRINT_VAR_5(a1, a2, a3, a4, a5) PLOG_IMPL_PRINT_VAR_4(a1, a2, a3, a4) PLOG_IMPL_PRINT_VAR_TAIL(a5) +#define PLOG_IMPL_PRINT_VAR_6(a1, a2, a3, a4, a5, a6) PLOG_IMPL_PRINT_VAR_5(a1, a2, a3, a4, a5) PLOG_IMPL_PRINT_VAR_TAIL(a6) +#define PLOG_IMPL_PRINT_VAR_7(a1, a2, a3, a4, a5, a6, a7) PLOG_IMPL_PRINT_VAR_6(a1, a2, a3, a4, a5, a6) PLOG_IMPL_PRINT_VAR_TAIL(a7) +#define PLOG_IMPL_PRINT_VAR_8(a1, a2, a3, a4, a5, a6, a7, a8) PLOG_IMPL_PRINT_VAR_7(a1, a2, a3, a4, a5, a6, a7) PLOG_IMPL_PRINT_VAR_TAIL(a8) +#define PLOG_IMPL_PRINT_VAR_9(a1, a2, a3, a4, a5, a6, a7, a8, a9) PLOG_IMPL_PRINT_VAR_8(a1, a2, a3, a4, a5, a6, a7, a8) PLOG_IMPL_PRINT_VAR_TAIL(a9) +#define PLOG_IMPL_PRINT_VAR_TAIL(a) << ", " PLOG_IMPL_PRINT_VAR_1(a) + +#define PLOG_IMPL_PRINT_VAR_EXPAND(x) x + +#ifdef __GNUC__ +#pragma GCC system_header // disable warning: variadic macros are a C99 feature +#endif + +#define PLOG_IMPL_PRINT_VAR_GET_MACRO(x1, x2, x3, x4, x5, x6, x7, x8, x9, NAME, ...) NAME + +#define PLOG_PRINT_VAR(...) PLOG_IMPL_PRINT_VAR_EXPAND(PLOG_IMPL_PRINT_VAR_GET_MACRO(__VA_ARGS__,\ + PLOG_IMPL_PRINT_VAR_9, PLOG_IMPL_PRINT_VAR_8, PLOG_IMPL_PRINT_VAR_7, PLOG_IMPL_PRINT_VAR_6, PLOG_IMPL_PRINT_VAR_5,\ + PLOG_IMPL_PRINT_VAR_4, PLOG_IMPL_PRINT_VAR_3, PLOG_IMPL_PRINT_VAR_2, PLOG_IMPL_PRINT_VAR_1, UNUSED)(__VA_ARGS__)) diff --git a/include/plog/Init.h b/include/plog/Init.h new file mode 100644 index 000000000..615c41d6c --- /dev/null +++ b/include/plog/Init.h @@ -0,0 +1,17 @@ +#pragma once +#include + +namespace plog +{ + template + PLOG_LINKAGE_HIDDEN inline Logger& init(Severity maxSeverity = none, IAppender* appender = NULL) + { + static Logger logger(maxSeverity); + return appender ? logger.addAppender(appender) : logger; + } + + inline Logger& init(Severity maxSeverity = none, IAppender* appender = NULL) + { + return init(maxSeverity, appender); + } +} diff --git a/include/plog/Initializers/ConsoleInitializer.h b/include/plog/Initializers/ConsoleInitializer.h new file mode 100644 index 000000000..5b504abd5 --- /dev/null +++ b/include/plog/Initializers/ConsoleInitializer.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +namespace plog +{ + ////////////////////////////////////////////////////////////////////////// + // ColorConsoleAppender with any Formatter + + template + PLOG_LINKAGE_HIDDEN inline Logger& init(Severity maxSeverity, OutputStream outputStream) + { + static ColorConsoleAppender appender(outputStream); + return init(maxSeverity, &appender); + } + + template + inline Logger& init(Severity maxSeverity, OutputStream outputStream) + { + return init(maxSeverity, outputStream); + } +} diff --git a/include/plog/Initializers/RollingFileInitializer.h b/include/plog/Initializers/RollingFileInitializer.h new file mode 100644 index 000000000..dc0adda14 --- /dev/null +++ b/include/plog/Initializers/RollingFileInitializer.h @@ -0,0 +1,80 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace plog +{ + ////////////////////////////////////////////////////////////////////////// + // RollingFileAppender with any Formatter + + template + PLOG_LINKAGE_HIDDEN inline Logger& init(Severity maxSeverity, const util::nchar* fileName, size_t maxFileSize = 0, int maxFiles = 0) + { + static RollingFileAppender rollingFileAppender(fileName, maxFileSize, maxFiles); + return init(maxSeverity, &rollingFileAppender); + } + + template + inline Logger& init(Severity maxSeverity, const util::nchar* fileName, size_t maxFileSize = 0, int maxFiles = 0) + { + return init(maxSeverity, fileName, maxFileSize, maxFiles); + } + + ////////////////////////////////////////////////////////////////////////// + // RollingFileAppender with TXT/CSV chosen by file extension + + namespace + { + inline bool isCsv(const util::nchar* fileName) + { + const util::nchar* dot = util::findExtensionDot(fileName); +#if PLOG_CHAR_IS_UTF8 + return dot && 0 == std::strcmp(dot, ".csv"); +#else + return dot && 0 == std::wcscmp(dot, L".csv"); +#endif + } + } + + template + inline Logger& init(Severity maxSeverity, const util::nchar* fileName, size_t maxFileSize = 0, int maxFiles = 0) + { + return isCsv(fileName) ? init(maxSeverity, fileName, maxFileSize, maxFiles) : init(maxSeverity, fileName, maxFileSize, maxFiles); + } + + inline Logger& init(Severity maxSeverity, const util::nchar* fileName, size_t maxFileSize = 0, int maxFiles = 0) + { + return init(maxSeverity, fileName, maxFileSize, maxFiles); + } + + ////////////////////////////////////////////////////////////////////////// + // CHAR variants for Windows + +#if defined(_WIN32) && !PLOG_CHAR_IS_UTF8 + template + inline Logger& init(Severity maxSeverity, const char* fileName, size_t maxFileSize = 0, int maxFiles = 0) + { + return init(maxSeverity, util::toWide(fileName).c_str(), maxFileSize, maxFiles); + } + + template + inline Logger& init(Severity maxSeverity, const char* fileName, size_t maxFileSize = 0, int maxFiles = 0) + { + return init(maxSeverity, fileName, maxFileSize, maxFiles); + } + + template + inline Logger& init(Severity maxSeverity, const char* fileName, size_t maxFileSize = 0, int maxFiles = 0) + { + return init(maxSeverity, util::toWide(fileName).c_str(), maxFileSize, maxFiles); + } + + inline Logger& init(Severity maxSeverity, const char* fileName, size_t maxFileSize = 0, int maxFiles = 0) + { + return init(maxSeverity, fileName, maxFileSize, maxFiles); + } +#endif +} diff --git a/include/plog/LICENSE b/include/plog/LICENSE new file mode 100644 index 000000000..8a91a0aee --- /dev/null +++ b/include/plog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Sergey Podobry + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/include/plog/Log.h b/include/plog/Log.h new file mode 100644 index 000000000..127cb9d6b --- /dev/null +++ b/include/plog/Log.h @@ -0,0 +1,211 @@ +////////////////////////////////////////////////////////////////////////// +// Plog - portable and simple log for C++ +// Documentation and sources: https://github.com/SergiusTheBest/plog +// License: MIT, https://choosealicense.com/licenses/mit + +#pragma once +#include + +////////////////////////////////////////////////////////////////////////// +// Helper macros that get context info + +#if defined(PLOG_ENABLE_GET_THIS) && defined(_MSC_VER) && _MSC_VER >= 1600 && !defined(__INTELLISENSE__) && !defined(__INTEL_COMPILER) && !defined(__llvm__) && !defined(__RESHARPER__) // >= Visual Studio 2010, skip IntelliSense, Intel Compiler, Clang Code Model and ReSharper +# define PLOG_GET_THIS() __if_exists(this) { this } __if_not_exists(this) { 0 } +#else +# define PLOG_GET_THIS() reinterpret_cast(0) +#endif + +#ifdef PLOG_NO_CAPTURE_FUNCTION_NAME +# define PLOG_GET_FUNC() "" +#elif defined(_MSC_VER) +# define PLOG_GET_FUNC() __FUNCTION__ +#elif defined(__BORLANDC__) +# define PLOG_GET_FUNC() __FUNC__ +#else +# define PLOG_GET_FUNC() __PRETTY_FUNCTION__ +#endif + +#ifdef PLOG_CAPTURE_FILE +# define PLOG_GET_FILE() __FILE__ +#else +# define PLOG_GET_FILE() "" +#endif + +////////////////////////////////////////////////////////////////////////// +// Log severity level checker + +#ifdef PLOG_DISABLE_LOGGING +# ifdef _MSC_VER +# define IF_PLOG_(instanceId, severity) __pragma(warning(push)) __pragma(warning(disable:4127)) if (true) {;} else __pragma(warning(pop)) // conditional expression is constant +# else +# define IF_PLOG_(instanceId, severity) if (true) {;} else +# endif +#else +# define IF_PLOG_(instanceId, severity) if (!plog::get() || !plog::get()->checkSeverity(severity)) {;} else +#endif + +#define IF_PLOG(severity) IF_PLOG_(PLOG_DEFAULT_INSTANCE_ID, severity) + +////////////////////////////////////////////////////////////////////////// +// Main logging macros + +#ifdef PLOG_MESSAGE_PREFIX +# define PLOG_PRINT_MESSAGE_PREFIX() << PLOG_MESSAGE_PREFIX +#else +# define PLOG_PRINT_MESSAGE_PREFIX() +#endif + +#define PLOG_(instanceId, severity) IF_PLOG_(instanceId, severity) (*plog::get()) += \ + plog::Record(severity, PLOG_GET_FUNC(), __LINE__, PLOG_GET_FILE(), PLOG_GET_THIS(), instanceId).ref() PLOG_PRINT_MESSAGE_PREFIX() +#define PLOG(severity) PLOG_(PLOG_DEFAULT_INSTANCE_ID, severity) + +#define PLOG_VERBOSE PLOG(plog::verbose) +#define PLOG_DEBUG PLOG(plog::debug) +#define PLOG_INFO PLOG(plog::info) +#define PLOG_WARNING PLOG(plog::warning) +#define PLOG_ERROR PLOG(plog::error) +#define PLOG_FATAL PLOG(plog::fatal) +#define PLOG_NONE PLOG(plog::none) + +#define PLOG_VERBOSE_(instanceId) PLOG_(instanceId, plog::verbose) +#define PLOG_DEBUG_(instanceId) PLOG_(instanceId, plog::debug) +#define PLOG_INFO_(instanceId) PLOG_(instanceId, plog::info) +#define PLOG_WARNING_(instanceId) PLOG_(instanceId, plog::warning) +#define PLOG_ERROR_(instanceId) PLOG_(instanceId, plog::error) +#define PLOG_FATAL_(instanceId) PLOG_(instanceId, plog::fatal) +#define PLOG_NONE_(instanceId) PLOG_(instanceId, plog::none) + +#define PLOGV PLOG_VERBOSE +#define PLOGD PLOG_DEBUG +#define PLOGI PLOG_INFO +#define PLOGW PLOG_WARNING +#define PLOGE PLOG_ERROR +#define PLOGF PLOG_FATAL +#define PLOGN PLOG_NONE + +#define PLOGV_(instanceId) PLOG_VERBOSE_(instanceId) +#define PLOGD_(instanceId) PLOG_DEBUG_(instanceId) +#define PLOGI_(instanceId) PLOG_INFO_(instanceId) +#define PLOGW_(instanceId) PLOG_WARNING_(instanceId) +#define PLOGE_(instanceId) PLOG_ERROR_(instanceId) +#define PLOGF_(instanceId) PLOG_FATAL_(instanceId) +#define PLOGN_(instanceId) PLOG_NONE_(instanceId) + +////////////////////////////////////////////////////////////////////////// +// Conditional logging macros + +#define PLOG_IF_(instanceId, severity, condition) if (!(condition)) {;} else PLOG_(instanceId, severity) +#define PLOG_IF(severity, condition) PLOG_IF_(PLOG_DEFAULT_INSTANCE_ID, severity, condition) + +#define PLOG_VERBOSE_IF(condition) PLOG_IF(plog::verbose, condition) +#define PLOG_DEBUG_IF(condition) PLOG_IF(plog::debug, condition) +#define PLOG_INFO_IF(condition) PLOG_IF(plog::info, condition) +#define PLOG_WARNING_IF(condition) PLOG_IF(plog::warning, condition) +#define PLOG_ERROR_IF(condition) PLOG_IF(plog::error, condition) +#define PLOG_FATAL_IF(condition) PLOG_IF(plog::fatal, condition) +#define PLOG_NONE_IF(condition) PLOG_IF(plog::none, condition) + +#define PLOG_VERBOSE_IF_(instanceId, condition) PLOG_IF_(instanceId, plog::verbose, condition) +#define PLOG_DEBUG_IF_(instanceId, condition) PLOG_IF_(instanceId, plog::debug, condition) +#define PLOG_INFO_IF_(instanceId, condition) PLOG_IF_(instanceId, plog::info, condition) +#define PLOG_WARNING_IF_(instanceId, condition) PLOG_IF_(instanceId, plog::warning, condition) +#define PLOG_ERROR_IF_(instanceId, condition) PLOG_IF_(instanceId, plog::error, condition) +#define PLOG_FATAL_IF_(instanceId, condition) PLOG_IF_(instanceId, plog::fatal, condition) +#define PLOG_NONE_IF_(instanceId, condition) PLOG_IF_(instanceId, plog::none, condition) + +#define PLOGV_IF(condition) PLOG_VERBOSE_IF(condition) +#define PLOGD_IF(condition) PLOG_DEBUG_IF(condition) +#define PLOGI_IF(condition) PLOG_INFO_IF(condition) +#define PLOGW_IF(condition) PLOG_WARNING_IF(condition) +#define PLOGE_IF(condition) PLOG_ERROR_IF(condition) +#define PLOGF_IF(condition) PLOG_FATAL_IF(condition) +#define PLOGN_IF(condition) PLOG_NONE_IF(condition) + +#define PLOGV_IF_(instanceId, condition) PLOG_VERBOSE_IF_(instanceId, condition) +#define PLOGD_IF_(instanceId, condition) PLOG_DEBUG_IF_(instanceId, condition) +#define PLOGI_IF_(instanceId, condition) PLOG_INFO_IF_(instanceId, condition) +#define PLOGW_IF_(instanceId, condition) PLOG_WARNING_IF_(instanceId, condition) +#define PLOGE_IF_(instanceId, condition) PLOG_ERROR_IF_(instanceId, condition) +#define PLOGF_IF_(instanceId, condition) PLOG_FATAL_IF_(instanceId, condition) +#define PLOGN_IF_(instanceId, condition) PLOG_NONE_IF_(instanceId, condition) + +// Old macro names for downward compatibility. To bypass including these macro names, add +// #define PLOG_OMIT_LOG_DEFINES before #include +#ifndef PLOG_OMIT_LOG_DEFINES + +////////////////////////////////////////////////////////////////////////// +// Main logging macros - can be changed later to point at macros for a different logging package + +#define LOG_(instanceId, severity) PLOG_(instanceId, severity) +#define LOG(severity) PLOG(severity) + +#define LOG_VERBOSE PLOG_VERBOSE +#define LOG_DEBUG PLOG_DEBUG +#define LOG_INFO PLOG_INFO +#define LOG_WARNING PLOG_WARNING +#define LOG_ERROR PLOG_ERROR +#define LOG_FATAL PLOG_FATAL +#define LOG_NONE PLOG_NONE + +#define LOG_VERBOSE_(instanceId) PLOG_VERBOSE_(instanceId) +#define LOG_DEBUG_(instanceId) PLOG_DEBUG_(instanceId) +#define LOG_INFO_(instanceId) PLOG_INFO_(instanceId) +#define LOG_WARNING_(instanceId) PLOG_WARNING_(instanceId) +#define LOG_ERROR_(instanceId) PLOG_ERROR_(instanceId) +#define LOG_FATAL_(instanceId) PLOG_FATAL_(instanceId) +#define LOG_NONE_(instanceId) PLOG_NONE_(instanceId) + +#define LOGV PLOGV +#define LOGD PLOGD +#define LOGI PLOGI +#define LOGW PLOGW +#define LOGE PLOGE +#define LOGF PLOGF +#define LOGN PLOGN + +#define LOGV_(instanceId) PLOGV_(instanceId) +#define LOGD_(instanceId) PLOGD_(instanceId) +#define LOGI_(instanceId) PLOGI_(instanceId) +#define LOGW_(instanceId) PLOGW_(instanceId) +#define LOGE_(instanceId) PLOGE_(instanceId) +#define LOGF_(instanceId) PLOGF_(instanceId) +#define LOGN_(instanceId) PLOGN_(instanceId) + +////////////////////////////////////////////////////////////////////////// +// Conditional logging macros + +#define LOG_IF_(instanceId, severity, condition) PLOG_IF_(instanceId, severity, condition) +#define LOG_IF(severity, condition) PLOG_IF(severity, condition) + +#define LOG_VERBOSE_IF(condition) PLOG_VERBOSE_IF(condition) +#define LOG_DEBUG_IF(condition) PLOG_DEBUG_IF(condition) +#define LOG_INFO_IF(condition) PLOG_INFO_IF(condition) +#define LOG_WARNING_IF(condition) PLOG_WARNING_IF(condition) +#define LOG_ERROR_IF(condition) PLOG_ERROR_IF(condition) +#define LOG_FATAL_IF(condition) PLOG_FATAL_IF(condition) +#define LOG_NONE_IF(condition) PLOG_NONE_IF(condition) + +#define LOG_VERBOSE_IF_(instanceId, condition) PLOG_VERBOSE_IF_(instanceId, condition) +#define LOG_DEBUG_IF_(instanceId, condition) PLOG_DEBUG_IF_(instanceId, condition) +#define LOG_INFO_IF_(instanceId, condition) PLOG_INFO_IF_(instanceId, condition) +#define LOG_WARNING_IF_(instanceId, condition) PLOG_WARNING_IF_(instanceId, condition) +#define LOG_ERROR_IF_(instanceId, condition) PLOG_ERROR_IF_(instanceId, condition) +#define LOG_FATAL_IF_(instanceId, condition) PLOG_FATAL_IF_(instanceId, condition) +#define LOG_NONE_IF_(instanceId, condition) PLOG_NONE_IF_(instanceId, condition) + +#define LOGV_IF(condition) PLOGV_IF(condition) +#define LOGD_IF(condition) PLOGD_IF(condition) +#define LOGI_IF(condition) PLOGI_IF(condition) +#define LOGW_IF(condition) PLOGW_IF(condition) +#define LOGE_IF(condition) PLOGE_IF(condition) +#define LOGF_IF(condition) PLOGF_IF(condition) +#define LOGN_IF(condition) PLOGN_IF(condition) + +#define LOGV_IF_(instanceId, condition) PLOGV_IF_(instanceId, condition) +#define LOGD_IF_(instanceId, condition) PLOGD_IF_(instanceId, condition) +#define LOGI_IF_(instanceId, condition) PLOGI_IF_(instanceId, condition) +#define LOGW_IF_(instanceId, condition) PLOGW_IF_(instanceId, condition) +#define LOGE_IF_(instanceId, condition) PLOGE_IF_(instanceId, condition) +#define LOGF_IF_(instanceId, condition) PLOGF_IF_(instanceId, condition) +#define LOGN_IF_(instanceId, condition) PLOGN_IF_(instanceId, condition) +#endif diff --git a/include/plog/Logger.h b/include/plog/Logger.h new file mode 100644 index 000000000..0e116e4c6 --- /dev/null +++ b/include/plog/Logger.h @@ -0,0 +1,84 @@ +#pragma once +#include +#include +#include + +#ifdef PLOG_DEFAULT_INSTANCE // for backward compatibility +# define PLOG_DEFAULT_INSTANCE_ID PLOG_DEFAULT_INSTANCE +#endif + +#ifndef PLOG_DEFAULT_INSTANCE_ID +# define PLOG_DEFAULT_INSTANCE_ID 0 +#endif + +namespace plog +{ + template + class PLOG_LINKAGE Logger : public util::Singleton >, public IAppender + { + public: + Logger(Severity maxSeverity = none) : m_maxSeverity(maxSeverity) + { + } + + Logger& addAppender(IAppender* appender) + { + assert(appender != this); + m_appenders.push_back(appender); + return *this; + } + + Severity getMaxSeverity() const + { + return m_maxSeverity; + } + + void setMaxSeverity(Severity severity) + { + m_maxSeverity = severity; + } + + bool checkSeverity(Severity severity) const + { + return severity <= m_maxSeverity; + } + + virtual void write(const Record& record) PLOG_OVERRIDE + { + if (checkSeverity(record.getSeverity())) + { + *this += record; + } + } + + void operator+=(const Record& record) + { + for (std::vector::iterator it = m_appenders.begin(); it != m_appenders.end(); ++it) + { + (*it)->write(record); + } + } + + private: + Severity m_maxSeverity; +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4251) // needs to have dll-interface to be used by clients of class +#endif + std::vector m_appenders; +#ifdef _MSC_VER +# pragma warning(pop) +#endif + }; + + template + inline Logger* get() + { + return Logger::getInstance(); + } + + inline Logger* get() + { + return Logger::getInstance(); + } +} diff --git a/include/plog/Record.h b/include/plog/Record.h new file mode 100644 index 000000000..c919e3aa6 --- /dev/null +++ b/include/plog/Record.h @@ -0,0 +1,465 @@ +#pragma once +#include +#include +#include + +#ifdef __cplusplus_cli +#include // For PtrToStringChars +#endif + +namespace plog +{ + namespace detail + { +#if !defined(_MSC_VER) || _MSC_VER > 1400 // MSVC 2005 doesn't understand `enableIf`, so drop all `meta` + namespace meta + { + template + inline T& declval() + { +#ifdef __INTEL_COMPILER +# pragma warning(suppress: 327) // NULL reference is not allowed +#endif + return *reinterpret_cast(0); + } + + template + struct enableIf {}; + + template + struct enableIf { typedef T type; }; + + struct No { char a[1]; }; + struct Yes { char a[2]; }; + + template + struct isConvertible + { + // `+ sizeof(U*)` is required for GCC 4.5-4.7 + template + static typename enableIf(meta::declval())) + sizeof(U*)), Yes>::type test(int); + + template + static No test(...); + + enum { value = sizeof(test(0)) == sizeof(Yes) }; + }; + + template + struct isConvertibleToString : isConvertible {}; + +#if PLOG_ENABLE_WCHAR_INPUT + template + struct isConvertibleToWString : isConvertible {}; +#endif + + template + struct isContainer + { + template + static typename meta::enableIf().begin()) + sizeof(meta::declval().end() +#else + typename U::const_iterator +#endif + )), Yes>::type test(int); + + template + static No test(...); + + enum { value = sizeof(test(0)) == sizeof(Yes) }; + }; + + // Detects `std::filesystem::path` and `boost::filesystem::path`. They look like containers + // but we don't want to treat them as containers, so we use this detector to filter them out. + template + struct isFilesystemPath + { + template + static typename meta::enableIf().preferred_separator)), Yes>::type test(int); + + template + static No test(...); + + enum { value = sizeof(test(0)) == sizeof(Yes) }; + }; + } +#endif + + ////////////////////////////////////////////////////////////////////////// + // Stream output operators as free functions + +#if PLOG_ENABLE_WCHAR_INPUT + inline void operator<<(util::nostringstream& stream, const wchar_t* data) + { + data = data ? data : L"(null)"; + +# ifdef _WIN32 +# if PLOG_CHAR_IS_UTF8 + std::operator<<(stream, util::toNarrow(data, codePage::kUTF8)); +# else + std::operator<<(stream, data); +# endif +# else + std::operator<<(stream, util::toNarrow(data)); +# endif + } + + inline void operator<<(util::nostringstream& stream, wchar_t* data) + { + plog::detail::operator<<(stream, const_cast(data)); + } + + inline void operator<<(util::nostringstream& stream, const std::wstring& data) + { + plog::detail::operator<<(stream, data.c_str()); + } +#endif + + inline void operator<<(util::nostringstream& stream, const char* data) + { + data = data ? data : "(null)"; + +#if defined(_WIN32) && defined(__BORLANDC__) +# if PLOG_CHAR_IS_UTF8 + stream << data; +# else + stream << util::toWide(data); +# endif +#elif defined(_WIN32) +# if PLOG_CHAR_IS_UTF8 + std::operator<<(stream, data); +# else + std::operator<<(stream, util::toWide(data)); +# endif +#else + std::operator<<(stream, data); +#endif + } + + inline void operator<<(util::nostringstream& stream, char* data) + { + plog::detail::operator<<(stream, const_cast(data)); + } + + inline void operator<<(util::nostringstream& stream, const std::string& data) + { + plog::detail::operator<<(stream, data.c_str()); + } + +#ifdef __cpp_char8_t + inline void operator<<(util::nostringstream& stream, const char8_t* data) + { +# if PLOG_CHAR_IS_UTF8 + plog::detail::operator<<(stream, reinterpret_cast(data)); +# else + plog::detail::operator<<(stream, util::toWide(reinterpret_cast(data), codePage::kUTF8)); +# endif + } +#endif //__cpp_char8_t + + // Print `std::pair` + template + inline void operator<<(util::nostringstream& stream, const std::pair& data) + { + stream << data.first; + stream << ":"; + stream << data.second; + } + +#if defined(__clang__) || !defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 // skip for GCC < 4.5 due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38600 +#if !defined(_MSC_VER) || _MSC_VER > 1400 // MSVC 2005 doesn't understand `enableIf`, so drop all `meta` + // Print data that can be casted to `std::string` + template + inline typename meta::enableIf::value, void>::type operator<<(util::nostringstream& stream, const T& data) + { + plog::detail::operator<<(stream, static_cast(data)); + } + +#if PLOG_ENABLE_WCHAR_INPUT + // Print data that can be casted to `std::wstring` + template + inline typename meta::enableIf::value, void>::type operator<<(util::nostringstream& stream, const T& data) + { + plog::detail::operator<<(stream, static_cast(data)); + } +#endif + + // Print std containers + template + inline typename meta::enableIf::value && + !meta::isConvertibleToString::value && +#if PLOG_ENABLE_WCHAR_INPUT + !meta::isConvertibleToWString::value && +#endif + !meta::isFilesystemPath::value, void>::type operator<<(util::nostringstream& stream, const T& data) + { + stream << "["; + + for (typename T::const_iterator it = data.begin(); it != data.end();) + { + stream << *it; + + if (++it == data.end()) + { + break; + } + + stream << ", "; + } + + stream << "]"; + } +#endif +#endif + +#ifdef __cplusplus_cli + // Print managed C++ `System::String^` + inline void operator<<(util::nostringstream& stream, System::String^ data) + { + cli::pin_ptr ptr = PtrToStringChars(data); + plog::detail::operator<<(stream, static_cast(ptr)); + } +#endif + +#if PLOG_ENABLE_WCHAR_INPUT && (!defined(_MSC_VER) || _MSC_VER > 1400) // MSVC 2005 doesn't understand `enableIf`, so drop all `meta` + namespace meta + { + template + struct valueType { enum { value = Value }; }; + + template + inline No operator<<(Stream&, const T&); + + template + struct isStreamable : valueType(), meta::declval())) != sizeof(No)> {}; + + template + struct isStreamable : valueType {}; + + template + struct isStreamable : valueType {}; + + template + struct isStreamable : valueType {}; + + // meta doesn't work well for deleted functions and C++20 has `operator<<(std::ostream&, const wchar_t*) = delete` so explicitly define it + template<> + struct isStreamable : valueType {}; + +# ifdef __cpp_char8_t + // meta doesn't work well for deleted functions and C++20 has `operator<<(std::ostream&, const char8_t*) = delete` so explicitly define it + template + struct isStreamable : valueType {}; + + template + struct isStreamable : valueType {}; +# endif //__cpp_char8_t + } + +# if PLOG_CHAR_IS_UTF8 + // Print types that can be streamed into `std::owstringstream` but not into `std::ostringstream` when we use UTF-8 on Windows + template + inline typename meta::enableIf::value && + !meta::isStreamable::value && + !meta::isConvertibleToWString::value, void>::type operator<<(std::ostringstream& stream, const T& data) + { + std::wostringstream ss; + ss << data; + stream << ss.str(); + } +# else + // Print types that can be streamed into `std::ostringstream` but not into `std::owstringstream` when we use `wchar_t` on Windows + template + inline typename meta::enableIf::value && + !meta::isStreamable::value && + !meta::isConvertibleToString::value, void>::type operator<<(std::wostringstream& stream, const T& data) + { + std::ostringstream ss; + ss << data; + stream << ss.str(); + } +# endif +#endif + } + + class Record + { + public: + Record(Severity severity, const char* func, size_t line, const char* file, const void* object, int instanceId) + : m_severity(severity), m_tid(util::gettid()), m_object(object), m_line(line), m_func(func), m_file(file), m_instanceId(instanceId) + { + util::ftime(&m_time); + } + + Record& ref() + { + return *this; + } + + ////////////////////////////////////////////////////////////////////////// + // Stream output operators + + Record& operator<<(char data) + { + char str[] = { data, 0 }; + return *this << str; + } + +#if PLOG_ENABLE_WCHAR_INPUT + Record& operator<<(wchar_t data) + { + wchar_t str[] = { data, 0 }; + return *this << str; + } +#endif + + Record& operator<<(util::nostream& (PLOG_CDECL *data)(util::nostream&)) + { + m_message << data; + return *this; + } + +#ifdef QT_VERSION + Record& operator<<(const QString& data) + { +# if PLOG_CHAR_IS_UTF8 + return *this << data.toStdString(); +# else + return *this << data.toStdWString(); +# endif + } + +# if QT_VERSION < 0x060000 + Record& operator<<(const QStringRef& data) + { + return *this << data.toString(); + } +# endif + +# ifdef QSTRINGVIEW_H + Record& operator<<(QStringView data) + { + return *this << data.toString(); + } +# endif +#endif + + template + Record& operator<<(const T& data) + { + using namespace plog::detail; + + m_message << data; + return *this; + } + +#ifndef __cplusplus_cli + Record& printf(const char* format, ...) + { + using namespace util; + + char* str = NULL; + va_list ap; + + va_start(ap, format); + int len = vasprintf(&str, format, ap); + static_cast(len); + va_end(ap); + + *this << str; + free(str); + + return *this; + } + +#ifdef _WIN32 + Record& printf(const wchar_t* format, ...) + { + using namespace util; + + wchar_t* str = NULL; + va_list ap; + + va_start(ap, format); + int len = vaswprintf(&str, format, ap); + static_cast(len); + va_end(ap); + + *this << str; + free(str); + + return *this; + } +#endif +#endif //__cplusplus_cli + + ////////////////////////////////////////////////////////////////////////// + // Getters + + virtual const util::Time& getTime() const + { + return m_time; + } + + virtual Severity getSeverity() const + { + return m_severity; + } + + virtual unsigned int getTid() const + { + return m_tid; + } + + virtual const void* getObject() const + { + return m_object; + } + + virtual size_t getLine() const + { + return m_line; + } + + virtual const util::nchar* getMessage() const + { + m_messageStr = m_message.str(); + return m_messageStr.c_str(); + } + + virtual const char* getFunc() const + { + m_funcStr = util::processFuncName(m_func); + return m_funcStr.c_str(); + } + + virtual const char* getFile() const + { + return m_file; + } + + virtual ~Record() // virtual destructor to satisfy -Wnon-virtual-dtor warning + { + } + + virtual int getInstanceId() const + { + return m_instanceId; + } + + private: + util::Time m_time; + const Severity m_severity; + const unsigned int m_tid; + const void* const m_object; + const size_t m_line; + util::nostringstream m_message; + const char* const m_func; + const char* const m_file; + const int m_instanceId; + mutable std::string m_funcStr; + mutable util::nstring m_messageStr; + }; +} diff --git a/include/plog/Severity.h b/include/plog/Severity.h new file mode 100644 index 000000000..446768e8f --- /dev/null +++ b/include/plog/Severity.h @@ -0,0 +1,61 @@ +#pragma once +#include + +namespace plog +{ + enum Severity + { + none = 0, + fatal = 1, + error = 2, + warning = 3, + info = 4, + debug = 5, + verbose = 6 + }; + +#ifdef _MSC_VER +# pragma warning(suppress: 26812) // Prefer 'enum class' over 'enum' +#endif + inline const char* severityToString(Severity severity) + { + switch (severity) + { + case fatal: + return "FATAL"; + case error: + return "ERROR"; + case warning: + return "WARN"; + case info: + return "INFO"; + case debug: + return "DEBUG"; + case verbose: + return "VERB"; + default: + return "NONE"; + } + } + + inline Severity severityFromString(const char* str) + { + switch (std::toupper(str[0])) + { + case 'F': + return fatal; + case 'E': + return error; + case 'W': + return warning; + case 'I': + return info; + case 'D': + return debug; + case 'V': + return verbose; + default: + return none; + } + } +} diff --git a/include/plog/Util.h b/include/plog/Util.h new file mode 100644 index 000000000..ec319a10c --- /dev/null +++ b/include/plog/Util.h @@ -0,0 +1,635 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#ifndef PLOG_ENABLE_WCHAR_INPUT +# ifdef _WIN32 +# define PLOG_ENABLE_WCHAR_INPUT 1 +# else +# define PLOG_ENABLE_WCHAR_INPUT 0 +# endif +#endif + +////////////////////////////////////////////////////////////////////////// +// PLOG_CHAR_IS_UTF8 specifies character encoding of `char` type. On *nix +// systems it's set to UTF-8 while on Windows in can be ANSI or UTF-8. It +// automatically detects `/utf-8` command line option in MSVC. Also it can +// be set manually if required. +// This option allows to support http://utf8everywhere.org approach. + +#ifndef PLOG_CHAR_IS_UTF8 +# if defined(_WIN32) && !defined(_UTF8) +# define PLOG_CHAR_IS_UTF8 0 +# else +# define PLOG_CHAR_IS_UTF8 1 +# endif +#endif + +#ifdef _WIN32 +# if defined(PLOG_EXPORT) +# define PLOG_LINKAGE __declspec(dllexport) +# elif defined(PLOG_IMPORT) +# define PLOG_LINKAGE __declspec(dllimport) +# endif +# if defined(PLOG_GLOBAL) +# error "PLOG_GLOBAL isn't supported on Windows" +# endif +#else +# if defined(PLOG_GLOBAL) +# define PLOG_LINKAGE __attribute__ ((visibility ("default"))) +# elif defined(PLOG_LOCAL) +# define PLOG_LINKAGE __attribute__ ((visibility ("hidden"))) +# define PLOG_LINKAGE_HIDDEN PLOG_LINKAGE +# endif +# if defined(PLOG_EXPORT) || defined(PLOG_IMPORT) +# error "PLOG_EXPORT/PLOG_IMPORT is supported only on Windows" +# endif +#endif + +#ifndef PLOG_LINKAGE +# define PLOG_LINKAGE +#endif + +#ifndef PLOG_LINKAGE_HIDDEN +# define PLOG_LINKAGE_HIDDEN +#endif + +#ifdef _WIN32 +# include +# include +# include +# include +# include +#else +# include +# include +# if defined(__linux__) || defined(__FreeBSD__) +# include +# elif defined(__rtems__) +# include +# endif +# if defined(_POSIX_THREADS) +# include +# endif +# if PLOG_ENABLE_WCHAR_INPUT +# include +# endif +#endif + +#ifdef __FREERTOS__ // There is no standard way to know if the code is compiled for FreeRTOS. We expect __FREERTOS__ macro to be defined in such case. +# include +# include +# include +#endif + +#if PLOG_CHAR_IS_UTF8 +# define PLOG_NSTR(x) x +#else +# define _PLOG_NSTR(x) L##x +# define PLOG_NSTR(x) _PLOG_NSTR(x) +#endif + +#ifdef _WIN32 +# define PLOG_CDECL __cdecl +#else +# define PLOG_CDECL +#endif + +#if __cplusplus >= 201103L || defined(_MSC_VER) && _MSC_VER >= 1700 +# define PLOG_OVERRIDE override +#else +# define PLOG_OVERRIDE +#endif + +namespace plog +{ + namespace util + { +#if PLOG_CHAR_IS_UTF8 + typedef std::string nstring; + typedef std::ostringstream nostringstream; + typedef std::istringstream nistringstream; + typedef std::ostream nostream; + typedef char nchar; +#else + typedef std::wstring nstring; + typedef std::wostringstream nostringstream; + typedef std::wistringstream nistringstream; + typedef std::wostream nostream; + typedef wchar_t nchar; +#endif + + inline void localtime_s(struct tm* t, const time_t* time) + { +#if defined(_WIN32) && defined(__BORLANDC__) + ::localtime_s(time, t); +#elif defined(_WIN32) && defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + *t = *::localtime(time); +#elif defined(_WIN32) + ::localtime_s(t, time); +#else + ::localtime_r(time, t); +#endif + } + + inline void gmtime_s(struct tm* t, const time_t* time) + { +#if defined(_WIN32) && defined(__BORLANDC__) + ::gmtime_s(time, t); +#elif defined(_WIN32) && defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + *t = *::gmtime(time); +#elif defined(_WIN32) + ::gmtime_s(t, time); +#else + ::gmtime_r(time, t); +#endif + } + +#ifdef _WIN32 + typedef timeb Time; + + inline void ftime(Time* t) + { + ::ftime(t); + } +#else + struct Time + { + time_t time; + unsigned short millitm; + }; + + inline void ftime(Time* t) + { + timeval tv; + ::gettimeofday(&tv, NULL); + + t->time = tv.tv_sec; + t->millitm = static_cast(tv.tv_usec / 1000); + } +#endif + + inline unsigned int gettid() + { +#if defined(__FREERTOS__) && defined(INCLUDE_xTaskGetCurrentTaskHandle) + return static_cast(reinterpret_cast(xTaskGetCurrentTaskHandle())); +#elif defined(_WIN32) + return GetCurrentThreadId(); +#elif defined(__linux__) + return static_cast(::syscall(__NR_gettid)); +#elif defined(__FreeBSD__) + long tid; + syscall(SYS_thr_self, &tid); + return static_cast(tid); +#elif defined(__rtems__) + return rtems_task_self(); +#elif defined(__APPLE__) + uint64_t tid64; + pthread_threadid_np(NULL, &tid64); + return static_cast(tid64); +#else + return 0; +#endif + } + +#ifndef _GNU_SOURCE + inline int vasprintf(char** strp, const char* format, va_list ap) + { + va_list ap_copy; +#if defined(_MSC_VER) && _MSC_VER <= 1600 + ap_copy = ap; // there is no va_copy on Visual Studio 2010 +#else + va_copy(ap_copy, ap); +#endif +#ifndef __STDC_SECURE_LIB__ + int charCount = vsnprintf(NULL, 0, format, ap_copy); +#else + int charCount = _vscprintf(format, ap_copy); +#endif + va_end(ap_copy); + if (charCount < 0) + { + return -1; + } + + size_t bufferCharCount = static_cast(charCount) + 1; + + char* str = static_cast(malloc(bufferCharCount)); + if (!str) + { + return -1; + } + +#ifndef __STDC_SECURE_LIB__ + int retval = vsnprintf(str, bufferCharCount, format, ap); +#else + int retval = vsnprintf_s(str, bufferCharCount, static_cast(-1), format, ap); +#endif + if (retval < 0) + { + free(str); + return -1; + } + + *strp = str; + return retval; + } +#endif + +#ifdef _WIN32 + inline int vaswprintf(wchar_t** strp, const wchar_t* format, va_list ap) + { +#if defined(__BORLANDC__) + int charCount = 0x1000; // there is no _vscwprintf on Borland/Embarcadero +#else + int charCount = _vscwprintf(format, ap); + if (charCount < 0) + { + return -1; + } +#endif + + size_t bufferCharCount = static_cast(charCount) + 1; + + wchar_t* str = static_cast(malloc(bufferCharCount * sizeof(wchar_t))); + if (!str) + { + return -1; + } + +#if defined(__BORLANDC__) + int retval = vsnwprintf_s(str, bufferCharCount, format, ap); +#elif defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + int retval = _vsnwprintf(str, bufferCharCount, format, ap); +#else + int retval = _vsnwprintf_s(str, bufferCharCount, charCount, format, ap); +#endif + if (retval < 0) + { + free(str); + return -1; + } + + *strp = str; + return retval; + } +#endif + +#ifdef _WIN32 + inline std::wstring toWide(const char* str, UINT cp = codePage::kChar) + { + size_t len = ::strlen(str); + std::wstring wstr(len, 0); + + if (!wstr.empty()) + { + int wlen = MultiByteToWideChar(cp, 0, str, static_cast(len), &wstr[0], static_cast(wstr.size())); + wstr.resize(wlen); + } + + return wstr; + } + + inline std::wstring toWide(const std::string& str, UINT cp = codePage::kChar) + { + return toWide(str.c_str(), cp); + } + + inline const std::wstring& toWide(const std::wstring& str) // do nothing for already wide string + { + return str; + } + + inline std::string toNarrow(const std::wstring& wstr, long page) + { + int len = WideCharToMultiByte(page, 0, wstr.c_str(), static_cast(wstr.size()), 0, 0, 0, 0); + std::string str(len, 0); + + if (!str.empty()) + { + WideCharToMultiByte(page, 0, wstr.c_str(), static_cast(wstr.size()), &str[0], len, 0, 0); + } + + return str; + } +#elif PLOG_ENABLE_WCHAR_INPUT + inline std::string toNarrow(const wchar_t* wstr) + { + size_t wlen = ::wcslen(wstr); + std::string str(wlen * sizeof(wchar_t), 0); + + if (!str.empty()) + { + const char* in = reinterpret_cast(&wstr[0]); + char* out = &str[0]; + size_t inBytes = wlen * sizeof(wchar_t); + size_t outBytes = str.size(); + + iconv_t cd = ::iconv_open("UTF-8", "WCHAR_T"); + ::iconv(cd, const_cast(&in), &inBytes, &out, &outBytes); + ::iconv_close(cd); + + str.resize(str.size() - outBytes); + } + + return str; + } +#endif + + inline std::string processFuncName(const char* func) + { +#if (defined(_WIN32) && !defined(__MINGW32__)) || defined(__OBJC__) + return std::string(func); +#else + const char* funcBegin = func; + const char* funcEnd = ::strchr(funcBegin, '('); + int foundTemplate = 0; + + if (!funcEnd) + { + return std::string(func); + } + + for (const char* i = funcEnd - 1; i >= funcBegin; --i) // search backwards for the first space char + { + if (*i == '>') + { + foundTemplate++; + } + else if (*i == '<') + { + foundTemplate--; + } + else if (*i == ' ' && foundTemplate == 0) + { + funcBegin = i + 1; + break; + } + } + + return std::string(funcBegin, funcEnd); +#endif + } + + inline const nchar* findExtensionDot(const nchar* fileName) + { +#if PLOG_CHAR_IS_UTF8 + return std::strrchr(fileName, '.'); +#else + return std::wcsrchr(fileName, L'.'); +#endif + } + + inline void splitFileName(const nchar* fileName, nstring& fileNameNoExt, nstring& fileExt) + { + const nchar* dot = findExtensionDot(fileName); + + if (dot) + { + fileNameNoExt.assign(fileName, dot); + fileExt.assign(dot + 1); + } + else + { + fileNameNoExt.assign(fileName); + fileExt.clear(); + } + } + + class PLOG_LINKAGE NonCopyable + { + protected: + NonCopyable() + { + } + + private: + NonCopyable(const NonCopyable&); + NonCopyable& operator=(const NonCopyable&); + }; + + class PLOG_LINKAGE_HIDDEN File : NonCopyable + { + public: + File() : m_file(-1) + { + } + + ~File() + { + close(); + } + + size_t open(const nstring& fileName) + { +#if defined(_WIN32) && (defined(__BORLANDC__) || defined(__MINGW32__)) + m_file = ::_wsopen(toWide(fileName).c_str(), _O_CREAT | _O_WRONLY | _O_BINARY | _O_NOINHERIT, SH_DENYWR, _S_IREAD | _S_IWRITE); +#elif defined(_WIN32) + ::_wsopen_s(&m_file, toWide(fileName).c_str(), _O_CREAT | _O_WRONLY | _O_BINARY | _O_NOINHERIT, _SH_DENYWR, _S_IREAD | _S_IWRITE); +#elif defined(O_CLOEXEC) + m_file = ::open(fileName.c_str(), O_CREAT | O_APPEND | O_WRONLY | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +#else + m_file = ::open(fileName.c_str(), O_CREAT | O_APPEND | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); +#endif + return seek(0, SEEK_END); + } + + size_t write(const void* buf, size_t count) + { + return m_file != -1 ? static_cast( +#ifdef _WIN32 + ::_write(m_file, buf, static_cast(count)) +#else + ::write(m_file, buf, count) +#endif + ) : static_cast(-1); + } + + template + size_t write(const std::basic_string& str) + { + return write(str.data(), str.size() * sizeof(CharType)); + } + + size_t seek(size_t offset, int whence) + { + return m_file != -1 ? static_cast( +#if defined(_WIN32) && (defined(__BORLANDC__) || defined(__MINGW32__)) + ::_lseek(m_file, static_cast(offset), whence) +#elif defined(_WIN32) + ::_lseeki64(m_file, static_cast(offset), whence) +#else + ::lseek(m_file, static_cast(offset), whence) +#endif + ) : static_cast(-1); + } + + void close() + { + if (m_file != -1) + { +#ifdef _WIN32 + ::_close(m_file); +#else + ::close(m_file); +#endif + m_file = -1; + } + } + + static int unlink(const nstring& fileName) + { +#ifdef _WIN32 + return ::_wunlink(toWide(fileName).c_str()); +#else + return ::unlink(fileName.c_str()); +#endif + } + + static int rename(const nstring& oldFilename, const nstring& newFilename) + { +#ifdef _WIN32 + return MoveFileW(toWide(oldFilename).c_str(), toWide(newFilename).c_str()); +#else + return ::rename(oldFilename.c_str(), newFilename.c_str()); +#endif + } + + private: + int m_file; + }; + + class PLOG_LINKAGE_HIDDEN Mutex : NonCopyable + { + public: + Mutex() + { +#ifdef __FREERTOS__ + m_sync = xSemaphoreCreateBinary(); + xSemaphoreGive(m_sync); +#elif defined(_WIN32) + InitializeCriticalSection(&m_sync); +#elif defined(__rtems__) + rtems_semaphore_create(0, 1, + RTEMS_PRIORITY | + RTEMS_BINARY_SEMAPHORE | + RTEMS_INHERIT_PRIORITY, 1, &m_sync); +#elif defined(_POSIX_THREADS) + ::pthread_mutex_init(&m_sync, 0); +#endif + } + + ~Mutex() + { +#ifdef __FREERTOS__ + vSemaphoreDelete(m_sync); +#elif defined(_WIN32) + DeleteCriticalSection(&m_sync); +#elif defined(__rtems__) + rtems_semaphore_delete(m_sync); +#elif defined(_POSIX_THREADS) + ::pthread_mutex_destroy(&m_sync); +#endif + } + + friend class MutexLock; + + private: + void lock() + { +#ifdef __FREERTOS__ + xSemaphoreTake(m_sync, portMAX_DELAY); +#elif defined(_WIN32) + EnterCriticalSection(&m_sync); +#elif defined(__rtems__) + rtems_semaphore_obtain(m_sync, RTEMS_WAIT, RTEMS_NO_TIMEOUT); +#elif defined(_POSIX_THREADS) + ::pthread_mutex_lock(&m_sync); +#endif + } + + void unlock() + { +#ifdef __FREERTOS__ + xSemaphoreGive(m_sync); +#elif defined(_WIN32) + LeaveCriticalSection(&m_sync); +#elif defined(__rtems__) + rtems_semaphore_release(m_sync); +#elif defined(_POSIX_THREADS) + ::pthread_mutex_unlock(&m_sync); +#endif + } + + private: +#ifdef __FREERTOS__ + SemaphoreHandle_t m_sync; +#elif defined(_WIN32) + CRITICAL_SECTION m_sync; +#elif defined(__rtems__) + rtems_id m_sync; +#elif defined(_POSIX_THREADS) + pthread_mutex_t m_sync; +#endif + }; + + class PLOG_LINKAGE_HIDDEN MutexLock : NonCopyable + { + public: + MutexLock(Mutex& mutex) : m_mutex(mutex) + { + m_mutex.lock(); + } + + ~MutexLock() + { + m_mutex.unlock(); + } + + private: + Mutex& m_mutex; + }; + + template +#ifdef _WIN32 + class Singleton : NonCopyable +#else + class PLOG_LINKAGE Singleton : NonCopyable +#endif + { + public: +#if (defined(__clang__) || defined(__GNUC__) && __GNUC__ >= 8) && !defined(__BORLANDC__) + // This constructor is called before the `T` object is fully constructed, and + // pointers are not dereferenced anyway, so UBSan shouldn't check vptrs. + __attribute__((no_sanitize("vptr"))) +#endif + Singleton() + { + assert(!m_instance); + m_instance = static_cast(this); + } + + ~Singleton() + { + assert(m_instance); + m_instance = 0; + } + + static T* getInstance() + { + return m_instance; + } + + private: + static T* m_instance; + }; + + template + T* Singleton::m_instance = NULL; + } +} diff --git a/include/plog/WinApi.h b/include/plog/WinApi.h new file mode 100644 index 000000000..ccf44af0a --- /dev/null +++ b/include/plog/WinApi.h @@ -0,0 +1,175 @@ +#pragma once + +#ifdef _WIN32 + +// These windows structs must be in a global namespace +struct HKEY__; +struct _SECURITY_ATTRIBUTES; +struct _CONSOLE_SCREEN_BUFFER_INFO; +struct _RTL_CRITICAL_SECTION; + +namespace plog +{ + typedef unsigned long DWORD; + typedef unsigned short WORD; + typedef unsigned char BYTE; + typedef unsigned int UINT; + typedef int BOOL; + typedef long LSTATUS; + typedef char* LPSTR; + typedef wchar_t* LPWSTR; + typedef const char* LPCSTR; + typedef const wchar_t* LPCWSTR; + typedef void* HANDLE; + typedef HKEY__* HKEY; + typedef size_t ULONG_PTR; + + struct CRITICAL_SECTION + { + void* DebugInfo; + long LockCount; + long RecursionCount; + HANDLE OwningThread; + HANDLE LockSemaphore; + ULONG_PTR SpinCount; + }; + + struct COORD + { + short X; + short Y; + }; + + struct SMALL_RECT + { + short Left; + short Top; + short Right; + short Bottom; + }; + + struct CONSOLE_SCREEN_BUFFER_INFO + { + COORD dwSize; + COORD dwCursorPosition; + WORD wAttributes; + SMALL_RECT srWindow; + COORD dwMaximumWindowSize; + }; + + namespace codePage + { + const UINT kActive = 0; + const UINT kUTF8 = 65001; +#if PLOG_CHAR_IS_UTF8 + const UINT kChar = kUTF8; +#else + const UINT kChar = kActive; +#endif + } + + namespace eventLog + { + const WORD kErrorType = 0x0001; + const WORD kWarningType = 0x0002; + const WORD kInformationType = 0x0004; + } + + namespace hkey + { + const HKEY kLocalMachine = reinterpret_cast(static_cast(0x80000002)); + } + + namespace regSam + { + const DWORD kQueryValue = 0x0001; + const DWORD kSetValue = 0x0002; + } + + namespace regType + { + const DWORD kExpandSz = 2; + const DWORD kDword = 4; + } + + namespace stdHandle + { + const DWORD kOutput = static_cast(-11); + const DWORD kErrorOutput = static_cast(-12); + } + + namespace foreground + { + const WORD kBlue = 0x0001; + const WORD kGreen = 0x0002; + const WORD kRed = 0x0004; + const WORD kIntensity = 0x0008; + } + + namespace background + { + const WORD kBlue = 0x0010; + const WORD kGreen = 0x0020; + const WORD kRed = 0x0040; + const WORD kIntensity = 0x0080; + } + + extern "C" + { + __declspec(dllimport) int __stdcall MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar); + __declspec(dllimport) int __stdcall WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, const char* lpDefaultChar, BOOL* lpUsedDefaultChar); + + __declspec(dllimport) DWORD __stdcall GetCurrentThreadId(); + + __declspec(dllimport) BOOL __stdcall MoveFileW(LPCWSTR lpExistingFileName, LPCWSTR lpNewFileName); + + __declspec(dllimport) void __stdcall InitializeCriticalSection(_RTL_CRITICAL_SECTION* lpCriticalSection); + __declspec(dllimport) void __stdcall EnterCriticalSection(_RTL_CRITICAL_SECTION* lpCriticalSection); + __declspec(dllimport) void __stdcall LeaveCriticalSection(_RTL_CRITICAL_SECTION* lpCriticalSection); + __declspec(dllimport) void __stdcall DeleteCriticalSection(_RTL_CRITICAL_SECTION* lpCriticalSection); + + __declspec(dllimport) HANDLE __stdcall RegisterEventSourceW(LPCWSTR lpUNCServerName, LPCWSTR lpSourceName); + __declspec(dllimport) BOOL __stdcall DeregisterEventSource(HANDLE hEventLog); + __declspec(dllimport) BOOL __stdcall ReportEventW(HANDLE hEventLog, WORD wType, WORD wCategory, DWORD dwEventID, void* lpUserSid, WORD wNumStrings, DWORD dwDataSize, LPCWSTR* lpStrings, void* lpRawData); + + __declspec(dllimport) LSTATUS __stdcall RegCreateKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD Reserved, LPWSTR lpClass, DWORD dwOptions, DWORD samDesired, _SECURITY_ATTRIBUTES* lpSecurityAttributes, HKEY* phkResult, DWORD* lpdwDisposition); + __declspec(dllimport) LSTATUS __stdcall RegSetValueExW(HKEY hKey, LPCWSTR lpValueName, DWORD Reserved, DWORD dwType, const BYTE* lpData, DWORD cbData); + __declspec(dllimport) LSTATUS __stdcall RegCloseKey(HKEY hKey); + __declspec(dllimport) LSTATUS __stdcall RegOpenKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, DWORD samDesired, HKEY* phkResult); + __declspec(dllimport) LSTATUS __stdcall RegDeleteKeyW(HKEY hKey, LPCWSTR lpSubKey); + + __declspec(dllimport) HANDLE __stdcall GetStdHandle(DWORD nStdHandle); + + __declspec(dllimport) BOOL __stdcall WriteConsoleW(HANDLE hConsoleOutput, const void* lpBuffer, DWORD nNumberOfCharsToWrite, DWORD* lpNumberOfCharsWritten, void* lpReserved); + __declspec(dllimport) BOOL __stdcall GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, _CONSOLE_SCREEN_BUFFER_INFO* lpConsoleScreenBufferInfo); + __declspec(dllimport) BOOL __stdcall SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes); + + __declspec(dllimport) void __stdcall OutputDebugStringW(LPCWSTR lpOutputString); + } + + inline void InitializeCriticalSection(CRITICAL_SECTION* criticalSection) + { + plog::InitializeCriticalSection(reinterpret_cast<_RTL_CRITICAL_SECTION*>(criticalSection)); + } + + inline void EnterCriticalSection(CRITICAL_SECTION* criticalSection) + { + plog::EnterCriticalSection(reinterpret_cast<_RTL_CRITICAL_SECTION*>(criticalSection)); + } + + inline void LeaveCriticalSection(CRITICAL_SECTION* criticalSection) + { + plog::LeaveCriticalSection(reinterpret_cast<_RTL_CRITICAL_SECTION*>(criticalSection)); + } + + inline void DeleteCriticalSection(CRITICAL_SECTION* criticalSection) + { + plog::DeleteCriticalSection(reinterpret_cast<_RTL_CRITICAL_SECTION*>(criticalSection)); + } + + inline BOOL GetConsoleScreenBufferInfo(HANDLE consoleOutput, CONSOLE_SCREEN_BUFFER_INFO* consoleScreenBufferInfo) + { + return plog::GetConsoleScreenBufferInfo(consoleOutput, reinterpret_cast<_CONSOLE_SCREEN_BUFFER_INFO*>(consoleScreenBufferInfo)); + } +} +#endif // _WIN32 diff --git a/include/toml11/LICENSE b/include/toml11/LICENSE new file mode 100644 index 000000000..f55c511d6 --- /dev/null +++ b/include/toml11/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Toru Niina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/global/utils/toml.h b/include/toml11/toml.hpp similarity index 99% rename from src/global/utils/toml.h rename to include/toml11/toml.hpp index 5d9981e9f..9c1405213 100644 --- a/src/global/utils/toml.h +++ b/include/toml11/toml.hpp @@ -16456,12 +16456,12 @@ namespace toml { inline namespace literals { inline namespace toml_literals { - ::toml::value operator"" _toml(const char* str, std::size_t len); + ::toml::value operator""_toml(const char* str, std::size_t len); #if defined(TOML11_HAS_CHAR8_T) // value of u8"" literal has been changed from char to char8_t and char8_t // is NOT compatible to char - ::toml::value operator"" _toml(const char8_t* str, std::size_t len); + ::toml::value operator""_toml(const char8_t* str, std::size_t len); #endif } // namespace toml_literals @@ -16566,7 +16566,7 @@ namespace toml { inline namespace literals { inline namespace toml_literals { - TOML11_INLINE ::toml::value operator"" _toml(const char* str, + TOML11_INLINE ::toml::value operator""_toml(const char* str, std::size_t len) { if (len == 0) { return ::toml::value {}; @@ -16596,7 +16596,7 @@ namespace toml { #if defined(TOML11_HAS_CHAR8_T) // value of u8"" literal has been changed from char to char8_t and char8_t // is NOT compatible to char - TOML11_INLINE ::toml::value operator"" _toml(const char8_t* str, + TOML11_INLINE ::toml::value operator""_toml(const char8_t* str, std::size_t len) { if (len == 0) { return ::toml::value {}; diff --git a/input.example.toml b/input.example.toml index bffb1d71a..f890143a6 100644 --- a/input.example.toml +++ b/input.example.toml @@ -107,7 +107,7 @@ [grid.boundaries.match] # Size of the matching layer in each direction for fields in physical (code) units - # @type: float | array> + # @type: float | array> # @default: 1% of the domain size (in shortest dimension) # @note: In spherical, this is the size of the layer in `r` from the outer wall # @example: `ds = 1.5` (will set the same for all directions) @@ -195,6 +195,83 @@ # @from: `scales.larmor0` # @value: `1 / larmor0` +[radiation] + [radiation.drag] + [radiation.drag.synchrotron] + # Radiation reaction limit gamma-factor for synchrotron + # @type: float [> 0.0] + # @default: 1.0 + # @note: [required] if one of the species has `radiative_drag = "synchrotron"` + gamma_rad = "" + + [radiation.drag.compton] + # Radiation reaction limit gamma-factor for Compton drag + # @type: float [> 0.0] + # @default: 1.0 + # @note: [required] if one of the species has `radiative_drag = "compton"` + gamma_rad = "" + + [radiation.emission] + [radiation.emission.synchrotron] + # Gamma-factor of a particle emitting synchrotron photons at energy `m0 c^2` in fiducial magnetic field `B0` + # @type: float [> 1.0] + # @default: 10.0 + gamma_qed = "" + # Minimum photon energy for synchrotron emission (units of `m0 c^2`) + # @type: float [> 0.0] + # @default: 1e-4 + photon_energy_min = "" + # Weights for the emitted synchrotron photons + # @type: float [> 0.0] + # @default: 1.0 + photon_weight = "" + # Index of species for the emitted photon + # @type: ushort [> 0] + # @required + photon_species = "" + + # @inferred: + # - nominal_probability + # @brief: Nominal probability of the emission for a particle with `gamma * beta = 1`, charge-to-mass = `q0 / m0` + # @type: float + # @from: `.gamma_qed`, `.photon_weight`, `...drag.synchrotron.gamma_rad`, `scales.omegaB0`, `algorithms.timestep.dt` + # @value: `0.1 * omegaB0 * dt * (gamma_qed / gamma_rad)^2 / photon_weight` + # - nominal_photon_energy + # @brief: Nominal energy of the emitted photon for a particle with `gamma * beta = 1`, mass = `m0` + # @type: float + # @from: `.gamma_qed` + # @value: `(1 / gamma_qed)^2` + + [radiation.emission.compton] + # Gamma-factor of a particle emitting inverse Compton photons at energy `m0 c^2` in fiducial magnetic field `B0` + # @type: float [> 1.0] + # @default: 10.0 + gamma_qed = "" + # Minimum photon energy for inverse Compton emission (units of `m0 c^2`) + # @type: float [> 0.0] + # @default: 1e-4 + photon_energy_min = "" + # Weights for the emitted inverse Compton photons + # @type: float [> 0.0] + # @default: 1.0 + photon_weight = "" + # Index of species for the emitted photon + # @type: ushort [> 0] + # @required + photon_species = "" + + # @inferred: + # - nominal_probability + # @brief: Nominal probability of the emission for a particle with `gamma * beta = 1`, charge-to-mass = `q0 / m0` + # @type: float + # @from: `.gamma_qed`, `.photon_weight`, `...drag.compton.gamma_rad`, `scales.omegaB0`, `algorithms.timestep.dt` + # @value: `0.1 * omegaB0 * dt * (gamma_qed / gamma_rad)^2 / photon_weight` + # - nominal_photon_energy + # @brief: Nominal energy of the emitted photon for a particle with `gamma * beta = 1`, mass = `m0` + # @type: float + # @from: `.gamma_qed` + # @value: `(1 / gamma_qed)^2` + [algorithms] # Number of current smoothing passes # @type: ushort [>= 0] @@ -224,10 +301,12 @@ # @type: bool # @default: true enable = "" - # Order of the particle shape function - # @type: int - # @default: 1 - order = "" + + # @inferred: + # - order + # @brief: order of the particle shape function + # @from: compile-time definition `shape_order` + # @type: ushort [0 -> 10] [algorithms.gr] # Stepsize for numerical differentiation in GR pusher @@ -250,13 +329,6 @@ # @note: When `larmor_max` == 0, the limit is disabled larmor_max = "" - [algorithms.synchrotron] - # Radiation reaction limit gamma-factor for synchrotron - # @type: float [> 0.0] - # @default: 1.0 - # @note: [required] if one of the species has `cooling = "synchrotron"` - gamma_rad = "" - # Stencil coefficients for the field solver [notation as in Blinne+ (2018)] # @note: Standard Yee solver: `delta_i = beta_ij = 0.0` [algorithms.fieldsolver] @@ -318,11 +390,16 @@ # @type: bool # @default: false use_weights = "" - # Timesteps between particle re-sorting (removing dead particles) + # Timesteps between particle re-sorting by tags (removing dead particles) # @type: uint # @default: 100 # @note: Set to 0 to disable re-sorting clear_interval = "" + # Timesteps between spatial sorting of particles (for better cache performance) + # @type: uint + # @default: 0 + # @note: Set to 0 to disable spatial sorting + spatial_sorting_interval = "" # @inferred: # - nspec @@ -370,8 +447,29 @@ # Radiation reaction to use for the species # @type: string # @default: "None" - # @enum: "None", "Synchrotron" - cooling = "" + # @enum: "None", "Synchrotron", "Compton" + # @note: Can also be coma-separated combination, e.g., "Synchrotron,Compton" + # @note: Relevant radiation.drag parameters should also be provided + radiative_drag = "" + # Particle emission policy for the species + # @type: string + # @default: "None" + # @enum: "None", "Synchrotron", "Compton" + # @note: Only one emission mechanism allowed + # @note: Appropriate radiation drag flag will be applied automatically (unless explicitly set to "None") + emission = "" + # Timesteps between spatial sorting of particles for given species + # @type: uint + # @default: 0 + # @note: Set to 0 to disable spatial sorting + # @note: Overrides `particles.spatial_sorting_interval` for the given species + spatial_sorting_interval = "" + # Timesteps between particle re-sorting by tags (removing dead particles) + # @type: uint + # @default: 100 + # @note: Set to 0 to disable re-sorting + # @note: Overrides `particles.clear_interval` for the given species + clear_interval = "" # Parameters for specific problem generators and setups [setup] @@ -553,7 +651,7 @@ # @note: 0 = disable checkpointing # @note: -1 = keep all checkpoints keep = "" - # Write a checkpoint once after a fixed walltime + # Write a checkpoint once after a fixed walltime # @type: string # @default: "00:00:00" # @note: The format is "HH:MM:SS" diff --git a/conda-entity-nompi.sh b/legacy/conda-entity-nompi.sh similarity index 100% rename from conda-entity-nompi.sh rename to legacy/conda-entity-nompi.sh diff --git a/docker-compose.yml b/legacy/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to legacy/docker-compose.yml diff --git a/minimal/CMakeLists.txt b/minimal/CMakeLists.txt index b21dd0fec..6a43c0dbd 100644 --- a/minimal/CMakeLists.txt +++ b/minimal/CMakeLists.txt @@ -83,13 +83,16 @@ if("KOKKOS" IN_LIST MODES) endif() if("ADIOS2_NOMPI" IN_LIST MODES) - set(libs stdc++fs) + set(libs "") + if(NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang") + list(APPEND libs stdc++fs) + endif() set(exec adios2-nompi.xc) set(src ${CMAKE_CURRENT_SOURCE_DIR}/adios2.cpp) find_kokkos() find_adios2() - list(APPEND libs Kokkos::kokkos adios2::cxx11) + list(APPEND libs Kokkos::kokkos adios2::cxx) add_executable(${exec} ${src}) @@ -97,14 +100,17 @@ if("ADIOS2_NOMPI" IN_LIST MODES) endif() if("ADIOS2_MPI" IN_LIST MODES) - set(libs stdc++fs) + set(libs "") + if(NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang") + list(APPEND libs stdc++fs) + endif() set(exec adios2-mpi.xc) set(src ${CMAKE_CURRENT_SOURCE_DIR}/adios2.cpp) find_package(MPI REQUIRED) find_kokkos() find_adios2() - list(APPEND libs MPI::MPI_CXX Kokkos::kokkos adios2::cxx11_mpi) + list(APPEND libs MPI::MPI_CXX Kokkos::kokkos adios2::cxx_mpi) add_executable(${exec} ${src}) diff --git a/minimal/adios2.cpp b/minimal/adios2.cpp index cd4ca3d6f..e664ef87e 100644 --- a/minimal/adios2.cpp +++ b/minimal/adios2.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #if defined(MPI_ENABLED) #include @@ -366,7 +366,8 @@ template auto define_constdim_array(adios2::IO& io, const std::vector& glob_shape, const std::vector& loc_corner, - const std::vector& loc_shape) -> std::string { + const std::vector& loc_shape) + -> std::string { const std::string arrname = "ConstantDimArr" + std::to_string(glob_shape.size()) + "D::" + std::string(typeid(T).name()); diff --git a/pgens/CMakeLists.txt b/pgens/CMakeLists.txt deleted file mode 100644 index e3d047a98..000000000 --- a/pgens/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# ------------------------------ -# @defines: ntt_pgen [INTERFACE] -# -# @includes: -# -# * ../src/ -# ------------------------------ - -add_library(ntt_pgen INTERFACE) -target_link_libraries(ntt_pgen INTERFACE ntt_global ntt_framework - ntt_archetypes ntt_kernels) - -target_include_directories(ntt_pgen - INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/${PGEN}) diff --git a/pgens/accretion/pgen.hpp b/pgens/accretion/pgen.hpp index 42690f041..a9dbd65bc 100644 --- a/pgens/accretion/pgen.hpp +++ b/pgens/accretion/pgen.hpp @@ -5,15 +5,14 @@ #include "global.h" #include "arch/kokkos_aliases.h" -#include "arch/traits.h" +#include "traits/metric.h" +#include "traits/pgen.h" #include "utils/numeric.h" #include "archetypes/energy_dist.h" #include "archetypes/particle_injector.h" -#include "archetypes/problem_generator.h" -#include "archetypes/spatial_dist.h" #include "framework/domain/metadomain.h" - +#include "framework/parameters/parameters.h" #include "kernels/particle_moments.hpp" namespace user { @@ -43,7 +42,8 @@ namespace user { TWO * metric.spin() * g_00); } - Inline auto bx1(const coord_t& x_Ph) const -> real_t { // at ( i , j + HALF ) + Inline auto bx1(const coord_t& x_Ph) const + -> real_t { // at ( i , j + HALF ) coord_t xi { ZERO }, x0m { ZERO }, x0p { ZERO }; metric.template convert(x_Ph, xi); @@ -61,7 +61,8 @@ namespace user { } } - Inline auto bx2(const coord_t& x_Ph) const -> real_t { // at ( i + HALF , j ) + Inline auto bx2(const coord_t& x_Ph) const + -> real_t { // at ( i + HALF , j ) coord_t xi { ZERO }, x0m { ZERO }, x0p { ZERO }; metric.template convert(x_Ph, xi); @@ -78,19 +79,19 @@ namespace user { } } - Inline auto bx3(const coord_t& x_Ph) const -> real_t { + Inline auto bx3(const coord_t& /*x_Ph*/) const -> real_t { return ZERO; } - Inline auto dx1(const coord_t& x_Ph) const -> real_t { + Inline auto dx1(const coord_t& /*x_Ph*/) const -> real_t { return ZERO; } - Inline auto dx2(const coord_t& x_Ph) const -> real_t { + Inline auto dx2(const coord_t& /*x_Ph*/) const -> real_t { return ZERO; } - Inline auto dx3(const coord_t& x_Ph) const -> real_t { + Inline auto dx3(const coord_t& /*x_Ph*/) const -> real_t { return ZERO; } @@ -99,21 +100,20 @@ namespace user { const real_t m_eps; }; - template - struct PointDistribution : public arch::SpatialDistribution { - PointDistribution(const std::vector& xi_min, - const std::vector& xi_max, - const real_t sigma_thr, - const real_t dens_thr, - const SimulationParams& params, - Domain* domain_ptr) - : arch::SpatialDistribution { domain_ptr->mesh.metric } - , metric { domain_ptr->mesh.metric } + template + struct PointDistribution { + PointDistribution(const std::vector& xi_min, + const std::vector& xi_max, + const real_t sigma_thr, + const real_t dens_thr, + const SimulationParams& params, + Domain* domain_ptr) + : metric { domain_ptr->mesh.metric } , EM { domain_ptr->fields.em } , density { domain_ptr->fields.buff } , sigma_thr { sigma_thr } - , inv_n0 { ONE / params.template get("scales.n0") } - , dens_thr { dens_thr } { + , dens_thr { dens_thr } + , inv_n0 { ONE / params.template get("scales.n0") } { std::copy(xi_min.begin(), xi_min.end(), x_min); std::copy(xi_max.begin(), xi_max.end(), x_max); @@ -138,7 +138,7 @@ namespace user { Kokkos::parallel_for( "ComputeMoments", prtl_spec.rangeActiveParticles(), - kernel::ParticleMoments_kernel({}, scatter_buff, 0u, + kernel::ParticleMoments_kernel({}, scatter_buff, 0u, prtl_spec.i1, prtl_spec.i2, prtl_spec.i3, prtl_spec.dx1, prtl_spec.dx2, prtl_spec.dx3, prtl_spec.ux1, prtl_spec.ux2, prtl_spec.ux3, @@ -183,30 +183,29 @@ namespace user { } private: - tuple_t x_min; - tuple_t x_max; + const M metric; + ndfield_t EM; + ndfield_t density; + tuple_t x_min { ZERO }; + tuple_t x_max { ZERO }; const real_t sigma_thr; const real_t dens_thr; const real_t inv_n0; - Domain* domain_ptr; - ndfield_t density; - ndfield_t EM; - const M metric; }; template - struct PGen : public arch::ProblemGenerator { + struct PGen { + static constexpr auto D { M::Dim }; // compatibility traits for the problem generator - static constexpr auto engines { traits::compatible_with::value }; + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; static constexpr auto metrics { - traits::compatible_with::value + ::traits::pgen::compatible_with {} }; - static constexpr auto dimensions { traits::compatible_with::value }; + static constexpr auto dimensions { ::traits::pgen::compatible_with {} }; - // for easy access to variables in the child class - using arch::ProblemGenerator::D; - using arch::ProblemGenerator::C; - using arch::ProblemGenerator::params; + const SimulationParams& params; const std::vector xi_min; const std::vector xi_max; @@ -215,30 +214,30 @@ namespace user { InitFields init_flds; const Metadomain* metadomain; - inline PGen(SimulationParams& p, const Metadomain& m) - : arch::ProblemGenerator(p) - , xi_min { p.template get>("setup.xi_min") } - , xi_max { p.template get>("setup.xi_max") } - , sigma_max { p.template get("setup.sigma_max") } - , sigma0 { p.template get("scales.sigma0") } - , multiplicity { p.template get("setup.multiplicity") } - , nGJ { p.template get("scales.B0") * - SQR(p.template get("scales.skindepth0")) } - , temperature { p.template get("setup.temperature") } - , m_eps { p.template get("setup.m_eps") } + PGen(SimulationParams& p, const Metadomain& m) + : params { p } + , xi_min { params.template get>("setup.xi_min") } + , xi_max { params.template get>("setup.xi_max") } + , sigma_max { params.template get("setup.sigma_max") } + , sigma0 { params.template get("scales.sigma0") } + , multiplicity { params.template get("setup.multiplicity") } + , nGJ { params.template get("scales.B0") * + SQR(params.template get("scales.skindepth0")) } + , temperature { params.template get("setup.temperature") } + , m_eps { params.template get("setup.m_eps") } , init_flds { m.mesh().metric, m_eps } , metadomain { &m } {} - inline void InitPrtls(Domain& local_domain) { - const auto energy_dist = arch::Maxwellian(local_domain.mesh.metric, - local_domain.random_pool(), - temperature); - const auto spatial_dist = PointDistribution(xi_min, - xi_max, - sigma_max / sigma0, - multiplicity * nGJ, - params, - &local_domain); + void InitPrtls(Domain& local_domain) { + const auto energy_dist = arch::energy_dist::Maxwellian( + local_domain.random_pool(), + temperature); + const auto spatial_dist = PointDistribution(xi_min, + xi_max, + sigma_max / sigma0, + multiplicity * nGJ, + params, + &local_domain); arch::InjectNonUniform( params, @@ -250,16 +249,18 @@ namespace user { true); } - void CustomPostStep(std::size_t, long double time, Domain& local_domain) { - const auto energy_dist = arch::Maxwellian(local_domain.mesh.metric, - local_domain.random_pool(), - temperature); - const auto spatial_dist = PointDistribution(xi_min, - xi_max, - sigma_max / sigma0, - multiplicity * nGJ, - params, - &local_domain); + void CustomPostStep(timestep_t /*step*/, + simtime_t /*time*/, + Domain& local_domain) { + const auto energy_dist = arch::energy_dist::Maxwellian( + local_domain.random_pool(), + temperature); + const auto spatial_dist = PointDistribution(xi_min, + xi_max, + sigma_max / sigma0, + multiplicity * nGJ, + params, + &local_domain); arch::InjectNonUniform( params, local_domain, diff --git a/pgens/magnetosphere/pgen.hpp b/pgens/magnetosphere/pgen.hpp index 1afc8ccc4..4ef798844 100644 --- a/pgens/magnetosphere/pgen.hpp +++ b/pgens/magnetosphere/pgen.hpp @@ -5,18 +5,18 @@ #include "global.h" #include "arch/kokkos_aliases.h" -#include "arch/traits.h" +#include "traits/pgen.h" #include "utils/numeric.h" -#include "archetypes/problem_generator.h" #include "framework/domain/metadomain.h" +#include "framework/parameters/parameters.h" #include namespace user { using namespace ntt; - enum class FieldGeometry { + enum class FieldGeometry : uint8_t { dipole, monopole }; @@ -52,13 +52,13 @@ namespace user { template struct DriveFields : public InitFields { - DriveFields(real_t time, + DriveFields(simtime_t time, real_t bsurf, real_t rstar, real_t omega, const std::string& field_geometry) : InitFields { bsurf, rstar, field_geometry } - , time { time } + , time { (real_t)time } , Omega { omega } {} using InitFields::bx1; @@ -85,39 +85,34 @@ namespace user { }; template - struct PGen : public arch::ProblemGenerator { + struct PGen { + static constexpr auto D { M::Dim }; // compatibility traits for the problem generator - static constexpr auto engines { traits::compatible_with::value }; + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; static constexpr auto metrics { - traits::compatible_with::value + ::traits::pgen::compatible_with {} }; - static constexpr auto dimensions { traits::compatible_with::value }; - - // for easy access to variables in the child class - using arch::ProblemGenerator::D; - using arch::ProblemGenerator::C; - using arch::ProblemGenerator::params; + static constexpr auto dimensions { ::traits::pgen::compatible_with {} }; const real_t Bsurf, Rstar, Omega; const std::string field_geom; InitFields init_flds; - inline PGen(const SimulationParams& p, const Metadomain& m) - : arch::ProblemGenerator(p) - , Bsurf { p.template get("setup.Bsurf", ONE) } + PGen(const SimulationParams& p, const Metadomain& m) + : Bsurf { p.template get("setup.Bsurf", ONE) } , Rstar { m.mesh().extent(in::x1).first } , Omega { static_cast(constant::TWO_PI) / p.template get("setup.period", ONE) } , field_geom { p.template get("setup.field_geometry", "dipole") } , init_flds { Bsurf, Rstar, field_geom } {} - inline PGen() {} - - auto AtmFields(real_t time) const -> DriveFields { + auto AtmFields(simtime_t time) const -> DriveFields { return DriveFields { time, Bsurf, Rstar, Omega, field_geom }; } - auto MatchFields(real_t) const -> InitFields { + auto MatchFields(simtime_t) const -> InitFields { return InitFields { Bsurf, Rstar, field_geom }; } }; diff --git a/pgens/magnetosphere/sketch.png b/pgens/magnetosphere/sketch.png index fe33228e4..18ddc7199 100644 Binary files a/pgens/magnetosphere/sketch.png and b/pgens/magnetosphere/sketch.png differ diff --git a/pgens/pgen.hpp b/pgens/pgen.hpp index 78d8e6486..3daf262be 100644 --- a/pgens/pgen.hpp +++ b/pgens/pgen.hpp @@ -4,10 +4,9 @@ #include "enums.h" #include "global.h" -#include "arch/traits.h" +#include "traits/pgen.h" #include "utils/formatting.h" -#include "archetypes/problem_generator.h" #include "framework/domain/metadomain.h" #include @@ -16,34 +15,28 @@ namespace user { using namespace ntt; template - struct PGen : public arch::ProblemGenerator { + struct PGen { // compatibility traits for the problem generator static constexpr auto engines { - traits::compatible_with::value + ::traits::pgen::compatible_with {} }; static constexpr auto metrics { - traits::compatible_with::value + ::traits::pgen::compatible_with {} }; static constexpr auto dimensions { - traits::compatible_with::value + ::traits::pgen::compatible_with {} }; - // for easy access to variables in the child class - using arch::ProblemGenerator::D; - using arch::ProblemGenerator::C; - using arch::ProblemGenerator::params; - - inline PGen(const SimulationParams& p, const Metadomain&) - : arch::ProblemGenerator { p } { + PGen(const SimulationParams&, const Metadomain&) { const auto message = fmt::format( "Problem generator initialized with `%s` engine and `%dD %s` metric", SimEngine(S).to_string(), - D, + M::Dim, Metric(M::MetricType).to_string()); PLOGI << message; } diff --git a/pgens/reconnection/pgen.hpp b/pgens/reconnection/pgen.hpp index d152b578a..b0450b600 100644 --- a/pgens/reconnection/pgen.hpp +++ b/pgens/reconnection/pgen.hpp @@ -5,30 +5,27 @@ #include "global.h" #include "arch/kokkos_aliases.h" -#include "arch/traits.h" +#include "traits/pgen.h" #include "utils/numeric.h" #include "archetypes/energy_dist.h" #include "archetypes/particle_injector.h" -#include "archetypes/problem_generator.h" #include "archetypes/spatial_dist.h" #include "archetypes/utils.h" #include "framework/domain/metadomain.h" - #include "kernels/particle_moments.hpp" namespace user { using namespace ntt; - template - struct CurrentLayer : public arch::SpatialDistribution { - CurrentLayer(const M& metric, real_t cs_width, real_t center_x, real_t cs_y) - : arch::SpatialDistribution { metric } - , cs_width { cs_width } + template + struct CurrentLayer { + CurrentLayer(real_t cs_width, real_t center_x, real_t cs_y) + : cs_width { cs_width } , center_x { center_x } , cs_y { cs_y } {} - Inline auto operator()(const coord_t& x_Ph) const -> real_t { + Inline auto operator()(const coord_t& x_Ph) const -> real_t { return ONE / SQR(math::cosh((x_Ph[1] - cs_y) / cs_width)) * (ONE - math::exp(-SQR((x_Ph[0] - center_x) / cs_width))); } @@ -139,18 +136,21 @@ namespace user { // constant particle density for particle boundaries template - struct PGen : public arch::ProblemGenerator { + struct PGen { + static constexpr auto D { M::Dim }; // compatibility traits for the problem generator - static constexpr auto engines { traits::compatible_with::value }; - static constexpr auto metrics { traits::compatible_with::value }; + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; static constexpr auto dimensions { - traits::compatible_with::value + ::traits::pgen::compatible_with {} }; - // for easy access to variables in the child class - using arch::ProblemGenerator::D; - using arch::ProblemGenerator::C; - using arch::ProblemGenerator::params; + const SimulationParams& params; + Metadomain& metadomain; const real_t bg_B, bg_Bguide, bg_temperature, inj_ypad; const real_t cs_width, cs_overdensity, cs_x, cs_y; @@ -158,33 +158,29 @@ namespace user { const simtime_t t_open; bool bc_opened { false }; - Metadomain& metadomain; - InitFields init_flds; - inline PGen(const SimulationParams& p, Metadomain& m) - : arch::ProblemGenerator(p) - , bg_B { p.template get("setup.bg_B", 1.0) } - , bg_Bguide { p.template get("setup.bg_Bguide", 0.0) } - , bg_temperature { p.template get("setup.bg_temperature", 0.001) } - , inj_ypad { p.template get("setup.inj_ypad", (real_t)0.05) } - , cs_width { p.template get("setup.cs_width") } - , cs_overdensity { p.template get("setup.cs_overdensity") } + PGen(const SimulationParams& p, Metadomain& m) + : params { p } + , metadomain { m } + , bg_B { params.template get("setup.bg_B", 1.0) } + , bg_Bguide { params.template get("setup.bg_Bguide", 0.0) } + , bg_temperature { params.template get("setup.bg_temperature", 0.001) } + , inj_ypad { params.template get("setup.inj_ypad", (real_t)0.05) } + , cs_width { params.template get("setup.cs_width") } + , cs_overdensity { params.template get("setup.cs_overdensity") } , cs_x { INV_2 * (m.mesh().extent(in::x1).second + m.mesh().extent(in::x1).first) } , cs_y { INV_2 * (m.mesh().extent(in::x2).second + m.mesh().extent(in::x2).first) } , ymin { m.mesh().extent(in::x2).first } , ymax { m.mesh().extent(in::x2).second } - , t_open { p.template get( + , t_open { params.template get( "setup.t_open", 1.5 * HALF * (m.mesh().extent(in::x1).second - m.mesh().extent(in::x1).first)) } - , metadomain { m } , init_flds { bg_B, bg_Bguide, cs_width, cs_y } {} - inline PGen() {} - auto MatchFieldsInX1(simtime_t) const -> BoundaryFieldsInX1 { return BoundaryFieldsInX1 { bg_B, bg_Bguide, (real_t)0.1, cs_width, cs_x, cs_y }; @@ -194,7 +190,7 @@ namespace user { return BoundaryFieldsInX2 { bg_B, bg_Bguide, cs_width, cs_y }; } - inline void InitPrtls(Domain& local_domain) { + void InitPrtls(Domain& local_domain) { // background arch::InjectUniformMaxwellian(params, local_domain, @@ -211,14 +207,10 @@ namespace user { const auto cs_temperature = HALF * sigma / cs_overdensity; // current layer - auto edist_cs = arch::Maxwellian(local_domain.mesh.metric, - local_domain.random_pool(), - cs_temperature, - { ZERO, ZERO, cs_drift_u }); - const auto sdist_cs = CurrentLayer(local_domain.mesh.metric, - cs_width, - cs_x, - cs_y); + auto edist_cs = arch::energy_dist::Maxwellian( + local_domain.random_pool(), + cs_temperature); + const auto sdist_cs = CurrentLayer(cs_width, cs_x, cs_y); arch::InjectNonUniform( params, local_domain, @@ -238,9 +230,9 @@ namespace user { metadomain.setPrtlBC(bc_in::Px1, PrtlBC::ABSORB); } - const auto energy_dist = arch::Maxwellian(domain.mesh.metric, - domain.random_pool(), - bg_temperature); + const auto energy_dist = arch::energy_dist::Maxwellian( + domain.random_pool(), + bg_temperature); const auto dx = domain.mesh.metric.template sqrt_h_<1, 1>({}); @@ -285,7 +277,7 @@ namespace user { Kokkos::Experimental::contribute(domain.fields.buff, scatter_buff); } - const auto replenish_sdist = arch::ReplenishUniform( + const auto replenish_sdist = arch::spatial_dist::ReplenishUniform( domain.mesh.metric, domain.fields.buff, 0u, diff --git a/pgens/reconnection/sketch.png b/pgens/reconnection/sketch.png index 6b25615d3..1d0c76b65 100644 Binary files a/pgens/reconnection/sketch.png and b/pgens/reconnection/sketch.png differ diff --git a/pgens/shock/pgen.hpp b/pgens/shock/pgen.hpp index fc579777d..7a7aaac21 100644 --- a/pgens/shock/pgen.hpp +++ b/pgens/shock/pgen.hpp @@ -4,12 +4,11 @@ #include "enums.h" #include "global.h" -#include "arch/traits.h" +#include "traits/pgen.h" #include "utils/error.h" #include "utils/numeric.h" #include "archetypes/field_setter.h" -#include "archetypes/problem_generator.h" #include "archetypes/utils.h" #include "framework/domain/metadomain.h" @@ -67,20 +66,20 @@ namespace user { }; template - struct PGen : public arch::ProblemGenerator { + struct PGen { + static constexpr auto D { M::Dim }; // compatibility traits for the problem generator - static constexpr auto engines { traits::compatible_with::value }; - static constexpr auto metrics { traits::compatible_with::value }; + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; + static constexpr auto metrics { + ::traits::pgen::compatible_with {} + }; static constexpr auto dimensions { - traits::compatible_with::value + ::traits::pgen::compatible_with {} }; - - // for easy access to variables in the child class - using arch::ProblemGenerator::D; - using arch::ProblemGenerator::C; - using arch::ProblemGenerator::params; - - Metadomain& global_domain; + const SimulationParams& params; + Metadomain& metadomain; // domain properties const real_t global_xmin, global_xmax; @@ -93,37 +92,39 @@ namespace user { real_t Btheta, Bphi, Bmag; InitFields init_flds; - inline PGen(const SimulationParams& p, Metadomain& global_domain) - : arch::ProblemGenerator { p } - , global_domain { global_domain } - , global_xmin { global_domain.mesh().extent(in::x1).first } - , global_xmax { global_domain.mesh().extent(in::x1).second } - , drift_ux { p.template get("setup.drift_ux") } - , temperature { p.template get("setup.temperature") } - , temperature_ratio { p.template get("setup.temperature_ratio") } - , Bmag { p.template get("setup.Bmag", ZERO) } - , Btheta { p.template get("setup.Btheta", ZERO) } - , Bphi { p.template get("setup.Bphi", ZERO) } + PGen(const SimulationParams& p, Metadomain& m) + : params { p } + , metadomain { m } + , global_xmin { metadomain.mesh().extent(in::x1).first } + , global_xmax { metadomain.mesh().extent(in::x1).second } + , drift_ux { params.template get("setup.drift_ux") } + , temperature { params.template get("setup.temperature") } + , temperature_ratio { params.template get( + "setup.temperature_ratio") } + , Bmag { params.template get("setup.Bmag", ZERO) } + , Btheta { params.template get("setup.Btheta", ZERO) } + , Bphi { params.template get("setup.Bphi", ZERO) } , init_flds { Bmag, Btheta, Bphi, drift_ux } - , filling_fraction { p.template get("setup.filling_fraction", 1.0) } - , injector_velocity { p.template get("setup.injector_velocity", 1.0) } - , injection_start { p.template get("setup.injection_start", 0.0) } - , injection_frequency { p.template get("setup.injection_frequency", 100) } - , dt { p.template get("algorithms.timestep.dt") } {} - - inline PGen() {} - - auto MatchFields(real_t time) const -> InitFields { + , filling_fraction { params.template get("setup.filling_fraction", + 1.0) } + , injector_velocity { params.template get( + "setup.injector_velocity", + 1.0) } + , injection_start { params.template get("setup.injection_start", 0.0) } + , injection_frequency { params.template get( + "setup.injection_frequency", + 100) } + , dt { params.template get("algorithms.timestep.dt") } {} + + auto MatchFields(simtime_t) const -> InitFields { return init_flds; } - auto FixFieldsConst(const bc_in&, - const em& comp) const -> std::pair { + auto FixFieldsConst(const bc_in&, const em& comp) const + -> std::pair { if (comp == em::ex1) { return { init_flds.ex1({ ZERO }), true }; - } else if (comp == em::ex2) { - return { ZERO, true }; - } else if (comp == em::ex3) { + } else if ((comp == em::ex2) or (comp == em::ex3)) { return { ZERO, true }; } else if (comp == em::bx1) { return { init_flds.bx1({ ZERO }), true }; @@ -137,7 +138,7 @@ namespace user { } } - inline void InitPrtls(Domain& domain) { + void InitPrtls(Domain& domain) { /* * Plasma setup as partially filled box @@ -163,7 +164,7 @@ namespace user { for (auto d { 0u }; d < (unsigned int)M::Dim; ++d) { // compute the range for the x-direction if (d == static_cast(in::x1)) { - box.push_back({ xg_min, xg_max }); + box.emplace_back(xg_min, xg_max); } else { // inject into full range in other directions box.push_back(Range::All); @@ -231,7 +232,7 @@ namespace user { // define indice range to reset fields boundaries_t incl_ghosts; for (auto d = 0; d < M::Dim; ++d) { - incl_ghosts.push_back({ false, false }); + incl_ghosts.emplace_back(false, false); } // define box to reset fields @@ -239,14 +240,14 @@ namespace user { // loop over all dimension for (auto d = 0u; d < M::Dim; ++d) { if (d == 0) { - purge_box.push_back({ xmin, global_xmax }); + purge_box.emplace_back(xmin, global_xmax); } else { purge_box.push_back(Range::All); } } const auto extent = domain.mesh.ExtentToRange(purge_box, incl_ghosts); - tuple_t x_min { 0 }, x_max { 0 }; + tuple_t x_min { 0 }, x_max { 0 }; for (auto d = 0; d < M::Dim; ++d) { x_min[d] = extent[d].first; x_max[d] = extent[d].second; @@ -254,11 +255,11 @@ namespace user { Kokkos::parallel_for("ResetFields", CreateRangePolicy(x_min, x_max), - arch::SetEMFields_kernel { + arch::SetEMFields_kernel { domain.fields.em, init_flds, domain.mesh.metric }); - global_domain.CommunicateFields(domain, Comm::E | Comm::B); + metadomain.CommunicateFields(domain, Comm::E | Comm::B); /* tag particles inside the injection zone as dead @@ -276,7 +277,7 @@ namespace user { Kokkos::parallel_for( "RemoveParticles", species.rangeActiveParticles(), - Lambda(index_t p) { + Lambda(prtlidx_t p) { // check if the particle is already dead if (tag(p) == ParticleTag::dead) { return; @@ -301,7 +302,7 @@ namespace user { // loop over all dimension for (auto d = 0u; d < M::Dim; ++d) { if (d == 0) { - inj_box.push_back({ xmin, xmax }); + inj_box.emplace_back(xmin, xmax); } else { inj_box.push_back(Range::All); } diff --git a/pgens/shock/sketch.png b/pgens/shock/sketch.png index 9df562721..64ebce780 100644 Binary files a/pgens/shock/sketch.png and b/pgens/shock/sketch.png differ diff --git a/pgens/streaming/pgen.hpp b/pgens/streaming/pgen.hpp index dca6cc31d..6983d249d 100644 --- a/pgens/streaming/pgen.hpp +++ b/pgens/streaming/pgen.hpp @@ -5,14 +5,14 @@ #include "global.h" #include "arch/kokkos_aliases.h" -#include "arch/traits.h" +#include "traits/pgen.h" #include "utils/error.h" #include "utils/numeric.h" -#include "archetypes/problem_generator.h" #include "archetypes/utils.h" #include "framework/domain/domain.h" #include "framework/domain/metadomain.h" +#include "framework/parameters/parameters.h" namespace user { using namespace ntt; @@ -51,37 +51,39 @@ namespace user { }; template - struct PGen : public arch::ProblemGenerator { - + struct PGen { + static constexpr auto D { M::Dim }; // compatibility traits for the problem generator - static constexpr auto engines = traits::compatible_with::value; - static constexpr auto metrics = traits::compatible_with::value; + static constexpr auto engines = ::traits::pgen::compatible_with {}; + static constexpr auto metrics = + ::traits::pgen::compatible_with {}; static constexpr auto dimensions = - traits::compatible_with::value; + ::traits::pgen::compatible_with {}; - // for easy access to variables in the child class - using arch::ProblemGenerator::D; - using arch::ProblemGenerator::C; - using arch::ProblemGenerator::params; + const SimulationParams& params; - prmvec_t drifts_in_x, drifts_in_y, drifts_in_z; - prmvec_t densities, temperatures; + prmvec_t drifts_in_x, drifts_in_y, drifts_in_z; + prmvec_t densities, temperatures; // initial magnetic field real_t Btheta, Bphi, Bmag; InitFields init_flds; - inline PGen(const SimulationParams& p, const Metadomain& global_domain) - : arch::ProblemGenerator { p } - , drifts_in_x { p.template get("setup.drifts_in_x", prmvec_t {}) } - , drifts_in_y { p.template get("setup.drifts_in_y", prmvec_t {}) } - , drifts_in_z { p.template get("setup.drifts_in_z", prmvec_t {}) } - , Bmag { p.template get("setup.Bmag", ZERO) } - , Btheta { p.template get("setup.Btheta", ZERO) } - , Bphi { p.template get("setup.Bphi", ZERO) } + PGen(const SimulationParams& p, const Metadomain& global_domain) + : params { p } + , drifts_in_x { params.template get("setup.drifts_in_x", + prmvec_t {}) } + , drifts_in_y { params.template get("setup.drifts_in_y", + prmvec_t {}) } + , drifts_in_z { params.template get("setup.drifts_in_z", + prmvec_t {}) } + , Bmag { params.template get("setup.Bmag", ZERO) } + , Btheta { params.template get("setup.Btheta", ZERO) } + , Bphi { params.template get("setup.Bphi", ZERO) } , init_flds { Bmag, Btheta, Bphi } - , densities { p.template get("setup.densities", prmvec_t {}) } - , temperatures { p.template get("setup.temperatures", prmvec_t {}) } { - const auto nspec = p.template get("particles.nspec"); + , densities { params.template get("setup.densities", prmvec_t {}) } + , temperatures { params.template get("setup.temperatures", + prmvec_t {}) } { + const auto nspec = params.template get("particles.nspec"); raise::ErrorIf(nspec % 2 != 0, "Number of species must be even for this setup", HERE); @@ -115,7 +117,7 @@ namespace user { HERE); } - inline void InitPrtls(Domain& domain) { + void InitPrtls(Domain& domain) { const auto nspec = domain.species.size(); for (auto n = 0u; n < nspec; n += 2) { const auto drift_1 = prmvec_t { drifts_in_x[n], diff --git a/pgens/streaming/sketch.png b/pgens/streaming/sketch.png index a2cc66102..82ea02092 100644 Binary files a/pgens/streaming/sketch.png and b/pgens/streaming/sketch.png differ diff --git a/pgens/turbulence/pgen.hpp b/pgens/turbulence/pgen.hpp index dce59f029..45b802c64 100644 --- a/pgens/turbulence/pgen.hpp +++ b/pgens/turbulence/pgen.hpp @@ -5,12 +5,11 @@ #include "global.h" #include "arch/kokkos_aliases.h" +#include "traits/pgen.h" #include "utils/error.h" #include "utils/numeric.h" #include "archetypes/energy_dist.h" -#include "archetypes/particle_injector.h" -#include "archetypes/problem_generator.h" #include "archetypes/utils.h" #include "framework/domain/domain.h" #include "framework/domain/metadomain.h" @@ -107,7 +106,7 @@ namespace user { #endif // MPI_ENABLED return new_seed; } else { - return static_cast(seed); + return seed; } } @@ -169,15 +168,20 @@ namespace user { auto k_host = Kokkos::create_mirror_view(k); if constexpr (D == Dim::_2D) { for (auto i = 0u; i < n_modes; i++) { - k_host(0, i) = constant::TWO_PI * wavenumbers[i][0] / Lx; - k_host(1, i) = constant::TWO_PI * wavenumbers[i][1] / Ly; + k_host(0, i) = static_cast(constant::TWO_PI) * + wavenumbers[i][0] / Lx; + k_host(1, i) = static_cast(constant::TWO_PI) * + wavenumbers[i][1] / Ly; } } if constexpr (D == Dim::_3D) { for (auto i = 0u; i < n_modes; i++) { - k_host(0, i) = constant::TWO_PI * wavenumbers[i][0] / Lx; - k_host(1, i) = constant::TWO_PI * wavenumbers[i][1] / Ly; - k_host(2, i) = constant::TWO_PI * wavenumbers[i][2] / Lz; + k_host(0, i) = static_cast(constant::TWO_PI) * + wavenumbers[i][0] / Lx; + k_host(1, i) = static_cast(constant::TWO_PI) * + wavenumbers[i][1] / Ly; + k_host(2, i) = static_cast(constant::TWO_PI) * + wavenumbers[i][2] / Lz; } } // initializing initial complex amplitudes @@ -197,12 +201,13 @@ namespace user { auto k_perp = math::sqrt( k_host(0, i) * k_host(0, i) + k_host(1, i) * k_host(1, i)); real_t phase = static_cast(rand()) / - static_cast(RAND_MAX) * constant::TWO_PI; + static_cast(RAND_MAX) * + static_cast(constant::TWO_PI); A0_host(i) = dB / math::sqrt((real_t)n_modes) / k_perp * prefac; a_real_host(i) = A0_host(i) * math::cos(phase); a_imag_host(i) = A0_host(i) * math::sin(phase); phase = static_cast(rand()) / static_cast(RAND_MAX) * - constant::TWO_PI; + static_cast(constant::TWO_PI); a_imag_inv_host(i) = A0_host(i) * math::cos(phase); a_real_inv_host(i) = A0_host(i) * math::sin(phase); } @@ -290,17 +295,16 @@ namespace user { }; template - struct PGen : public arch::ProblemGenerator { - + struct PGen { + static constexpr auto D { M::Dim }; // compatibility traits for the problem generator - static constexpr auto engines = traits::compatible_with::value; - static constexpr auto metrics = traits::compatible_with::value; - static constexpr auto dimensions = traits::compatible_with::value; + static constexpr auto engines = ::traits::pgen::compatible_with {}; + static constexpr auto metrics = + ::traits::pgen::compatible_with {}; + static constexpr auto dimensions = + ::traits::pgen::compatible_with {}; - // for easy access to variables in the child class - using arch::ProblemGenerator::D; - using arch::ProblemGenerator::C; - using arch::ProblemGenerator::params; + const SimulationParams& params; const real_t temperature, dB, omega_0, gamma_0; const real_t Lx, Ly, Lz, escape_dist; @@ -316,14 +320,14 @@ namespace user { ExternalCurrent ext_current; InitFields init_flds; - inline PGen(const SimulationParams& p, const Metadomain& global_domain) - : arch::ProblemGenerator { p } - , temperature { p.template get("setup.temperature") } - , dB { p.template get("setup.dB", ONE) } - , omega_0 { p.template get("setup.omega_0") } - , gamma_0 { p.template get("setup.gamma_0") } + PGen(const SimulationParams& p, const Metadomain& global_domain) + : params { p } + , temperature { params.template get("setup.temperature") } + , dB { params.template get("setup.dB", ONE) } + , omega_0 { params.template get("setup.omega_0") } + , gamma_0 { params.template get("setup.gamma_0") } , wavenumbers { init_wavenumbers() } - , random_seed { p.template get("setup.seed", 0) } + , random_seed { params.template get("setup.seed", 0) } , random_pool { init_pool(random_seed) } , Lx { global_domain.mesh().extent(in::x1).second - global_domain.mesh().extent(in::x1).first } @@ -331,7 +335,7 @@ namespace user { global_domain.mesh().extent(in::x2).first } , Lz { global_domain.mesh().extent(in::x3).second - global_domain.mesh().extent(in::x3).first } - , escape_dist { p.template get("setup.escape_dist", HALF * Lx) } + , escape_dist { params.template get("setup.escape_dist", HALF * Lx) } , ext_current { dB, omega_0, gamma_0, wavenumbers, init_pool(random_seed), Lx, Ly, Lz } , init_flds { ext_current.k, @@ -340,7 +344,7 @@ namespace user { ext_current.a_real_inv, ext_current.a_imag_inv } {}; - inline void InitPrtls(Domain& domain) { + void InitPrtls(Domain& domain) { arch::InjectUniformMaxwellian(params, domain, ONE, temperature, { 1, 2 }); } @@ -355,7 +359,7 @@ namespace user { Kokkos::parallel_for( "Antenna amplitudes", wavenumbers.size(), - ClassLambda(index_t i) { + ClassLambda(cellidx_t i) { auto generator = random_pool.get_state(); const auto u_imag = Random(generator) - HALF; const auto u_real = Random(generator) - HALF; @@ -397,9 +401,9 @@ namespace user { }); // particle escape (resample velocities) - const auto energy_dist = arch::Maxwellian(domain.mesh.metric, - domain.random_pool(), - temperature); + const auto energy_dist = arch::energy_dist::Maxwellian( + domain.random_pool(), + temperature); for (const auto& sp : { 0, 1 }) { if (domain.species[sp].npld_r() > 1) { const auto& ux1 = domain.species[sp].ux1; @@ -411,7 +415,7 @@ namespace user { Kokkos::parallel_for( "UpdatePld", domain.species[sp].npart(), - Lambda(index_t p) { + Lambda(prtlidx_t p) { if (tag(p) == ParticleTag::dead) { return; } diff --git a/pgens/wald/pgen.hpp b/pgens/wald/pgen.hpp index 71ee905e3..de1b4fd71 100644 --- a/pgens/wald/pgen.hpp +++ b/pgens/wald/pgen.hpp @@ -5,23 +5,17 @@ #include "global.h" #include "arch/kokkos_aliases.h" -#include "arch/traits.h" +#include "traits/pgen.h" #include "utils/comparators.h" #include "utils/error.h" #include "utils/formatting.h" -#include "utils/log.h" #include "utils/numeric.h" -#include "archetypes/energy_dist.h" -#include "archetypes/particle_injector.h" -#include "archetypes/problem_generator.h" -#include "framework/domain/domain.h" #include "framework/domain/metadomain.h" #include -#include -enum InitFieldGeometry { +enum class InitFieldGeometry : uint8_t { Wald, Vertical, }; @@ -64,7 +58,8 @@ namespace user { TWO * metric.spin() * g_00); } - Inline auto bx1(const coord_t& x_Ph) const -> real_t { // at ( i , j + HALF ) + Inline auto bx1(const coord_t& x_Ph) const + -> real_t { // at ( i , j + HALF ) coord_t xi { ZERO }, x0m { ZERO }, x0p { ZERO }; metric.template convert(x_Ph, xi); @@ -82,7 +77,8 @@ namespace user { } } - Inline auto bx2(const coord_t& x_Ph) const -> real_t { // at ( i + HALF , j ) + Inline auto bx2(const coord_t& x_Ph) const + -> real_t { // at ( i + HALF , j ) coord_t xi { ZERO }, x0m { ZERO }, x0p { ZERO }; metric.template convert(x_Ph, xi); @@ -99,8 +95,8 @@ namespace user { } } - Inline auto bx3( - const coord_t& x_Ph) const -> real_t { // at ( i + HALF , j + HALF ) + Inline auto bx3(const coord_t& x_Ph) const + -> real_t { // at ( i + HALF , j + HALF ) if (field_geometry == InitFieldGeometry::Wald) { coord_t xi { ZERO }, x0m { ZERO }, x0p { ZERO }; metric.template convert(x_Ph, xi); @@ -120,7 +116,8 @@ namespace user { } } - Inline auto dx1(const coord_t& x_Ph) const -> real_t { // at ( i + HALF , j ) + Inline auto dx1(const coord_t& x_Ph) const + -> real_t { // at ( i + HALF , j ) if (field_geometry == InitFieldGeometry::Wald) { coord_t xi { ZERO }, x0m { ZERO }, x0p { ZERO }; metric.template convert(x_Ph, xi); @@ -158,7 +155,8 @@ namespace user { } } - Inline auto dx2(const coord_t& x_Ph) const -> real_t { // at ( i , j + HALF ) + Inline auto dx2(const coord_t& x_Ph) const + -> real_t { // at ( i , j + HALF ) if (field_geometry == InitFieldGeometry::Wald) { coord_t xi { ZERO }, x0m { ZERO }, x0p { ZERO }; metric.template convert(x_Ph, xi); @@ -230,26 +228,21 @@ namespace user { }; template - struct PGen : public arch::ProblemGenerator { + struct PGen { + static constexpr auto D { M::Dim }; // compatibility traits for the problem generator - static constexpr auto engines { traits::compatible_with::value }; + static constexpr auto engines { + ::traits::pgen::compatible_with {} + }; static constexpr auto metrics { - traits::compatible_with::value + ::traits::pgen::compatible_with {} }; - static constexpr auto dimensions { traits::compatible_with::value }; - - // for easy access to variables in the child class - using arch::ProblemGenerator::D; - using arch::ProblemGenerator::C; - using arch::ProblemGenerator::params; + static constexpr auto dimensions { ::traits::pgen::compatible_with {} }; - InitFields init_flds; - const Metadomain& global_domain; + InitFields init_flds; - inline PGen(const SimulationParams& p, const Metadomain& m) - : arch::ProblemGenerator { p } - , global_domain { m } - , init_flds { m.mesh().metric, + PGen(const SimulationParams& p, const Metadomain& m) + : init_flds { m.mesh().metric, p.template get("setup.init_field", "wald") } {} }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 31114c330..1d293e8db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,20 +24,12 @@ set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) -# dependencies -add_subdirectory(${SRC_DIR}/global ${CMAKE_CURRENT_BINARY_DIR}/global) -add_subdirectory(${SRC_DIR}/metrics ${CMAKE_CURRENT_BINARY_DIR}/metrics) -add_subdirectory(${SRC_DIR}/kernels ${CMAKE_CURRENT_BINARY_DIR}/kernels) -add_subdirectory(${SRC_DIR}/archetypes ${CMAKE_CURRENT_BINARY_DIR}/archetypes) -add_subdirectory(${SRC_DIR}/framework ${CMAKE_CURRENT_BINARY_DIR}/framework) -add_subdirectory(${SRC_DIR}/engines ${CMAKE_CURRENT_BINARY_DIR}/engines) -add_subdirectory(${SRC_DIR}/output ${CMAKE_CURRENT_BINARY_DIR}/output) - -set(ENTITY ${PROJECT_NAME}.xc) +set(ENTITY ${PROJECT_NAME}${pgen_suffix}.xc) set(SOURCES ${SRC_DIR}/entity.cpp) add_executable(${ENTITY} ${SOURCES}) -set(libs ntt_global ntt_framework ntt_metrics ntt_engines ntt_pgen) +set(libs ntt_global ntt_framework ntt_metrics ntt_engines${pgen_suffix} + ntt_pgen${pgen_suffix}) add_dependencies(${ENTITY} ${libs}) target_link_libraries(${ENTITY} PUBLIC ${libs}) install(TARGETS ${ENTITY} DESTINATION bin) diff --git a/src/archetypes/emission.h b/src/archetypes/emission.h new file mode 100644 index 000000000..f6a9240c1 --- /dev/null +++ b/src/archetypes/emission.h @@ -0,0 +1,492 @@ +/** + * @file archetypes/emission.h + * @brief Emission policy archetypes for radiative processes in particle pushers + * @implements + * - arch::EmissionSynchrotron<> + * - arch::EmissionCompton<> + * @namespaces: + * - arch:: + */ + +#ifndef ARCHETYPES_EMISSION_HPP +#define ARCHETYPES_EMISSION_HPP + +#include "enums.h" +#include "global.h" + +#include "traits/metric.h" +#include "utils/param_container.h" + +#include "framework/containers/particles.h" +#include "kernels/injectors.hpp" + +#include + +#include + +namespace arch { + using namespace ntt; + + template + struct EmissionSynchrotron { + struct Payload { + real_t photon_energy; + }; + + const spidx_t emitted_index; + const real_t species_mass; + const real_t emitted_photon_weight; + const real_t emitted_photon_min_energy; + + const real_t nominal_probability; + const real_t nominal_photon_energy; + + const bool should_drag; + + array_t photon_i1, photon_i2, photon_i3; + array_t photon_dx1, photon_dx2, photon_dx3; + array_t photon_ux1, photon_ux2, photon_ux3; + array_t photon_phi; + array_t photon_weight; + array_t photon_tag; + array_t photon_pld_i; + + array_t photon_idx { "idx" }; + const npart_t photon_offset, photon_cntr, domain_idx; + const bool photon_use_tracking; + + random_number_pool_t random_pool; + + EmissionSynchrotron(Particles& photon_species, + spidx_t emitted_index, + float sp_mass, + float sp_charge, + RadiativeDragFlags radiative_drag_flags, + npart_t domain_idx, + const prm::Parameters& params, + random_number_pool_t& random_pool) + : emitted_index { emitted_index } + , species_mass { static_cast(sp_mass) } + , emitted_photon_weight { params.template get( + "radiation.emission.synchrotron.photon_weight") } + , emitted_photon_min_energy { params.template get( + "radiation.emission.synchrotron.photon_energy_min") } + , nominal_probability { math::abs(sp_charge / species_mass) * + params.template get( + "radiation.emission.synchrotron.nominal_" + "probability") } + , nominal_photon_energy { species_mass * + params.template get( + "radiation.emission.synchrotron." + "nominal_photon_energy") } + , should_drag { static_cast(radiative_drag_flags & + RadiativeDrag::SYNCHROTRON) } + , photon_i1 { photon_species.i1 } + , photon_i2 { photon_species.i2 } + , photon_i3 { photon_species.i3 } + , photon_dx1 { photon_species.dx1 } + , photon_dx2 { photon_species.dx2 } + , photon_dx3 { photon_species.dx3 } + , photon_ux1 { photon_species.ux1 } + , photon_ux2 { photon_species.ux2 } + , photon_ux3 { photon_species.ux3 } + , photon_phi { photon_species.phi } + , photon_weight { photon_species.weight } + , photon_tag { photon_species.tag } + , photon_pld_i { photon_species.pld_i } + , photon_offset { photon_species.npart() } + , photon_cntr { photon_species.counter() } + , domain_idx { domain_idx } + , photon_use_tracking { photon_species.use_tracking() } + , random_pool { random_pool } {} + + auto emitted_species_indices() const -> std::vector { + return { emitted_index }; + } + + auto numbers_injected() const -> std::vector { + auto photon_idx_h = Kokkos::create_mirror_view(photon_idx); + Kokkos::deep_copy(photon_idx_h, photon_idx); + return { photon_idx_h() }; + } + + /** + * + * @brief Determine whether a photon is emitted, the drag is applied, + * and compute its energy and the recoil on the emitting particle + * + * @param x_Cd Position of the particle (code) + * @param x_Ph Position of the particle (physical) + * @param u_Ph Velocity of the particle (physical) + * @param ep Interpolated electric field at the particle position (physical, units of B0) + * @param bp Interpolated magnetic field at the particle position (physical, units of B0) + * @param delta_u_Ph Output parameter for the recoil on the emitting particle (physical units) + * + * @note + * + * probability at each timestep is: + * nominal_probability = omegaB0 * etarec * dt * (gamma_QED / gamma_rad)^2 / photon_weight + * kappaR = (e + beta x b) x b + (beta . e) e + * chiR^2 = (e + beta x b)^2 - (beta . e)^2 + * p_gamma = (q / q0) / (m / m0) * nominal_probability * (-kappaR . beta_hat / gamma^2 + beta chiR^2) + * + * mean energy of the emitted photon [units of m0 c^2]: + * nominal_photon_energy = (1 / gamma_QED)^2 + * e_gamma = (gamma)^2 * (m / m0) * nominal_photon_energy + * + * drag force [in units of m c]: + * du / dt = - photon_weight * p_gamma * e_gamma * u_hat + * + * @returns Pair of booleans to indicate whether a particle should be emitted + * and whether the emitting particle should experience a recoil (i.e. radiative drag) + * + */ + Inline auto shouldEmit(const coord_t&, + const coord_t&, + const vec_t& u_Ph, + const vec_t& ep_Ph, + const vec_t& bp_Ph, + vec_t& delta_u_Ph, + Payload& payload) const -> Kokkos::pair { + const auto u_sqr = NORM_SQR(u_Ph[0], u_Ph[1], u_Ph[2]); + const auto u_mag = math::sqrt(u_sqr); + const auto gamma_sqr = ONE + u_sqr; + const auto gamma = math::sqrt(gamma_sqr); + const auto beta = u_mag / gamma; + + const auto e_plus_beta_cross_b_x1 = + ep_Ph[0] + + CROSS_x1(u_Ph[0], u_Ph[1], u_Ph[2], bp_Ph[0], bp_Ph[1], bp_Ph[2]) / gamma; + const auto e_plus_beta_cross_b_x2 = + ep_Ph[1] + + CROSS_x2(u_Ph[0], u_Ph[1], u_Ph[2], bp_Ph[0], bp_Ph[1], bp_Ph[2]) / gamma; + const auto e_plus_beta_cross_b_x3 = + ep_Ph[2] + + CROSS_x3(u_Ph[0], u_Ph[1], u_Ph[2], bp_Ph[0], bp_Ph[1], bp_Ph[2]) / gamma; + const auto beta_dot_e = + DOT(u_Ph[0], u_Ph[1], u_Ph[2], ep_Ph[0], ep_Ph[1], ep_Ph[2]) / gamma; + + const auto kappaR_x1 = CROSS_x1(e_plus_beta_cross_b_x1, + e_plus_beta_cross_b_x2, + e_plus_beta_cross_b_x3, + bp_Ph[0], + bp_Ph[1], + bp_Ph[2]) + + beta_dot_e * ep_Ph[0]; + const auto kappaR_x2 = CROSS_x2(e_plus_beta_cross_b_x1, + e_plus_beta_cross_b_x2, + e_plus_beta_cross_b_x3, + bp_Ph[0], + bp_Ph[1], + bp_Ph[2]) + + beta_dot_e * ep_Ph[1]; + const auto kappaR_x3 = CROSS_x3(e_plus_beta_cross_b_x1, + e_plus_beta_cross_b_x2, + e_plus_beta_cross_b_x3, + bp_Ph[0], + bp_Ph[1], + bp_Ph[2]) + + beta_dot_e * ep_Ph[2]; + const auto chiR_sqr = NORM_SQR(e_plus_beta_cross_b_x1, + e_plus_beta_cross_b_x2, + e_plus_beta_cross_b_x3) - + SQR(beta_dot_e); + + const auto probability = + nominal_probability * + (-DOT(kappaR_x1, kappaR_x2, kappaR_x3, u_Ph[0], u_Ph[1], u_Ph[2]) / + (gamma_sqr * u_mag) + + beta * chiR_sqr); + + const auto dir_x1 = -kappaR_x1 + gamma * u_Ph[0] * chiR_sqr; + const auto dir_x2 = -kappaR_x2 + gamma * u_Ph[1] * chiR_sqr; + const auto dir_x3 = -kappaR_x3 + gamma * u_Ph[2] * chiR_sqr; + + payload.photon_energy = gamma_sqr * nominal_photon_energy; + + const auto delta_u = -emitted_photon_weight * payload.photon_energy / + (NORM(dir_x1, dir_x2, dir_x3) * species_mass); + + delta_u_Ph[0] = delta_u * dir_x1; + delta_u_Ph[1] = delta_u * dir_x2; + delta_u_Ph[2] = delta_u * dir_x3; + + auto rand_gen = random_pool.get_state(); + // should not emit if photon energy is above 20% of (gamma - 1) m c^2 + const auto should_emit = (Random(rand_gen) < probability) and + (payload.photon_energy < + species_mass * (gamma - ONE) * + static_cast(0.2)); + random_pool.free_state(rand_gen); + + return Kokkos::make_pair( + should_emit and (payload.photon_energy >= emitted_photon_min_energy), + should_drag and should_emit); + } + + Inline void emit(const tuple_t& xi_Cd, + const tuple_t& dxi_Cd, + const vec_t& direction, + real_t weight, + real_t phi, + const Payload& payload) const { + const auto index = Kokkos::atomic_fetch_add(&photon_idx(), 1); + if (not photon_use_tracking) { + kernel::InjectParticle( + photon_offset + index, + photon_i1, + photon_i2, + photon_i3, + photon_dx1, + photon_dx2, + photon_dx3, + photon_ux1, + photon_ux2, + photon_ux3, + photon_phi, + photon_weight, + photon_tag, + photon_pld_i, + xi_Cd, + dxi_Cd, + { direction[0] * payload.photon_energy, + direction[1] * payload.photon_energy, + direction[2] * payload.photon_energy }, + emitted_photon_weight * weight, + phi); + } else { + kernel::InjectParticle( + photon_offset + index, + photon_i1, + photon_i2, + photon_i3, + photon_dx1, + photon_dx2, + photon_dx3, + photon_ux1, + photon_ux2, + photon_ux3, + photon_phi, + photon_weight, + photon_tag, + photon_pld_i, + xi_Cd, + dxi_Cd, + { direction[0] * payload.photon_energy, + direction[1] * payload.photon_energy, + direction[2] * payload.photon_energy }, + emitted_photon_weight * weight, + phi, + domain_idx, + photon_cntr + index); + } + } + }; + + template + struct EmissionCompton { + struct Payload { + real_t photon_energy; + }; + + const spidx_t emitted_index; + const real_t species_mass; + const real_t emitted_photon_weight; + const real_t emitted_photon_min_energy; + + const real_t nominal_probability; + const real_t nominal_photon_energy; + + const bool should_drag; + + array_t photon_i1, photon_i2, photon_i3; + array_t photon_dx1, photon_dx2, photon_dx3; + array_t photon_ux1, photon_ux2, photon_ux3; + array_t photon_phi; + array_t photon_weight; + array_t photon_tag; + array_t photon_pld_i; + + array_t photon_idx { "idx" }; + const npart_t photon_offset, photon_cntr, domain_idx; + const bool photon_use_tracking; + + random_number_pool_t random_pool; + + EmissionCompton(Particles& photon_species, + spidx_t emitted_index, + float sp_mass, + float sp_charge, + RadiativeDragFlags radiative_drag_flags, + npart_t domain_idx, + const prm::Parameters& params, + random_number_pool_t& random_pool) + : emitted_index { emitted_index } + , species_mass { static_cast(sp_mass) } + , emitted_photon_weight { params.template get( + "radiation.emission.compton.photon_weight") } + , emitted_photon_min_energy { params.template get( + "radiation.emission.compton.photon_energy_min") } + , nominal_probability { math::abs(sp_charge / species_mass) * + params.template get( + "radiation.emission.compton.nominal_" + "probability") } + , nominal_photon_energy { species_mass * params.template get( + "radiation.emission.compton." + "nominal_photon_energy") } + , should_drag { static_cast(radiative_drag_flags & + RadiativeDrag::COMPTON) } + , photon_i1 { photon_species.i1 } + , photon_i2 { photon_species.i2 } + , photon_i3 { photon_species.i3 } + , photon_dx1 { photon_species.dx1 } + , photon_dx2 { photon_species.dx2 } + , photon_dx3 { photon_species.dx3 } + , photon_ux1 { photon_species.ux1 } + , photon_ux2 { photon_species.ux2 } + , photon_ux3 { photon_species.ux3 } + , photon_phi { photon_species.phi } + , photon_weight { photon_species.weight } + , photon_tag { photon_species.tag } + , photon_pld_i { photon_species.pld_i } + , photon_offset { photon_species.npart() } + , photon_cntr { photon_species.counter() } + , domain_idx { domain_idx } + , photon_use_tracking { photon_species.use_tracking() } + , random_pool { random_pool } {} + + auto emitted_species_indices() const -> std::vector { + return { emitted_index }; + } + + auto numbers_injected() const -> std::vector { + auto photon_idx_h = Kokkos::create_mirror_view(photon_idx); + Kokkos::deep_copy(photon_idx_h, photon_idx); + return { photon_idx_h() }; + } + + /** + * + * @brief Determine whether a photon is emitted, the drag is applied, + * and compute its energy and the recoil on the emitting particle + * + * @param x_Cd Position of the particle (code) + * @param x_Ph Position of the particle (physical) + * @param u_Ph Velocity of the particle (physical) + * @param ep Interpolated electric field at the particle position (physical) + * @param bp Interpolated magnetic field at the particle position (physical) + * @param delta_u_Ph Output parameter for the recoil on the emitting particle (physical units) + * + * @note + * + * probability at each timestep is: + * nominal_probability = omegaB0 * etarec * dt * (gamma_QED / gamma_rad)^2 / photon_weight + * p_gamma = (q / q0) / (m / m0) * beta * nominal_probability + * + * mean energy of the emitted photon [units of m0 c^2]: + * nominal_photon_energy = (1 / gamma_QED)^2 + * e_gamma = (gamma)^2 * (m / m0) * nominal_photon_energy + * + * drag acceleration * dt: + * du = - photon_weight * p_gamma * e_gamma * u_hat / (m / m0) + * + * @returns Pair of booleans to indicate whether a particle should be emitted + * and whether the emitting particle should experience a recoil (i.e. radiative drag) + * + */ + Inline auto shouldEmit(const coord_t&, + const coord_t&, + const vec_t& u_Ph, + const vec_t&, + const vec_t&, + vec_t& delta_u_Ph, + Payload& payload) const -> Kokkos::pair { + const auto u_sqr = NORM_SQR(u_Ph[0], u_Ph[1], u_Ph[2]); + const auto gamma_sqr = ONE + u_sqr; + + payload.photon_energy = gamma_sqr * nominal_photon_energy; + + const auto delta_u = -emitted_photon_weight * payload.photon_energy / + (math::sqrt(u_sqr) * species_mass); + + delta_u_Ph[0] = delta_u * u_Ph[0]; + delta_u_Ph[1] = delta_u * u_Ph[1]; + delta_u_Ph[2] = delta_u * u_Ph[2]; + + auto rand_gen = random_pool.get_state(); + // should not emit if photon energy is above 20% of (gamma - 1) m c^2 + const auto should_emit = (Random(rand_gen) < + (nominal_probability * + math::sqrt(u_sqr / gamma_sqr))) and + (payload.photon_energy < + species_mass * (math::sqrt(gamma_sqr) - ONE) * + static_cast(0.2)); + random_pool.free_state(rand_gen); + + return Kokkos::make_pair( + should_emit and (payload.photon_energy >= emitted_photon_min_energy), + should_drag and should_emit); + } + + Inline void emit(const tuple_t& xi_Cd, + const tuple_t& dxi_Cd, + const vec_t& direction, + real_t weight, + real_t phi, + const Payload& payload) const { + const auto index = Kokkos::atomic_fetch_add(&photon_idx(), 1); + if (not photon_use_tracking) { + kernel::InjectParticle( + photon_offset + index, + photon_i1, + photon_i2, + photon_i3, + photon_dx1, + photon_dx2, + photon_dx3, + photon_ux1, + photon_ux2, + photon_ux3, + photon_phi, + photon_weight, + photon_tag, + photon_pld_i, + xi_Cd, + dxi_Cd, + { direction[0] * payload.photon_energy, + direction[1] * payload.photon_energy, + direction[2] * payload.photon_energy }, + emitted_photon_weight * weight, + phi); + } else { + kernel::InjectParticle( + photon_offset + index, + photon_i1, + photon_i2, + photon_i3, + photon_dx1, + photon_dx2, + photon_dx3, + photon_ux1, + photon_ux2, + photon_ux3, + photon_phi, + photon_weight, + photon_tag, + photon_pld_i, + xi_Cd, + dxi_Cd, + { direction[0] * payload.photon_energy, + direction[1] * payload.photon_energy, + direction[2] * payload.photon_energy }, + emitted_photon_weight * weight, + phi, + domain_idx, + photon_cntr + index); + } + } + }; + +} // namespace arch + +#endif // ARCHETYPES_EMISSION_HPP \ No newline at end of file diff --git a/src/archetypes/energy_dist.h b/src/archetypes/energy_dist.h index 56a797751..11fe04369 100644 --- a/src/archetypes/energy_dist.h +++ b/src/archetypes/energy_dist.h @@ -1,18 +1,12 @@ /** - * @file archetypes/energy_dist.hpp + * @file archetypes/energy_dist.h * @brief Defines an archetype for energy distributions * @implements - * - arch::EnergyDistribution<> - * - arch::Cold<> : arch::EnergyDistribution<> - * - arch::Powerlaw<> : arch::EnergyDistribution<> - * - arch::Maxwellian<> : arch::EnergyDistribution<> + * - arch::energy_dist::Cold<> + * - arch::energy_dist::Powerlaw<> + * - arch::energy_dist::Maxwellian<> * @namespaces: - * - arch:: - * @note - * The class returns a random velocity according to a coded distribution - * For Cartesian: the returned velocity is in the global Cartesian basis - * For non-Cartesian SR: the returned velocity is in the tetrad basis - * For GR: the returned velocity is in the covariant basis + * - arch::energy_dist:: */ #ifndef ARCHETYPES_ENERGY_DIST_HPP @@ -29,26 +23,12 @@ #include #include -namespace arch { +namespace arch::energy_dist { using namespace ntt; - template - struct EnergyDistribution { - static constexpr auto D = M::Dim; - static constexpr bool is_energy_dist { true }; - static_assert(M::is_metric, "M must be a metric class"); - - EnergyDistribution(const M& metric) : metric { metric } {} - - protected: - const M metric; - }; - - template - struct Cold : public EnergyDistribution { - Cold(const M& metric) : EnergyDistribution { metric } {} - - Inline void operator()(const coord_t&, vec_t& v) const { + template + struct Cold { + Inline void operator()(const coord_t&, vec_t& v) const { v[0] = ZERO; v[1] = ZERO; @@ -56,22 +36,16 @@ namespace arch { } }; - template - struct Powerlaw : public EnergyDistribution { - using EnergyDistribution::metric; + template + struct Powerlaw { - Powerlaw(const M& metric, - random_number_pool_t& pool, - real_t g_min, - real_t g_max, - real_t pl_ind) - : EnergyDistribution { metric } - , g_min { g_min } + Powerlaw(random_number_pool_t& pool, real_t g_min, real_t g_max, real_t pl_ind) + : g_min { g_min } , g_max { g_max } , pl_ind { pl_ind } , pool { pool } {} - Inline void operator()(const coord_t&, vec_t& v) const { + Inline void operator()(const coord_t&, vec_t& v) const { auto rand_gen = pool.get_state(); auto rand_X1 = Random(rand_gen); auto rand_gam = ONE; @@ -91,8 +65,8 @@ namespace arch { auto rand_X3 = Random(rand_gen); v[0] = rand_u * (TWO * rand_X2 - ONE); v[2] = TWO * rand_u * math::sqrt(rand_X2 * (ONE - rand_X2)); - v[1] = v[2] * math::cos(constant::TWO_PI * rand_X3); - v[2] = v[2] * math::sin(constant::TWO_PI * rand_X3); + v[1] = v[2] * math::cos(static_cast(constant::TWO_PI) * rand_X3); + v[2] = v[2] * math::sin(static_cast(constant::TWO_PI) * rand_X3); pool.free_state(rand_gen); } @@ -114,7 +88,7 @@ namespace arch { randX1 = Random(rand_gen); } randX1 = math::sqrt(-TWO * math::log(randX1)); - randX2 = constant::TWO_PI * Random(rand_gen); + randX2 = static_cast(constant::TWO_PI) * Random(rand_gen); v[0] = randX1 * math::cos(randX2) * math::sqrt(temp); randX1 = Random(rand_gen); @@ -122,7 +96,7 @@ namespace arch { randX1 = Random(rand_gen); } randX1 = math::sqrt(-TWO * math::log(randX1)); - randX2 = constant::TWO_PI * Random(rand_gen); + randX2 = static_cast(constant::TWO_PI) * Random(rand_gen); v[1] = randX1 * math::cos(randX2) * math::sqrt(temp); randX1 = Random(rand_gen); @@ -130,7 +104,7 @@ namespace arch { randX1 = Random(rand_gen); } randX1 = math::sqrt(-TWO * math::log(randX1)); - randX2 = constant::TWO_PI * Random(rand_gen); + randX2 = static_cast(constant::TWO_PI) * Random(rand_gen); v[2] = randX1 * math::cos(randX2) * math::sqrt(temp); } else { // Juttner-Synge distribution using the Sobol method - relativistic @@ -154,13 +128,13 @@ namespace arch { randX2 = Random(rand_gen); v[0] = randu * (TWO * randX1 - ONE); v[2] = TWO * randu * math::sqrt(randX1 * (ONE - randX1)); - v[1] = v[2] * math::cos(constant::TWO_PI * randX2); - v[2] = v[2] * math::sin(constant::TWO_PI * randX2); + v[1] = v[2] * math::cos(static_cast(constant::TWO_PI) * randX2); + v[2] = v[2] * math::sin(static_cast(constant::TWO_PI) * randX2); } pool.free_state(rand_gen); } - template + template Inline void SampleFromMaxwellian(vec_t& v, const random_number_pool_t& pool, real_t temperature, @@ -199,16 +173,12 @@ namespace arch { } } - template - struct Maxwellian : public EnergyDistribution { - using EnergyDistribution::metric; - - Maxwellian(const M& metric, - random_number_pool_t& pool, + template + struct Maxwellian { + Maxwellian(random_number_pool_t& pool, real_t temperature, const std::vector& drift_four_vel = { ZERO, ZERO, ZERO }) - : EnergyDistribution { metric } - , pool { pool } + : pool { pool } , temperature { temperature } { raise::ErrorIf(drift_four_vel.size() != 3, "Maxwellian: Drift velocity must be a 3D vector", @@ -216,7 +186,7 @@ namespace arch { raise::ErrorIf(temperature < ZERO, "Maxwellian: Temperature must be non-negative", HERE); - if constexpr (M::CoordType == Coord::Cart) { + if constexpr (C == Coord::Cartesian) { drift_4vel = NORM(drift_four_vel[0], drift_four_vel[1], drift_four_vel[2]); if (cmp::AlmostZero_host(drift_4vel)) { drift_dir = 0; @@ -234,7 +204,7 @@ namespace arch { const auto dnext = (d + 1) % 3; if (cmp::AlmostZero_host(drift_four_vel[dprev]) and cmp::AlmostZero_host(drift_four_vel[dnext])) { - drift_dir = SIGN(drift_four_vel[d]) * (d + 1); + drift_dir = SIGN(drift_four_vel[d]) * (static_cast(d + 1)); break; } } @@ -243,13 +213,13 @@ namespace arch { "Maxwellian: Incorrect drift direction", HERE); raise::ErrorIf( - drift_dir != 0 and (M::CoordType != Coord::Cart), + drift_dir != 0 and (C != Coord::Cartesian), "Maxwellian: Boosting is only supported in Cartesian coordinates", HERE); } } - Inline void operator()(const coord_t& x_Code, vec_t& v) const { + Inline void operator()(const coord_t&, vec_t& v) const { if (cmp::AlmostZero(temperature)) { v[0] = ZERO; v[1] = ZERO; @@ -258,7 +228,7 @@ namespace arch { JuttnerSinge(v, temperature, pool); } // @note: boost only when using cartesian coordinates - if constexpr (M::CoordType == Coord::Cart) { + if constexpr (C == Coord::Cartesian) { if (drift_dir != 0) { // Boost an isotropic Maxwellian with a drift velocity using // flipping method https://arxiv.org/pdf/1504.03910.pdf @@ -321,6 +291,6 @@ namespace arch { short drift_dir { 0 }; }; -} // namespace arch +} // namespace arch::energy_dist #endif // ARCHETYPES_ENERGY_DIST_HPP diff --git a/src/archetypes/field_setter.h b/src/archetypes/field_setter.h index 5c5c4dbe4..1e3137d01 100644 --- a/src/archetypes/field_setter.h +++ b/src/archetypes/field_setter.h @@ -1,19 +1,21 @@ /** - * @file archetypes/field_setter.hpp - * @brief Defines a kernel which populates the EM fields with user-defined values + * @file archetypes/field_setter.h + * @brief Defines kernels which populate the EM fields with user-defined values + * conditionally or unconditionally * @implements - * - arch::SetEMFields_kernel + * - arch::SetEMFields_kernel<> + * - arch::CustomSetEMFields_kernel<> * @namespaces: * - arch:: * @note - * The functor accepts a class I as a template argument, which must contain + * The functors accept a class F as a template argument, which must contain * one of the ex1, ex2, ex3, bx1, bx2, bx3 methods (dx1, dx2, dx3 for GR). * * `coord_t` --> [ I ] --> `real_t` --> [ SetEMFields_kernel ] --> ... * * ^ ^ ^ - * * (physical) (SR: hatted basis ) (SR & GR: cntrv) + * * (physical) (SR: tetrad basis ) (SR & GR: cntrv) * * (GR: phys. cntrv ) * @note - * The functor automatically takes care of the staggering, ghost cells and + * The functors automatically take care of the staggering, ghost cells and * conversion to contravariant basis. */ @@ -24,7 +26,8 @@ #include "global.h" #include "arch/kokkos_aliases.h" -#include "arch/traits.h" +#include "traits/archetypes.h" +#include "traits/metric.h" #include "utils/numeric.h" #include @@ -32,77 +35,67 @@ namespace arch { using namespace ntt; - template + template FS> class SetEMFields_kernel { - static constexpr Dimension D = M::Dim; - static constexpr bool defines_ex1 = traits::has_method::value; - static constexpr bool defines_ex2 = traits::has_method::value; - static constexpr bool defines_ex3 = traits::has_method::value; - static constexpr bool defines_bx1 = traits::has_method::value; - static constexpr bool defines_bx2 = traits::has_method::value; - static constexpr bool defines_bx3 = traits::has_method::value; - static constexpr bool defines_dx1 = traits::has_method::value; - static constexpr bool defines_dx2 = traits::has_method::value; - static constexpr bool defines_dx3 = traits::has_method::value; - - static_assert(defines_ex1 || defines_ex2 || defines_ex3 || defines_bx1 || - defines_bx2 || defines_bx3 || defines_dx1 || defines_dx2 || - defines_dx3, - "No field initializer defined"); - static_assert((S != SimEngine::GRPIC) || - (defines_dx1 == defines_dx2 && defines_dx2 == defines_dx3 && - defines_bx1 == defines_bx2 && defines_bx2 == defines_bx3), - "In GR mode, all components must be defined or none"); - static_assert(M::is_metric, "M must be a metric class"); + static constexpr auto D = M::Dim; + static constexpr auto HasEx1 = ::traits::fieldsetter::HasEx1; + static constexpr auto HasEx2 = ::traits::fieldsetter::HasEx2; + static constexpr auto HasEx3 = ::traits::fieldsetter::HasEx3; + static constexpr auto HasBx1 = ::traits::fieldsetter::HasBx1; + static constexpr auto HasBx2 = ::traits::fieldsetter::HasBx2; + static constexpr auto HasBx3 = ::traits::fieldsetter::HasBx3; + static constexpr auto HasDx1 = ::traits::fieldsetter::HasDx1; + static constexpr auto HasDx2 = ::traits::fieldsetter::HasDx2; + static constexpr auto HasDx3 = ::traits::fieldsetter::HasDx3; ndfield_t EM; - const I finit; + const FS finit; const M metric; public: - SetEMFields_kernel(ndfield_t& EM, const I& finit, const M& metric) + SetEMFields_kernel(ndfield_t& EM, const FS& finit, const M& metric) : EM { EM } , finit { finit } , metric { metric } {} ~SetEMFields_kernel() = default; - Inline void operator()(index_t i1) const { + Inline void operator()(cellidx_t i1) const { if constexpr (D == Dim::_1D) { const auto i1_ = COORD(i1); coord_t x_Phys { ZERO }; if constexpr (S == SimEngine::SRPIC) { - if constexpr (defines_ex1) { + if constexpr (HasEx1) { metric.template convert({ i1_ + HALF }, x_Phys); EM(i1, em::ex1) = metric.template transform<1, Idx::T, Idx::U>( { i1_ + HALF }, finit.ex1(x_Phys)); } - if constexpr (defines_ex2) { + if constexpr (HasEx2) { metric.template convert({ i1_ }, x_Phys); EM(i1, em::ex2) = metric.template transform<2, Idx::T, Idx::U>( { i1_ }, finit.ex2(x_Phys)); } - if constexpr (defines_ex3) { + if constexpr (HasEx3) { metric.template convert({ i1_ }, x_Phys); EM(i1, em::ex3) = metric.template transform<3, Idx::T, Idx::U>( { i1_ }, finit.ex3(x_Phys)); } - if constexpr (defines_bx1) { + if constexpr (HasBx1) { metric.template convert({ i1_ }, x_Phys); EM(i1, em::bx1) = metric.template transform<1, Idx::T, Idx::U>( { i1_ }, finit.bx1(x_Phys)); } - if constexpr (defines_bx2) { + if constexpr (HasBx2) { metric.template convert({ i1_ + HALF }, x_Phys); EM(i1, em::bx2) = metric.template transform<2, Idx::T, Idx::U>( { i1_ + HALF }, finit.bx2(x_Phys)); } - if constexpr (defines_bx3) { + if constexpr (HasBx3) { metric.template convert({ i1_ + HALF }, x_Phys); EM(i1, em::bx3) = metric.template transform<3, Idx::T, Idx::U>( { i1_ + HALF }, @@ -116,44 +109,44 @@ namespace arch { } } - Inline void operator()(index_t i1, index_t i2) const { + Inline void operator()(cellidx_t i1, cellidx_t i2) const { if constexpr (D == Dim::_2D) { const auto i1_ = COORD(i1); const auto i2_ = COORD(i2); // srpic if constexpr (S == SimEngine::SRPIC) { coord_t x_Phys { ZERO }; - if constexpr (defines_ex1) { + if constexpr (HasEx1) { metric.template convert({ i1_ + HALF, i2_ }, x_Phys); EM(i1, i2, em::ex1) = metric.template transform<1, Idx::T, Idx::U>( { i1_ + HALF, i2_ }, finit.ex1(x_Phys)); } - if constexpr (defines_ex2) { + if constexpr (HasEx2) { metric.template convert({ i1_, i2_ + HALF }, x_Phys); EM(i1, i2, em::ex2) = metric.template transform<2, Idx::T, Idx::U>( { i1_, i2_ + HALF }, finit.ex2(x_Phys)); } - if constexpr (defines_ex3) { + if constexpr (HasEx3) { metric.template convert({ i1_, i2_ }, x_Phys); EM(i1, i2, em::ex3) = metric.template transform<3, Idx::T, Idx::U>( { i1_, i2_ }, finit.ex3(x_Phys)); } - if constexpr (defines_bx1) { + if constexpr (HasBx1) { metric.template convert({ i1_, i2_ + HALF }, x_Phys); EM(i1, i2, em::bx1) = metric.template transform<1, Idx::T, Idx::U>( { i1_, i2_ + HALF }, finit.bx1(x_Phys)); } - if constexpr (defines_bx2) { + if constexpr (HasBx2) { metric.template convert({ i1_ + HALF, i2_ }, x_Phys); EM(i1, i2, em::bx2) = metric.template transform<2, Idx::T, Idx::U>( { i1_ + HALF, i2_ }, finit.bx2(x_Phys)); } - if constexpr (defines_bx3) { + if constexpr (HasBx3) { metric.template convert({ i1_ + HALF, i2_ + HALF }, x_Phys); EM(i1, i2, em::bx3) = metric.template transform<3, Idx::T, Idx::U>( @@ -162,7 +155,7 @@ namespace arch { } } else if constexpr (S == SimEngine::GRPIC) { // grpic - if constexpr (defines_dx1 && defines_dx2 && defines_dx3) { + if constexpr (HasDx1 && HasDx2 && HasDx3) { const real_t x1_0 { metric.template convert<1, Crd::Cd, Crd::Ph>(i1_) }; const real_t x1_H { metric.template convert<1, Crd::Cd, Crd::Ph>( i1_ + HALF) }; @@ -179,7 +172,7 @@ namespace arch { EM(i1, i2, em::dx3) = finit.dx3({ x1_0, x2_0 }); } } - if constexpr (defines_bx1 && defines_bx2 && defines_bx3) { + if constexpr (HasBx1 && HasBx2 && HasBx3) { const real_t x1_0 { metric.template convert<1, Crd::Cd, Crd::Ph>(i1_) }; const real_t x1_H { metric.template convert<1, Crd::Cd, Crd::Ph>( i1_ + HALF) }; @@ -204,7 +197,7 @@ namespace arch { } } - Inline void operator()(index_t i1, index_t i2, index_t i3) const { + Inline void operator()(cellidx_t i1, cellidx_t i2, cellidx_t i3) const { if constexpr (D == Dim::_3D) { const auto i1_ = COORD(i1); const auto i2_ = COORD(i2); @@ -212,28 +205,28 @@ namespace arch { coord_t x_Phys { ZERO }; if constexpr (S == SimEngine::SRPIC) { // srpic - if constexpr (defines_ex1) { + if constexpr (HasEx1) { metric.template convert({ i1_ + HALF, i2_, i3_ }, x_Phys); EM(i1, i2, i3, em::ex1) = metric.template transform<1, Idx::T, Idx::U>( { i1_ + HALF, i2_, i3_ }, finit.ex1(x_Phys)); } - if constexpr (defines_ex2) { + if constexpr (HasEx2) { metric.template convert({ i1_, i2_ + HALF, i3_ }, x_Phys); EM(i1, i2, i3, em::ex2) = metric.template transform<2, Idx::T, Idx::U>( { i1_, i2_ + HALF, i3_ }, finit.ex2(x_Phys)); } - if constexpr (defines_ex3) { + if constexpr (HasEx3) { metric.template convert({ i1_, i2_, i3_ + HALF }, x_Phys); EM(i1, i2, i3, em::ex3) = metric.template transform<3, Idx::T, Idx::U>( { i1_, i2_, i3_ + HALF }, finit.ex3(x_Phys)); } - if constexpr (defines_bx1) { + if constexpr (HasBx1) { metric.template convert( { i1_, i2_ + HALF, i3_ + HALF }, x_Phys); @@ -241,7 +234,7 @@ namespace arch { { i1_, i2_ + HALF, i3_ + HALF }, finit.bx1(x_Phys)); } - if constexpr (defines_bx2) { + if constexpr (HasBx2) { metric.template convert( { i1_ + HALF, i2_, i3_ + HALF }, x_Phys); @@ -249,7 +242,7 @@ namespace arch { { i1_ + HALF, i2_, i3_ + HALF }, finit.bx2(x_Phys)); } - if constexpr (defines_bx3) { + if constexpr (HasBx3) { metric.template convert( { i1_ + HALF, i2_ + HALF, i3_ }, x_Phys); @@ -269,71 +262,26 @@ namespace arch { const real_t x3_H { metric.template convert<3, Crd::Cd, Crd::Ph>( i3_ + HALF) }; - if constexpr (defines_dx1 && defines_dx2 && defines_dx3) { + if constexpr (HasDx1 && HasDx2 && HasDx3) { { // dx1 - vec_t d_PU { finit.dx1({ x1_H, x2_0, x3_0 }), - finit.dx2({ x1_H, x2_0, x3_0 }), - finit.dx3({ x1_H, x2_0, x3_0 }) }; - vec_t d_U { ZERO }; - metric.template transform({ i1_ + HALF, i2_, i3_ }, - d_PU, - d_U); - EM(i1, i2, i3, em::dx1) = d_U[0]; + EM(i1, i2, i3, em::dx1) = finit.dx1({ x1_H, x2_0, x3_0 }); } { // dx2 - vec_t d_PU { finit.dx1({ x1_0, x2_H, x3_0 }), - finit.dx2({ x1_0, x2_H, x3_0 }), - finit.dx3({ x1_0, x2_H, x3_0 }) }; - vec_t d_U { ZERO }; - metric.template transform({ i1_, i2_ + HALF, i3_ }, - d_PU, - d_U); - EM(i1, i2, i3, em::dx2) = d_U[1]; + EM(i1, i2, i3, em::dx2) = finit.dx2({ x1_0, x2_H, x3_0 }); } { // dx3 - vec_t d_PU { finit.dx1({ x1_0, x2_0, x3_H }), - finit.dx2({ x1_0, x2_0, x3_H }), - finit.dx3({ x1_0, x2_0, x3_H }) }; - vec_t d_U { ZERO }; - metric.template transform({ i1_, i2_, i3_ + HALF }, - d_PU, - d_U); - EM(i1, i2, i3, em::dx3) = d_U[2]; + EM(i1, i2, i3, em::dx3) = finit.dx3({ x1_0, x2_0, x3_H }); } } - if constexpr (defines_bx1 && defines_bx2 && defines_bx3) { + if constexpr (HasBx1 && HasBx2 && HasBx3) { { // bx1 - vec_t b_PU { finit.bx1({ x1_0, x2_H, x3_H }), - finit.bx2({ x1_0, x2_H, x3_H }), - finit.bx3({ x1_0, x2_H, x3_H }) }; - vec_t b_U { ZERO }; - metric.template transform( - { i1_, i2_ + HALF, i3_ + HALF }, - b_PU, - b_U); - EM(i1, i2, i3, em::bx1) = b_U[0]; + EM(i1, i2, i3, em::bx1) = finit.bx1({ x1_0, x2_H, x3_H }); } { // bx2 - vec_t b_PU { finit.bx1({ x1_H, x2_0, x3_H }), - finit.bx2({ x1_H, x2_0, x3_H }), - finit.bx3({ x1_H, x2_0, x3_H }) }; - vec_t b_U { ZERO }; - metric.template transform( - { i1_ + HALF, i2_, i3_ + HALF }, - b_PU, - b_U); - EM(i1, i2, i3, em::bx2) = b_U[1]; + EM(i1, i2, i3, em::bx2) = finit.bx2({ x1_H, x2_0, x3_H }); } { // bx3 - vec_t b_PU { finit.bx1({ x1_H, x2_H, x3_0 }), - finit.bx2({ x1_H, x2_H, x3_0 }), - finit.bx3({ x1_H, x2_H, x3_0 }) }; - vec_t b_U { ZERO }; - metric.template transform( - { i1_ + HALF, i2_ + HALF, i3_ }, - b_PU, - b_U); - EM(i1, i2, i3, em::bx3) = b_U[2]; + EM(i1, i2, i3, em::bx3) = finit.bx3({ x1_H, x2_H, x3_0 }); } } } else { @@ -345,6 +293,531 @@ namespace arch { } }; + template FS> + struct CustomSetEMFields_kernel { + static constexpr auto D = M::Dim; + static constexpr auto HasEx1 = ::traits::fieldsetter::HasConditionalEx1; + static constexpr auto HasEx2 = ::traits::fieldsetter::HasConditionalEx2; + static constexpr auto HasEx3 = ::traits::fieldsetter::HasConditionalEx3; + static constexpr auto HasBx1 = ::traits::fieldsetter::HasConditionalBx1; + static constexpr auto HasBx2 = ::traits::fieldsetter::HasConditionalBx2; + static constexpr auto HasBx3 = ::traits::fieldsetter::HasConditionalBx3; + + M metric; + ndfield_t fields; + ndfield_t buffer; + + const FS fieldsetter; + + CustomSetEMFields_kernel(const M& metric, + ndfield_t& fields, + const ndfield_t& /*buffer*/, + const FS& fieldsetter) + : metric { metric } + , fields { fields } + , fieldsetter { fieldsetter } {} + + Inline void operator()(cellidx_t i1) const { + if constexpr (D == Dim::_1D) { + const auto i1_ = COORD(i1); + coord_t x_Ph { ZERO }; + + vec_t e_U { ZERO }; + vec_t b_U { ZERO }; + + vec_t e_T { ZERO }; + vec_t b_T { ZERO }; + + if constexpr (HasEx1 or HasBx2 or HasBx3) { + metric.template convert({ i1_ + HALF }, x_Ph); + + e_U[0] = buffer(i1, em::ex1); + e_U[1] = HALF * (buffer(i1, em::ex2) + buffer(i1 + 1, em::ex2)); + e_U[2] = HALF * (buffer(i1, em::ex3) + buffer(i1 + 1, em::ex3)); + + b_U[0] = HALF * (buffer(i1, em::bx1) + buffer(i1 + 1, em::bx1)); + b_U[1] = buffer(i1, em::bx2); + b_U[2] = buffer(i1, em::bx3); + + metric.template transform({ i1_ + HALF }, e_U, e_T); + metric.template transform({ i1_ + HALF }, b_U, b_T); + + if constexpr (HasEx1) { + const auto ex1_setter = fieldsetter.ex1(x_Ph, e_T, b_T); + + if (ex1_setter.first) { + fields(i1, em::ex1) = metric.template transform<1, Idx::T, Idx::U>( + { i1_ + HALF }, + ex1_setter.second); + } + } + if constexpr (HasBx2) { + const auto bx2_setter = fieldsetter.bx2(x_Ph, e_T, b_T); + + if (bx2_setter.first) { + fields(i1, em::bx2) = metric.template transform<2, Idx::T, Idx::U>( + { i1_ + HALF }, + bx2_setter.second); + } + } + if constexpr (HasBx3) { + const auto bx3_setter = fieldsetter.bx3(x_Ph, e_T, b_T); + + if (bx3_setter.first) { + fields(i1, em::bx3) = metric.template transform<3, Idx::T, Idx::U>( + { i1_ + HALF }, + bx3_setter.second); + } + } + } + if constexpr (HasEx2 or HasEx3 or HasBx1) { + metric.template convert({ i1_ }, x_Ph); + + e_U[0] = HALF * (buffer(i1, em::ex1) + buffer(i1 - 1, em::ex1)); + e_U[1] = buffer(i1, em::ex2); + e_U[2] = buffer(i1, em::ex3); + + b_U[0] = buffer(i1, em::bx1); + b_U[1] = HALF * (buffer(i1, em::bx2) + buffer(i1 - 1, em::bx2)); + b_U[2] = HALF * (buffer(i1, em::bx3) + buffer(i1 - 1, em::bx3)); + + metric.template transform({ i1_ }, e_U, e_T); + metric.template transform({ i1_ }, b_U, b_T); + + if constexpr (HasEx2) { + const auto ex2_setter = fieldsetter.ex2(x_Ph, e_T, b_T); + + if (ex2_setter.first) { + fields(i1, em::ex2) = metric.template transform<2, Idx::T, Idx::U>( + { i1_ }, + ex2_setter.second); + } + } + if constexpr (HasEx3) { + const auto ex3_setter = fieldsetter.ex3(x_Ph, e_T, b_T); + + if (ex3_setter.first) { + fields(i1, em::ex3) = metric.template transform<3, Idx::T, Idx::U>( + { i1_ }, + ex3_setter.second); + } + } + if constexpr (HasBx1) { + const auto bx1_setter = fieldsetter.bx1(x_Ph, e_T, b_T); + + if (bx1_setter.first) { + fields(i1, em::bx1) = metric.template transform<1, Idx::T, Idx::U>( + { i1_ }, + bx1_setter.second); + } + } + } + } else { + raise::KernelError(HERE, "CustomEMFields_kernel 1D called for 2D/3D"); + } + } + + Inline void operator()(cellidx_t i1, cellidx_t i2) const { + if constexpr (D == Dim::_2D) { + const auto i1_ = COORD(i1); + const auto i2_ = COORD(i2); + coord_t x_Ph { ZERO }; + + vec_t e_U { ZERO }; + vec_t b_U { ZERO }; + + vec_t e_T { ZERO }; + vec_t b_T { ZERO }; + + if constexpr (HasEx1 or HasBx2) { + metric.template convert({ i1_ + HALF, i2_ }, x_Ph); + + e_U[0] = buffer(i1, i2, em::ex1); + e_U[1] = INV_4 * (buffer(i1, i2, em::ex2) + buffer(i1 + 1, i2, em::ex2) + + buffer(i1, i2 - 1, em::ex2) + + buffer(i1 + 1, i2 - 1, em::ex2)); + e_U[2] = HALF * (buffer(i1, i2, em::ex3) + buffer(i1 + 1, i2, em::ex3)); + + b_U[0] = INV_4 * (buffer(i1, i2, em::bx1) + buffer(i1 + 1, i2, em::bx1) + + buffer(i1, i2 - 1, em::bx1) + + buffer(i1 + 1, i2 - 1, em::bx1)); + b_U[1] = buffer(i1, i2, em::bx2); + b_U[2] = HALF * (buffer(i1, i2, em::bx3) + buffer(i1, i2 - 1, em::bx3)); + + metric.template transform({ i1_ + HALF, i2_ }, e_U, e_T); + metric.template transform({ i1_ + HALF, i2_ }, b_U, b_T); + if constexpr (HasEx1) { + const auto ex1_setter = fieldsetter.ex1(x_Ph, e_T, b_T); + + if (ex1_setter.first) { + fields(i1, i2, em::ex1) = metric.template transform<1, Idx::T, Idx::U>( + { i1_ + HALF, i2_ }, + ex1_setter.second); + } + } + if constexpr (HasBx2) { + const auto bx2_setter = fieldsetter.bx2(x_Ph, e_T, b_T); + + if (bx2_setter.first) { + fields(i1, i2, em::bx2) = metric.template transform<2, Idx::T, Idx::U>( + { i1_ + HALF, i2_ }, + bx2_setter.second); + } + } + } + if constexpr (HasEx2 or HasBx1) { + metric.template convert({ i1_, i2_ + HALF }, x_Ph); + + e_U[0] = INV_4 * (buffer(i1, i2, em::ex1) + buffer(i1 - 1, i2, em::ex1) + + buffer(i1, i2 + 1, em::ex1) + + buffer(i1 - 1, i2 + 1, em::ex1)); + e_U[1] = buffer(i1, i2, em::ex2); + e_U[2] = HALF * (buffer(i1, i2, em::ex3) + buffer(i1, i2 + 1, em::ex3)); + + b_U[0] = buffer(i1, i2, em::bx1); + b_U[1] = INV_4 * (buffer(i1, i2, em::bx2) + buffer(i1 - 1, i2, em::bx2) + + buffer(i1, i2 + 1, em::bx2) + + buffer(i1 - 1, i2 + 1, em::bx2)); + b_U[2] = HALF * (buffer(i1, i2, em::bx3) + buffer(i1 - 1, i2, em::bx3)); + + metric.template transform({ i1_, i2_ + HALF }, e_U, e_T); + metric.template transform({ i1_, i2_ + HALF }, b_U, b_T); + if constexpr (HasEx2) { + const auto ex2_setter = fieldsetter.ex2(x_Ph, e_T, b_T); + + if (ex2_setter.first) { + fields(i1, i2, em::ex2) = metric.template transform<2, Idx::T, Idx::U>( + { i1_, i2_ + HALF }, + ex2_setter.second); + } + } + if constexpr (HasBx1) { + const auto bx1_setter = fieldsetter.bx1(x_Ph, e_T, b_T); + + if (bx1_setter.first) { + fields(i1, i2, em::bx1) = metric.template transform<1, Idx::T, Idx::U>( + { i1_, i2_ + HALF }, + bx1_setter.second); + } + } + } + if constexpr (HasEx3) { + metric.template convert({ i1_, i2_ }, x_Ph); + + e_U[0] = HALF * (buffer(i1, i2, em::ex1) + buffer(i1 - 1, i2, em::ex1)); + e_U[1] = HALF * (buffer(i1, i2, em::ex2) + buffer(i1, i2 - 1, em::ex2)); + e_U[2] = buffer(i1, i2, em::ex3); + + b_U[0] = HALF * (buffer(i1, i2, em::bx1) + buffer(i1, i2 - 1, em::bx1)); + b_U[1] = HALF * (buffer(i1, i2, em::bx2) + buffer(i1 - 1, i2, em::bx2)); + b_U[2] = INV_4 * (buffer(i1, i2, em::bx3) + buffer(i1 - 1, i2, em::bx3) + + buffer(i1, i2 - 1, em::bx3) + + buffer(i1 - 1, i2 - 1, em::bx3)); + + metric.template transform({ i1_, i2_ }, e_U, e_T); + metric.template transform({ i1_, i2_ }, b_U, b_T); + const auto ex3_setter = fieldsetter.ex3(x_Ph, e_T, b_T); + + if (ex3_setter.first) { + fields(i1, i2, em::ex3) = metric.template transform<3, Idx::T, Idx::U>( + { i1_, i2_ }, + ex3_setter.second); + } + } + if constexpr (HasBx3) { + metric.template convert({ i1_ + HALF, i2_ + HALF }, + x_Ph); + + e_U[0] = HALF * (buffer(i1, i2, em::ex1) + buffer(i1, i2 + 1, em::ex1)); + e_U[1] = HALF * (buffer(i1, i2, em::ex2) + buffer(i1 + 1, i2, em::ex2)); + e_U[2] = INV_4 * (buffer(i1, i2, em::ex3) + buffer(i1 + 1, i2, em::ex3) + + buffer(i1, i2 + 1, em::ex3) + + buffer(i1 + 1, i2 + 1, em::ex3)); + + b_U[0] = HALF * (buffer(i1, i2, em::bx1) + buffer(i1 + 1, i2, em::bx1)); + b_U[1] = HALF * (buffer(i1, i2, em::bx2) + buffer(i1, i2 + 1, em::bx2)); + b_U[2] = buffer(i1, i2, em::bx3); + + metric.template transform({ i1_ + HALF, i2_ + HALF }, + e_U, + e_T); + metric.template transform({ i1_ + HALF, i2_ + HALF }, + b_U, + b_T); + const auto bx3_setter = fieldsetter.bx3(x_Ph, e_T, b_T); + + if (bx3_setter.first) { + fields(i1, i2, em::bx3) = metric.template transform<3, Idx::T, Idx::U>( + { i1_ + HALF, i2_ + HALF }, + bx3_setter.second); + } + } + } else { + raise::KernelError(HERE, "CustomEMFields_kernel 2D called for 1D/3D"); + } + } + + Inline void operator()(cellidx_t i1, cellidx_t i2, cellidx_t i3) const { + if constexpr (D == Dim::_3D) { + const auto i1_ = COORD(i1); + const auto i2_ = COORD(i2); + const auto i3_ = COORD(i3); + coord_t x_Ph { ZERO }; + + vec_t e_U { ZERO }; + vec_t b_U { ZERO }; + + vec_t e_T { ZERO }; + vec_t b_T { ZERO }; + if constexpr (HasEx1) { + metric.template convert({ i1_ + HALF, i2_, i3_ }, x_Ph); + + e_U[0] = buffer(i1, i2, i3, em::ex1); + e_U[1] = INV_4 * (buffer(i1, i2, i3, em::ex2) + + buffer(i1 + 1, i2, i3, em::ex2) + + buffer(i1, i2 - 1, i3, em::ex2) + + buffer(i1 + 1, i2 - 1, i3, em::ex2)); + e_U[2] = INV_4 * (buffer(i1, i2, i3, em::ex3) + + buffer(i1 + 1, i2, i3, em::ex3) + + buffer(i1, i2, i3 - 1, em::ex3) + + buffer(i1 + 1, i2, i3 - 1, em::ex3)); + + b_U[0] = INV_8 * (buffer(i1, i2, i3, em::bx1) + + buffer(i1 + 1, i2, i3, em::bx1) + + buffer(i1, i2 - 1, i3, em::bx1) + + buffer(i1 + 1, i2 - 1, i3, em::bx1) + + buffer(i1, i2, i3 - 1, em::bx1) + + buffer(i1 + 1, i2, i3 - 1, em::bx1) + + buffer(i1, i2 - 1, i3 - 1, em::bx1) + + buffer(i1 + 1, i2 - 1, i3 - 1, em::bx1)); + b_U[1] = HALF * (buffer(i1, i2, i3, em::bx2) + + buffer(i1, i2, i3 - 1, em::bx2)); + b_U[2] = HALF * (buffer(i1, i2, i3, em::bx3) + + buffer(i1, i2 - 1, i3, em::bx3)); + + metric.template transform({ i1_ + HALF, i2_, i3_ }, + e_U, + e_T); + metric.template transform({ i1_ + HALF, i2_, i3_ }, + b_U, + b_T); + const auto ex1_setter = fieldsetter.ex1(x_Ph, e_T, b_T); + + if (ex1_setter.first) { + fields(i1, i2, i3, em::ex1) = metric.template transform<1, Idx::T, Idx::U>( + { i1_ + HALF, i2_, i3_ }, + ex1_setter.second); + } + } + if constexpr (HasEx2) { + metric.template convert({ i1_, i2_ + HALF, i3_ }, x_Ph); + + e_U[0] = INV_4 * (buffer(i1, i2, i3, em::ex1) + + buffer(i1 - 1, i2, i3, em::ex1) + + buffer(i1, i2 + 1, i3, em::ex1) + + buffer(i1 - 1, i2 + 1, i3, em::ex1)); + e_U[1] = buffer(i1, i2, i3, em::ex2); + e_U[2] = INV_4 * (buffer(i1, i2, i3, em::ex3) + + buffer(i1, i2 + 1, i3, em::ex3) + + buffer(i1, i2, i3 - 1, em::ex3) + + buffer(i1, i2 + 1, i3 - 1, em::ex3)); + + b_U[0] = HALF * (buffer(i1, i2, i3, em::bx1) + + buffer(i1, i2, i3 - 1, em::bx1)); + b_U[1] = INV_8 * (buffer(i1, i2, i3, em::bx2) + + buffer(i1 - 1, i2, i3, em::bx2) + + buffer(i1, i2 + 1, i3, em::bx2) + + buffer(i1 - 1, i2 + 1, i3, em::bx2) + + buffer(i1, i2, i3 - 1, em::bx2) + + buffer(i1 - 1, i2, i3 - 1, em::bx2) + + buffer(i1, i2 + 1, i3 - 1, em::bx2) + + buffer(i1 - 1, i2 + 1, i3 - 1, em::bx2)); + b_U[2] = HALF * (buffer(i1, i2, i3, em::bx3) + + buffer(i1 - 1, i2, i3, em::bx3)); + + metric.template transform({ i1_, i2_ + HALF, i3_ }, + e_U, + e_T); + metric.template transform({ i1_, i2_ + HALF, i3_ }, + b_U, + b_T); + const auto ex2_setter = fieldsetter.ex2(x_Ph, e_T, b_T); + + if (ex2_setter.first) { + fields(i1, i2, i3, em::ex2) = metric.template transform<2, Idx::T, Idx::U>( + { i1_, i2_ + HALF, i3_ }, + ex2_setter.second); + } + } + if constexpr (HasEx3) { + metric.template convert({ i1_, i2_, i3_ + HALF }, x_Ph); + + e_U[0] = INV_4 * (buffer(i1, i2, i3, em::ex1) + + buffer(i1 - 1, i2, i3, em::ex1) + + buffer(i1, i2, i3 + 1, em::ex1) + + buffer(i1 - 1, i2, i3 + 1, em::ex1)); + e_U[1] = INV_4 * (buffer(i1, i2, i3, em::ex2) + + buffer(i1, i2 - 1, i3, em::ex2) + + buffer(i1, i2, i3 + 1, em::ex2) + + buffer(i1, i2 - 1, i3 + 1, em::ex2)); + e_U[2] = buffer(i1, i2, i3, em::ex3); + + b_U[0] = HALF * (buffer(i1, i2, i3, em::bx1) + + buffer(i1, i2 - 1, i3, em::bx1)); + b_U[1] = HALF * (buffer(i1, i2, i3, em::bx2) + + buffer(i1 - 1, i2, i3, em::bx2)); + b_U[2] = INV_8 * (buffer(i1, i2, i3, em::bx3) + + buffer(i1 - 1, i2, i3, em::bx3) + + buffer(i1, i2, i3 + 1, em::bx3) + + buffer(i1 - 1, i2, i3 + 1, em::bx3) + + buffer(i1, i2 - 1, i3, em::bx3) + + buffer(i1 - 1, i2 - 1, i3, em::bx3) + + buffer(i1, i2 - 1, i3 + 1, em::bx3) + + buffer(i1 - 1, i2 - 1, i3 + 1, em::bx3)); + + metric.template transform({ i1_, i2_, i3_ + HALF }, + e_U, + e_T); + metric.template transform({ i1_, i2_, i3_ + HALF }, + b_U, + b_T); + const auto ex3_setter = fieldsetter.ex3(x_Ph, e_T, b_T); + + if (ex3_setter.first) { + fields(i1, i2, i3, em::ex3) = metric.template transform<3, Idx::T, Idx::U>( + { i1_, i2_, i3_ + HALF }, + ex3_setter.second); + } + } + if constexpr (HasBx1) { + metric.template convert({ i1_, i2_ + HALF, i3_ + HALF }, + x_Ph); + + e_U[0] = INV_8 * (buffer(i1, i2, i3, em::ex1) + + buffer(i1 - 1, i2, i3, em::ex1) + + buffer(i1, i2 + 1, i3, em::ex1) + + buffer(i1 - 1, i2 + 1, i3, em::ex1) + + buffer(i1, i2, i3 + 1, em::ex1) + + buffer(i1 - 1, i2, i3 + 1, em::ex1) + + buffer(i1, i2 + 1, i3 + 1, em::ex1) + + buffer(i1 - 1, i2 + 1, i3 + 1, em::ex1)); + e_U[1] = HALF * (buffer(i1, i2, i3, em::ex2) + + buffer(i1, i2, i3 + 1, em::ex2)); + e_U[2] = HALF * (buffer(i1, i2, i3, em::ex3) + + buffer(i1, i2 + 1, i3, em::ex3)); + + b_U[0] = buffer(i1, i2, i3, em::bx1); + b_U[1] = INV_4 * (buffer(i1, i2, i3, em::bx2) + + buffer(i1 - 1, i2, i3, em::bx2) + + buffer(i1, i2 + 1, i3, em::bx2) + + buffer(i1 - 1, i2 + 1, i3, em::bx2)); + b_U[2] = INV_4 * (buffer(i1, i2, i3, em::bx3) + + buffer(i1 - 1, i2, i3, em::bx3) + + buffer(i1, i2, i3 + 1, em::bx3) + + buffer(i1 - 1, i2, i3 + 1, em::bx3)); + + metric.template transform({ i1_, i2_ + HALF, i3_ + HALF }, + e_U, + e_T); + metric.template transform({ i1_, i2_ + HALF, i3_ + HALF }, + b_U, + b_T); + const auto bx1_setter = fieldsetter.bx1(x_Ph, e_T, b_T); + + if (bx1_setter.first) { + fields(i1, i2, i3, em::bx1) = metric.template transform<1, Idx::T, Idx::U>( + { i1_, i2_ + HALF, i3_ + HALF }, + bx1_setter.second); + } + } + if constexpr (HasBx2) { + metric.template convert({ i1_ + HALF, i2_, i3_ + HALF }, + x_Ph); + + e_U[0] = HALF * (buffer(i1, i2, i3, em::ex1) + + buffer(i1, i2, i3 + 1, em::ex1)); + e_U[1] = INV_8 * (buffer(i1, i2, i3, em::ex2) + + buffer(i1 + 1, i2, i3, em::ex2) + + buffer(i1, i2 - 1, i3, em::ex2) + + buffer(i1 + 1, i2 - 1, i3, em::ex2) + + buffer(i1, i2, i3 + 1, em::ex2) + + buffer(i1 + 1, i2, i3 + 1, em::ex2) + + buffer(i1, i2 - 1, i3 + 1, em::ex2) + + buffer(i1 + 1, i2 - 1, i3 + 1, em::ex2)); + e_U[2] = HALF * (buffer(i1, i2, i3, em::ex3) + + buffer(i1 + 1, i2, i3, em::ex3)); + + b_U[0] = INV_4 * (buffer(i1, i2, i3, em::bx1) + + buffer(i1 + 1, i2, i3, em::bx1) + + buffer(i1, i2 - 1, i3, em::bx1) + + buffer(i1 + 1, i2 - 1, i3, em::bx1)); + b_U[1] = buffer(i1, i2, i3, em::bx2); + b_U[2] = INV_4 * (buffer(i1, i2, i3, em::bx3) + + buffer(i1, i2 - 1, i3, em::bx3) + + buffer(i1, i2, i3 + 1, em::bx3) + + buffer(i1, i2 - 1, i3 + 1, em::bx3)); + + metric.template transform({ i1_ + HALF, i2_, i3_ + HALF }, + e_U, + e_T); + metric.template transform({ i1_ + HALF, i2_, i3_ + HALF }, + b_U, + b_T); + const auto bx2_setter = fieldsetter.bx2(x_Ph, e_T, b_T); + + if (bx2_setter.first) { + fields(i1, i2, i3, em::bx2) = metric.template transform<2, Idx::T, Idx::U>( + { i1_ + HALF, i2_, i3_ + HALF }, + bx2_setter.second); + } + } + if constexpr (HasBx3) { + metric.template convert({ i1_ + HALF, i2_ + HALF, i3_ }, + x_Ph); + + e_U[0] = HALF * (buffer(i1, i2, i3, em::ex1) + + buffer(i1, i2 + 1, i3, em::ex1)); + e_U[1] = HALF * (buffer(i1, i2, i3, em::ex2) + + buffer(i1 + 1, i2, i3, em::ex2)); + e_U[2] = INV_8 * (buffer(i1, i2, i3, em::ex3) + + buffer(i1 + 1, i2, i3, em::ex3) + + buffer(i1, i2 + 1, i3, em::ex3) + + buffer(i1 + 1, i2 + 1, i3, em::ex3) + + buffer(i1, i2, i3 - 1, em::ex3) + + buffer(i1 + 1, i2, i3 - 1, em::ex3) + + buffer(i1, i2 + 1, i3 - 1, em::ex3) + + buffer(i1 + 1, i2 + 1, i3 - 1, em::ex3)); + + b_U[0] = INV_4 * (buffer(i1, i2, i3, em::bx1) + + buffer(i1 + 1, i2, i3, em::bx1) + + buffer(i1, i2, i3 - 1, em::bx1) + + buffer(i1 + 1, i2, i3 - 1, em::bx1)); + b_U[1] = INV_4 * (buffer(i1, i2, i3, em::bx2) + + buffer(i1, i2 + 1, i3, em::bx2) + + buffer(i1, i2, i3 - 1, em::bx2) + + buffer(i1, i2 + 1, i3 - 1, em::bx2)); + b_U[2] = buffer(i1, i2, i3, em::bx3); + + metric.template transform({ i1_ + HALF, i2_ + HALF, i3_ }, + e_U, + e_T); + metric.template transform({ i1_ + HALF, i2_ + HALF, i3_ }, + b_U, + b_T); + const auto bx3_setter = fieldsetter.bx3(x_Ph, e_T, b_T); + + if (bx3_setter.first) { + fields(i1, i2, i3, em::bx3) = metric.template transform<3, Idx::T, Idx::U>( + { i1_ + HALF, i2_ + HALF, i3_ }, + bx3_setter.second); + } + } + } else { + raise::KernelError(HERE, "CustomEMFields_kernel 3D called for 1D/2D"); + } + } + }; + } // namespace arch #endif // ARCHETYPES_FIELD_SETTER_HPP diff --git a/src/archetypes/moving_window.h b/src/archetypes/moving_window.h new file mode 100644 index 000000000..42fd8f9ca --- /dev/null +++ b/src/archetypes/moving_window.h @@ -0,0 +1,278 @@ +/** + * @file archetypes/moving_window.h + * @brief Moving window functions for implementing the moving window in the CustomPostStep + * @implements + * - arch::MoveWindow<> -> void + * @namespaces: + * - arch:: + */ + +#ifndef ARCHETYPES_WINDOW_H +#define ARCHETYPES_WINDOW_H + +#include "enums.h" +#include "global.h" + +#include "framework/domain/domain.h" +#include "framework/domain/metadomain.h" + +namespace arch { + + template + struct FieldShift_kernel { + static_assert(static_cast(o) < static_cast(D), + "Invalid component index"); + + ndfield_t Fld; + ndfield_t backup_Fld; + const BCTags tags; + const int window_shift; + + FieldShift_kernel(ndfield_t Fld, + ndfield_t backup_Fld, + const int window_shift, + BCTags tags) + : Fld { Fld } + , backup_Fld { backup_Fld } + , window_shift { window_shift } + , tags { tags } {} + + Inline void operator()(cellidx_t i1) const { + if constexpr (D == Dim::_1D) { + if (tags & BC::E) { + Fld(i1, em::ex1) = backup_Fld(i1 + window_shift, em::ex1); + Fld(i1, em::ex2) = backup_Fld(i1 + window_shift, em::ex2); + Fld(i1, em::ex3) = backup_Fld(i1 + window_shift, em::ex3); + } + if (tags & BC::B) { + Fld(i1, em::bx1) = backup_Fld(i1 + window_shift, em::bx1); + Fld(i1, em::bx2) = backup_Fld(i1 + window_shift, em::bx2); + Fld(i1, em::bx3) = backup_Fld(i1 + window_shift, em::bx3); + } + } else { + raise::KernelError( + HERE, + "FieldShift_kernel: 1D implementation called for D != 1"); + } + } + + Inline void operator()(cellidx_t i1, cellidx_t i2) const { + if constexpr (D == Dim::_2D) { + if constexpr (o == in::x1) { + if (tags & BC::E) { + Fld(i1, i2, em::ex1) = backup_Fld(i1 + window_shift, i2, em::ex1); + Fld(i1, i2, em::ex2) = backup_Fld(i1 + window_shift, i2, em::ex2); + Fld(i1, i2, em::ex3) = backup_Fld(i1 + window_shift, i2, em::ex3); + } + if (tags & BC::B) { + Fld(i1, i2, em::bx1) = backup_Fld(i1 + window_shift, i2, em::bx1); + Fld(i1, i2, em::bx2) = backup_Fld(i1 + window_shift, i2, em::bx2); + Fld(i1, i2, em::bx3) = backup_Fld(i1 + window_shift, i2, em::bx3); + } + } else if constexpr (o == in::x2) { + if (tags & BC::E) { + Fld(i1, i2, em::ex1) = backup_Fld(i1, i2 + window_shift, em::ex1); + Fld(i1, i2, em::ex2) = backup_Fld(i1, i2 + window_shift, em::ex2); + Fld(i1, i2, em::ex3) = backup_Fld(i1, i2 + window_shift, em::ex3); + } + if (tags & BC::B) { + Fld(i1, i2, em::bx1) = backup_Fld(i1, i2 + window_shift, em::bx1); + Fld(i1, i2, em::bx2) = backup_Fld(i1, i2 + window_shift, em::bx2); + Fld(i1, i2, em::bx3) = backup_Fld(i1, i2 + window_shift, em::bx3); + } + } + } else { + raise::KernelError( + HERE, + "FieldShift_kernel: 2D implementation called for D != 2"); + } + } + + Inline void operator()(cellidx_t i1, cellidx_t i2, cellidx_t i3) const { + if constexpr (D == Dim::_3D) { + if constexpr (o == in::x1) { + if (tags & BC::E) { + Fld(i1, i2, i3, em::ex1) = backup_Fld(i1 + window_shift, i2, i3, em::ex1); + Fld(i1, i2, i3, em::ex2) = backup_Fld(i1 + window_shift, i2, i3, em::ex2); + Fld(i1, i2, i3, em::ex3) = backup_Fld(i1 + window_shift, i2, i3, em::ex3); + } + if (tags & BC::B) { + Fld(i1, i2, i3, em::bx1) = backup_Fld(i1 + window_shift, i2, i3, em::bx1); + Fld(i1, i2, i3, em::bx2) = backup_Fld(i1 + window_shift, i2, i3, em::bx2); + Fld(i1, i2, i3, em::bx3) = backup_Fld(i1 + window_shift, i2, i3, em::bx3); + } + } else if constexpr (o == in::x2) { + if (tags & BC::E) { + Fld(i1, i2, i3, em::ex1) = backup_Fld(i1, i2 + window_shift, i3, em::ex1); + Fld(i1, i2, i3, em::ex2) = backup_Fld(i1, i2 + window_shift, i3, em::ex2); + Fld(i1, i2, i3, em::ex3) = backup_Fld(i1, i2 + window_shift, i3, em::ex3); + } + if (tags & BC::B) { + Fld(i1, i2, i3, em::bx1) = backup_Fld(i1, i2 + window_shift, i3, em::bx1); + Fld(i1, i2, i3, em::bx2) = backup_Fld(i1, i2 + window_shift, i3, em::bx2); + Fld(i1, i2, i3, em::bx3) = backup_Fld(i1, i2 + window_shift, i3, em::bx3); + } + } else { + if (tags & BC::E) { + Fld(i1, i2, i3, em::ex1) = backup_Fld(i1, i2, i3 + window_shift, em::ex1); + Fld(i1, i2, i3, em::ex2) = backup_Fld(i1, i2, i3 + window_shift, em::ex2); + Fld(i1, i2, i3, em::ex3) = backup_Fld(i1, i2, i3 + window_shift, em::ex3); + } + if (tags & BC::B) { + Fld(i1, i2, i3, em::bx1) = backup_Fld(i1, i2, i3 + window_shift, em::bx1); + Fld(i1, i2, i3, em::bx2) = backup_Fld(i1, i2, i3 + window_shift, em::bx2); + Fld(i1, i2, i3, em::bx3) = backup_Fld(i1, i2, i3 + window_shift, em::bx3); + } + } + } else { + raise::KernelError( + HERE, + "FieldShift_kernel: 3D implementation called for D != 3"); + } + } + }; + + /** + * @brief Updates particle position and fields in the moving window. + + */ + template + Inline void MoveWindow(Domain& domain, + Metadomain& metadomain, + int window_shift) { + + /* + move particles in the window back by the window size + */ + const auto nspec = domain.species.size(); + const auto& mesh = domain.mesh; + if (o == in::x1) { + // loop over particle species + for (auto s { 0u }; s < nspec; ++s) { + // get particle properties + auto& species = domain.species[s]; + auto i1 = species.i1; + Kokkos::parallel_for( + "MoveParticles", + species.rangeActiveParticles(), + Lambda(prtlidx_t p) { + // shift particle position back by window update frequency + i1(p) -= window_shift; + }); + } + } else if (o == in::x2) { + if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { + // loop over particle species + for (auto s { 0u }; s < nspec; ++s) { + // get particle properties + auto& species = domain.species[s]; + auto i2 = species.i2; + Kokkos::parallel_for( + "MoveParticles", + species.rangeActiveParticles(), + Lambda(prtlidx_t p) { + // shift particle position back by window update frequency + i2(p) -= window_shift; + }); + } + } else { + raise::Error("Invalid dimension", HERE); + } + } else if (o == in::x3) { + if constexpr (M::Dim == Dim::_3D) { + // loop over particle species + for (auto s { 0u }; s < nspec; ++s) { + // get particle properties + auto& species = domain.species[s]; + auto i3 = species.i3; + Kokkos::parallel_for( + "MoveParticles", + species.rangeActiveParticles(), + Lambda(prtlidx_t p) { + // shift particle position back by window update frequency + i3(p) -= window_shift; + }); + } + } else { + raise::Error("Invalid direction", HERE); + } + } else { + raise::Error("Invalid direction", HERE); + } + + // shift fields in the window back by the window size + std::vector xi_min, xi_max; + const std::vector all_dirs { in::x1, in::x2, in::x3 }; + for (auto d { 0u }; d < M::Dim; ++d) { + const auto dd = all_dirs[d]; + if (o == dd) { + xi_min.push_back(0); + xi_max.push_back(domain.mesh.n_all(dd) - window_shift); + } else { + xi_min.push_back(0); + xi_max.push_back(domain.mesh.n_all(dd)); + } + } + raise::ErrorIf(xi_min.size() != xi_max.size() or + xi_min.size() != static_cast(M::Dim), + "Invalid range size", + HERE); + + // loop range for shifting fields + range_t range; + if constexpr (M::Dim == Dim::_1D) { + range = CreateRangePolicy({ xi_min[0] }, { xi_max[0] }); + } else if constexpr (M::Dim == Dim::_2D) { + range = CreateRangePolicy({ xi_min[0], xi_min[1] }, + { xi_max[0], xi_max[1] }); + } else if constexpr (M::Dim == Dim::_3D) { + range = CreateRangePolicy({ xi_min[0], xi_min[1], xi_min[2] }, + { xi_max[0], xi_max[1], xi_max[2] }); + } else { + raise::Error("Invalid dimension", HERE); + } + + // copy fields to backup before shifting + Kokkos::deep_copy(domain.fields.bckp, domain.fields.em); + + if (o == in::x1) { + Kokkos::parallel_for("ShiftFields", + range, + FieldShift_kernel(domain.fields.em, + domain.fields.bckp, + window_shift, + BC::B | BC::E)); + } else if (o == in::x2) { + if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { + Kokkos::parallel_for("ShiftFields", + range, + FieldShift_kernel(domain.fields.em, + domain.fields.bckp, + window_shift, + BC::B | BC::E)); + } else { + raise::Error("Invalid dimension", HERE); + } + } else { + if constexpr (M::Dim == Dim::_3D) { + Kokkos::parallel_for("ShiftFields", + range, + FieldShift_kernel(domain.fields.em, + domain.fields.bckp, + window_shift, + BC::B | BC::E)); + } else { + raise::Error("Invalid dimension", HERE); + } + } + + // synch ghost zones after moving the window + metadomain.CommunicateFields(domain, Comm::E | Comm::B); + // communicate particles after moving + metadomain.CommunicateParticles(domain); + + metadomain.ShiftByCells(window_shift, o); + } +} // namespace arch + +#endif // ARCHETYPES_UTILS_H diff --git a/src/archetypes/particle_injector.h b/src/archetypes/particle_injector.h index 24af59965..7adb8c5b5 100644 --- a/src/archetypes/particle_injector.h +++ b/src/archetypes/particle_injector.h @@ -19,12 +19,13 @@ #include "global.h" #include "arch/kokkos_aliases.h" +#include "traits/archetypes.h" +#include "traits/metric.h" #include "utils/error.h" #include "utils/numeric.h" #include "framework/domain/domain.h" #include "framework/domain/metadomain.h" - #include "kernels/injectors.hpp" #include @@ -52,7 +53,7 @@ namespace arch { * - array_t: minimum coordinates of the region in computational coords * - array_t: maximum coordinates of the region in computational coords */ - template + template auto DeduceRegion(const Domain& domain, const boundaries_t& box) -> std::tuple, array_t> { if (not domain.mesh.Intersects(box)) { @@ -78,7 +79,8 @@ namespace arch { domain.mesh.metric.template convert(xCorner_max_Ph, xCorner_max_Cd); - array_t xi_min { "xi_min", M::Dim }, xi_max { "xi_max", M::Dim }; + const array_t xi_min { "xi_min", M::Dim }, + xi_max { "xi_max", M::Dim }; auto xi_min_h = Kokkos::create_mirror_view(xi_min); auto xi_max_h = Kokkos::create_mirror_view(xi_max); @@ -106,7 +108,7 @@ namespace arch { * - array_t: minimum coordinates of the region in computational coords * - array_t: maximum coordinates of the region in computational coords */ - template + template auto ComputeNumInject(const SimulationParams& params, const Domain& domain, real_t number_density, @@ -156,7 +158,7 @@ namespace arch { if (xi < xsurf - ds or xi >= xsurf) { return ZERO; } else { - if constexpr (C == Coord::Cart) { + if constexpr (C == Coord::Cartesian) { return nmax * math::exp(-(xsurf - xi) / height); } else { raise::KernelError( @@ -170,7 +172,7 @@ namespace arch { if (xi < xsurf or xi >= xsurf + ds) { return ZERO; } else { - if constexpr (C == Coord::Cart) { + if constexpr (C == Coord::Cartesian) { return nmax * math::exp(-(xi - xsurf) / height); } else { return nmax * math::exp(-(xsurf / height) * (ONE - (xsurf / xi))); @@ -197,7 +199,7 @@ namespace arch { * @tparam ED1 Energy distribution type for species 1 * @tparam ED2 Energy distribution type for species 2 */ - template + template ED1, EnrgDistClass ED2> inline void InjectUniform(const SimulationParams& params, Domain& domain, const std::pair& species, @@ -205,13 +207,10 @@ namespace arch { real_t number_density, bool use_weights = false, const boundaries_t& box = {}) { - static_assert(M::is_metric, "M must be a metric class"); - static_assert(ED1::is_energy_dist, "ED1 must be an energy distribution class"); - static_assert(ED2::is_energy_dist, "ED2 must be an energy distribution class"); - raise::ErrorIf((M::CoordType != Coord::Cart) && (not use_weights), + raise::ErrorIf((M::CoordType != Coord::Cartesian) && (not use_weights), "Weights must be used for non-Cartesian coordinates", HERE); - raise::ErrorIf((M::CoordType == Coord::Cart) && use_weights, + raise::ErrorIf((M::CoordType == Coord::Cartesian) && use_weights, "Weights should not be used for Cartesian coordinates", HERE); raise::ErrorIf(params.template get("particles.use_weights") != use_weights, @@ -228,7 +227,7 @@ namespace arch { boundaries_t nonempty_box; for (auto d { 0u }; d < M::Dim; ++d) { if (d < box.size()) { - nonempty_box.push_back({ box[d].first, box[d].second }); + nonempty_box.emplace_back(box[d].first, box[d].second); } else { nonempty_box.push_back(Range::All); } @@ -246,7 +245,6 @@ namespace arch { kernel::UniformInjector_kernel( domain.species[species.first - 1], domain.species[species.second - 1], - nparticles, domain.index(), domain.mesh.metric, xi_min, @@ -255,10 +253,12 @@ namespace arch { energy_dists.second, ONE / params.template get("scales.V0"), domain.random_pool())); - domain.species[species.first - 1].set_npart( - domain.species[species.first - 1].npart() + nparticles); - domain.species[species.second - 1].set_npart( - domain.species[species.second - 1].npart() + nparticles); + for (auto sp : { species.first, species.second }) { + domain.species[sp - 1].set_npart( + domain.species[sp - 1].npart() + nparticles); + domain.species[sp - 1].set_counter( + domain.species[sp - 1].counter() + nparticles); + } } } @@ -272,13 +272,12 @@ namespace arch { * @param data Map containing all the coordinates/velocities of particles to inject * @param use_weights Boolean toggle to use weights or not */ - template + template inline void InjectGlobally(const Metadomain& global_domain, Domain& local_domain, spidx_t spidx, const std::map>& data, bool use_weights = false) { - static_assert(M::is_metric, "M must be a metric class"); const auto n_inject = data.at("ux1").size(); auto injector_kernel = kernel::GlobalInjector_kernel( local_domain.species[spidx - 1], @@ -290,6 +289,8 @@ namespace arch { const auto n_inj = injector_kernel.number_injected(); local_domain.species[spidx - 1].set_npart( local_domain.species[spidx - 1].npart() + n_inj); + local_domain.species[spidx - 1].set_counter( + local_domain.species[spidx - 1].counter() + n_inj); } /** @@ -308,7 +309,11 @@ namespace arch { * @tparam ED2 Energy distribution type for species 2 * @tparam SD Spatial distribution type */ - template + template ED1, + EnrgDistClass ED2, + SpatialDistClass SD> inline void InjectNonUniform(const SimulationParams& params, Domain& domain, const std::pair& species, @@ -317,14 +322,10 @@ namespace arch { real_t number_density, bool use_weights = false, const boundaries_t& box = {}) { - static_assert(M::is_metric, "M must be a metric class"); - static_assert(ED1::is_energy_dist, "ED1 must be an energy distribution class"); - static_assert(ED2::is_energy_dist, "ED2 must be an energy distribution class"); - static_assert(SD::is_spatial_dist, "SD must be a spatial distribution class"); - raise::ErrorIf((M::CoordType != Coord::Cart) && (not use_weights), + raise::ErrorIf((M::CoordType != Coord::Cartesian) && (not use_weights), "Weights must be used for non-Cartesian coordinates", HERE); - raise::ErrorIf((M::CoordType == Coord::Cart) && use_weights, + raise::ErrorIf((M::CoordType == Coord::Cartesian) && use_weights, "Weights should not be used for Cartesian coordinates", HERE); raise::ErrorIf( @@ -342,17 +343,21 @@ namespace arch { } { range_t cell_range; - if (box.size() == 0) { + if (box.empty()) { cell_range = domain.mesh.rangeActiveCells(); } else { - raise::ErrorIf(box.size() != M::Dim, + boundaries_t reduced_box(box); + if (reduced_box.size() > M::Dim) { + reduced_box.resize(M::Dim); + } + raise::ErrorIf(reduced_box.size() != M::Dim, "Box must have the same dimension as the mesh", HERE); boundaries_t incl_ghosts; for (auto d = 0; d < M::Dim; ++d) { - incl_ghosts.push_back({ false, false }); + incl_ghosts.emplace_back(false, false); } - const auto extent = domain.mesh.ExtentToRange(box, incl_ghosts); + const auto extent = domain.mesh.ExtentToRange(reduced_box, incl_ghosts); tuple_t x_min { 0 }, x_max { 0 }; for (auto d = 0; d < M::Dim; ++d) { x_min[d] = extent[d].first; diff --git a/src/archetypes/piston.h b/src/archetypes/piston.h new file mode 100644 index 000000000..66f7160f2 --- /dev/null +++ b/src/archetypes/piston.h @@ -0,0 +1,168 @@ +/** + * @file archetypes/piston.h + * @brief Piston functions for implementing the piston in the CustomPrtlUpdate in pgran + * @implements + * - arch::PistonUpdate<> -> void + * - arch::CrossesPiston<> -> Bool + * @namespaces: + * - arch:: + */ + +#ifndef ARCHETYPES_PISTON_H +#define ARCHETYPES_PISTON_H + +#include "global.h" + +#include "traits/metric.h" + +#include "framework/containers/particles.h" + +/* -------------------------------------------------------------------------- */ +/* Local macros (same as in particle_pusher_sr.hpp) */ +/* -------------------------------------------------------------------------- */ +#define from_Xi_to_i(XI, I) \ + { \ + (I) = static_cast(((XI) + 1)) - 1; \ + } + +#define from_Xi_to_i_di(XI, I, DI) \ + { \ + from_Xi_to_i((XI), (I)); \ + (DI) = static_cast((XI)) - static_cast(I); \ + } + +#define i_di_to_Xi(I, DI) (static_cast((I)) + static_cast((DI))) + +/* -------------------------------------------------------------------------- */ + +namespace arch { + + /** + * @brief Checks whether a particle reflects off a moving piston, called after particle has been moved by regular pusher + * + * @param p Index of particle + * @param dt Timestep + * @param particles Particle data arrays + * @param metric Metric object for coordinate transformations + * @param piston_position Position of the piston at the start of timestep in global coordinates + * @param piston_v Velocity of piston at current timestep + * @param is_left Is piston on the left side of the box or right side of the box + */ + template + Inline bool CrossesPiston(prtlidx_t p, + real_t dt, + const ntt::ParticleArrays& particles, + const M& metric, + real_t piston_position, + real_t piston_v, + bool is_left) { + const real_t x1_Cd = i_di_to_Xi(particles.i1(p), particles.dx1(p)); + // x1_Cd_wallmove is not the actual particle coordinate + // it is particle position minus how much the wall has moved in this + // timestep This is a computational trick + const real_t x1_Cd_wallmove = + x1_Cd - metric.template transform<1, Idx::XYZ, Idx::U>({}, piston_v) * dt; + const real_t x1_Ph_wallmove = metric.template convert<1, Crd::Cd, Crd::Ph>( + x1_Cd_wallmove); + + return is_left ? piston_position > x1_Ph_wallmove + : piston_position < x1_Ph_wallmove; + } + + /** + * @brief Updates particle position and velocity if it reflects off a moving + * piston, called to correct patticle position + * + * @param p Index of particle + * @param dt Timestep + * @param particles Particle data arrays + * @param metric Metric object for coordinate transformations + * @param piston_position Position of the piston at the start of timestep in global coordinates + * @param piston_v Velocity of piston at current timestep + * @param massive Whether the particle is massive or massless (e.g. photon) + */ + template + Inline void Piston(prtlidx_t p, + real_t dt, + const ntt::ParticleArrays& particles, + const M& metric, + real_t piston_position, + real_t piston_v, + bool massive) { + + // check if particle actually crosses the piston, if not return + if (!CrossesPiston(p, dt, particles, metric, piston_position, piston_v, true)) { + return; + } + // step 1: calculate the particle 3 velocity + const real_t gamma_p { + massive ? U2GAMMA(particles.ux1(p), particles.ux2(p), particles.ux3(p)) + : NORM(particles.ux1(p), particles.ux2(p), particles.ux3(p)) + }; + + const real_t beta_x_p = particles.ux1(p) / gamma_p; + const real_t xp_prev = i_di_to_Xi(particles.i1_prev(p), particles.dx1_prev(p)); + + const real_t piston_position_local = metric.template convert<1, Crd::Ph, Crd::Cd>( + piston_position); + int i_w; + prtldx_t dx_w; + + from_Xi_to_i_di(piston_position_local, i_w, dx_w); + + const real_t piston_gamma = ONE / math::sqrt(ONE - SQR(piston_v)); + + // step 2: calculate the time for the particle to reach the piston + const int delta_i1_to_piston = (i_w - particles.i1_prev(p)); + const prtldx_t delta_dx1_to_piston = (dx_w - particles.dx1_prev(p)); + const real_t dx_to_piston = i_di_to_Xi(delta_i1_to_piston, delta_dx1_to_piston); + + const real_t dt_to_piston = dx_to_piston / + metric.template transform<1, Idx::XYZ, Idx::U>( + {}, + beta_x_p - piston_v); + // step 3: calculate remaining time after the collision + const real_t remaining_dt = dt - dt_to_piston; + + // step 4: update the particle's velocity and position after the collision + particles.ux1(p) = -SQR(piston_gamma) * + ((ONE + SQR(piston_v)) * particles.ux1(p) - + TWO * piston_v * gamma_p); + + const real_t remaining_dt_inv_energy { + massive ? (remaining_dt / + U2GAMMA(particles.ux1(p), particles.ux2(p), particles.ux3(p))) + : (remaining_dt / + NORM(particles.ux1(p), particles.ux2(p), particles.ux3(p))) + }; + // define piston integer and fractional coordinate + int i_w_coll = i_w; + prtldx_t dx_w_coll = dx_w + metric.template transform<1, Idx::XYZ, Idx::U>( + {}, + piston_v) * + dt_to_piston; + + i_w_coll += static_cast(dx_w_coll >= ONE) - + static_cast(dx_w_coll < ZERO); + dx_w_coll -= static_cast(dx_w_coll >= ONE); + dx_w_coll += static_cast(dx_w_coll < ZERO); + + particles.i1(p) = i_w_coll; + particles.dx1( + p) = metric.template transform<1, Idx::XYZ, Idx::U>({}, particles.ux1(p)) * + remaining_dt_inv_energy + + dx_w_coll; + + particles.i1(p) += static_cast(particles.dx1(p) >= ONE) - + static_cast(particles.dx1(p) < ZERO); + particles.dx1(p) -= static_cast(particles.dx1(p) >= ONE); + particles.dx1(p) += static_cast(particles.dx1(p) < ZERO); + } + +} // namespace arch + +#undef from_Xi_to_i_di +#undef from_Xi_to_i +#undef i_di_to_Xi + +#endif // ARCHETYPES_UTILS_H diff --git a/src/archetypes/problem_generator.h b/src/archetypes/problem_generator.h deleted file mode 100644 index da116672d..000000000 --- a/src/archetypes/problem_generator.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @file archetypes/problem_generator.hpp - * @brief Base class for all problem generators - * @implements - * - arch::ProblemGenerator<> - * @namespace - * - arch:: - * @note - * To have easier access to variables inside ProblemGenerator in children - * classes, one should simply add: - * ```c++ - * using ProblemGenerator::D; - * using ProblemGenerator::C; - * using ProblemGenerator::params; - * ``` - * in the child class. This will allow to access these variables without - * the need to use `this->` or `ProblemGenerator::` prefix. - */ - -#ifndef ARCHETYPES_PROBLEM_GENERATOR_HPP -#define ARCHETYPES_PROBLEM_GENERATOR_HPP - -#include "enums.h" -#include "global.h" - -#include "framework/parameters.h" - -namespace arch { - using namespace ntt; - - template - struct ProblemGenerator { - static_assert(M::is_metric, "M must be a metric class"); - static constexpr bool is_pgen { true }; - static constexpr Dimension D { M::Dim }; - static constexpr Coord C { M::CoordType }; - - const SimulationParams& params; - - ProblemGenerator(const SimulationParams& p) : params { p } {} - - ~ProblemGenerator() = default; - }; - -} // namespace arch - -#endif // ARCHETYPES_PROBLEM_GENERATOR_HPP diff --git a/src/archetypes/spatial_dist.h b/src/archetypes/spatial_dist.h index 68477208c..e5022c5db 100644 --- a/src/archetypes/spatial_dist.h +++ b/src/archetypes/spatial_dist.h @@ -1,13 +1,12 @@ /** - * @file archetypes/spatial_dist.hpp + * @file archetypes/spatial_dist.h * @brief Spatial distribution class passed to injectors * @implements - * - arch::SpatialDistribution<> - * - arch::Uniform<> : arch::SpatialDistribution<> - * - arch::Replenish<> : arch::SpatialDistribution<> - * - arch::ReplenishUniform<> : arch::SpatialDistribution<> - * @namespace - * - arch:: + * - arch::spatial_dist::Uniform<> + * - arch::spatial_dist::Replenish<> + * - arch::spatial_dist::ReplenishUniform<> + * @namespaces: + * - arch::spatial_dist:: * @note * Instances of these functors take coordinate position in code units * and return a number between 0 and 1 that represents the spatial distribution @@ -16,39 +15,28 @@ #ifndef ARCHETYPES_SPATIAL_DIST_HPP #define ARCHETYPES_SPATIAL_DIST_HPP -#include "enums.h" #include "global.h" #include "arch/kokkos_aliases.h" +#include "traits/metric.h" #include "utils/error.h" #include "utils/numeric.h" -namespace arch { +namespace arch::spatial_dist { using namespace ntt; - template - struct SpatialDistribution { - static constexpr bool is_spatial_dist { true }; - static_assert(M::is_metric, "M must be a metric class"); + template + struct Uniform { - SpatialDistribution(const M& metric) : metric { metric } {} - - protected: - const M metric; - }; - - template - struct Uniform : public SpatialDistribution { - Uniform(const M& metric) : SpatialDistribution { metric } {} - - Inline auto operator()(const coord_t&) const -> real_t { + Inline auto operator()(const coord_t&) const -> real_t { return ONE; } }; - template - struct Replenish : public SpatialDistribution { - using SpatialDistribution::metric; + template + struct Replenish { + const M metric; + const ndfield_t density; const idx_t idx; @@ -60,7 +48,7 @@ namespace arch { idx_t idx, const T& target_density, real_t target_max_density) - : SpatialDistribution { metric } + : metric { metric } , density { density } , idx { idx } , target_density { target_density } @@ -93,9 +81,9 @@ namespace arch { } }; - template - struct ReplenishUniform : public SpatialDistribution { - using SpatialDistribution::metric; + template + struct ReplenishUniform { + const M metric; const ndfield_t density; const idx_t idx; @@ -105,7 +93,7 @@ namespace arch { const ndfield_t& density, idx_t idx, real_t target_density) - : SpatialDistribution { metric } + : metric { metric } , density { density } , idx { idx } , target_density { target_density } {} @@ -136,6 +124,6 @@ namespace arch { } }; -} // namespace arch +} // namespace arch::spatial_dist #endif // ARCHETYPES_SPATIAL_DIST_HPP diff --git a/src/archetypes/utils.h b/src/archetypes/utils.h index b1a29cb71..ff88d5385 100644 --- a/src/archetypes/utils.h +++ b/src/archetypes/utils.h @@ -4,6 +4,7 @@ * @implements * - arch::InjectUniformMaxwellians<> -> void * - arch::InjectUniformMaxwellian<> -> void + * - arch::ComputeMomentWithSpecies<> -> void * @namespaces: * - arch:: */ @@ -14,10 +15,14 @@ #include "enums.h" #include "global.h" +#include "traits/metric.h" + #include "archetypes/energy_dist.h" +#include "archetypes/field_setter.h" #include "archetypes/particle_injector.h" #include "framework/domain/domain.h" -#include "framework/parameters.h" +#include "framework/parameters/parameters.h" +#include "kernels/particle_moments.hpp" #include @@ -34,8 +39,11 @@ namespace arch { * @param drift_four_vels Pair of drift four-velocities for the two species * @param use_weights Use weights * @param box Region to inject the particles in global coords (or empty for the whole domain) + * + * @tparam S Simulation engine type + * @tparam M Metric type */ - template + template inline void InjectUniformMaxwellians( const SimulationParams& params, Domain& domain, @@ -45,21 +53,20 @@ namespace arch { const std::pair, std::vector>& drift_four_vels = {{ ZERO, ZERO, ZERO }, { ZERO, ZERO, ZERO }}, bool use_weights = false, const boundaries_t& box = {}) { - static_assert(M::is_metric, "M must be a metric class"); const auto mass_1 = domain.species[species.first - 1].mass(); const auto mass_2 = domain.species[species.second - 1].mass(); const auto temperature_1 = temperatures.first / mass_1; const auto temperature_2 = temperatures.second / mass_2; - const auto maxwellian_1 = arch::Maxwellian(domain.mesh.metric, - domain.random_pool(), - temperature_1, - drift_four_vels.first); - const auto maxwellian_2 = arch::Maxwellian(domain.mesh.metric, - domain.random_pool(), - temperature_2, - drift_four_vels.second); + const auto maxwellian_1 = arch::energy_dist::Maxwellian( + domain.random_pool(), + temperature_1, + drift_four_vels.first); + const auto maxwellian_2 = arch::energy_dist::Maxwellian( + domain.random_pool(), + temperature_2, + drift_four_vels.second); arch::InjectUniform( params, @@ -82,8 +89,11 @@ namespace arch { * @param drift_four_vels Pair of drift four-velocities for the two species * @param use_weights Use weights * @param box Region to inject the particles in global coords (or empty for the whole domain) + * + * @tparam S Simulation engine type + * @tparam M Metric type */ - template + template inline void InjectUniformMaxwellian( const SimulationParams& params, Domain& domain, @@ -93,7 +103,6 @@ namespace arch { const std::pair, std::vector>& drift_four_vels = {{ ZERO, ZERO, ZERO }, { ZERO, ZERO, ZERO }}, bool use_weights = false, const boundaries_t& box = {}) { - static_assert(M::is_metric, "M must be a metric class"); InjectUniformMaxwellians(params, domain, @@ -105,6 +114,87 @@ namespace arch { box); } + /** + * @brief Computes the moment of the distribution function for a given set of species and saves it to the provided buffer + * + * @param params Simulation parameters + * @param domain Domain object + * @param species Vector of species indices to include in the calculation + * @param buffer Buffer to save the computed moment (must be preallocated with the correct size) + * @param components Vector of field components to compute (e.g. {} for N, {0} + * {1} {2} for V, {0, 1} for T, etc., default: empty, i.e. scalar) + * @param buffer_idx Index of the field component in the buffer to save the result to + * @param window Window size for smoothing (in number of cells, default: 0, i.e. no smoothing) + * + * @tparam S Simulation engine type + * @tparam M Metric type + * @tparam F Field ID for the moment to compute (e.g. FldsID::N, FldsID::T, etc.) + * @tparam N Last dimension of the buffer (e.g. 3 or 6) + */ + template + inline void ComputeMomentWithSpecies( + const SimulationParams& params, + Domain& domain, + const std::vector& species, + ndfield_t& buffer, + const std::vector& components = {}, + idx_t buffer_idx = 0u, + unsigned short window = 0u) { + const auto ni2 = domain.mesh.n_active(in::x2); + const auto inv_n0 = ONE / params.template get("scales.n0"); + const auto use_weights = params.template get("particles.use_weights"); + + Kokkos::deep_copy(buffer, ZERO); + auto scatter_buff = Kokkos::Experimental::create_scatter_view(buffer); + for (const auto sp : species) { + const auto& prtl_spec = domain.species[sp - 1]; + Kokkos::parallel_for( + "ComputeMoment", + prtl_spec.rangeActiveParticles(), + kernel::ParticleMoments_kernel(components, + scatter_buff, + buffer_idx, + prtl_spec.i1, + prtl_spec.i2, + prtl_spec.i3, + prtl_spec.dx1, + prtl_spec.dx2, + prtl_spec.dx3, + prtl_spec.ux1, + prtl_spec.ux2, + prtl_spec.ux3, + prtl_spec.phi, + prtl_spec.weight, + prtl_spec.tag, + prtl_spec.mass(), + prtl_spec.charge(), + use_weights, + domain.mesh.metric, + domain.mesh.flds_bc(), + ni2, + inv_n0, + window)); + } + Kokkos::Experimental::contribute(buffer, scatter_buff); + } + + template + inline void UpdateEMFields(Domain& domain, const F& fieldsetter) { + if constexpr (S == SimEngine::SRPIC) { + Kokkos::deep_copy(domain.fields.bckp, domain.fields.em); + Kokkos::parallel_for( + "UpdateEMFields", + domain.mesh.rangeActiveCells(), + arch::CustomSetEMFields_kernel(domain.mesh.metric, + domain.fields.em, + domain.fields.bckp, + fieldsetter)); + // comm here + } else { + raise::Error("Custom fieldsetter is only implemented for SRPIC", HERE); + } + } + } // namespace arch #endif // ARCHETYPES_UTILS_H diff --git a/src/engines/CMakeLists.txt b/src/engines/CMakeLists.txt index 4cef18630..4d2a751f8 100644 --- a/src/engines/CMakeLists.txt +++ b/src/engines/CMakeLists.txt @@ -4,9 +4,7 @@ # # @sources: # -# * engine_printer.cpp -# * engine_init.cpp -# * engine_run.cpp +# * reporter.cpp # # @includes: # @@ -32,20 +30,20 @@ # ------------------------------ set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) -set(SOURCES ${SRC_DIR}/engine_printer.cpp ${SRC_DIR}/engine_init.cpp - ${SRC_DIR}/engine_run.cpp) -add_library(ntt_engines ${SOURCES}) +set(SOURCES ${SRC_DIR}/reporter.cpp) +set(ENGINES ntt_engines${pgen_suffix}) +add_library(${ENGINES} ${SOURCES}) set(libs ntt_global ntt_framework ntt_metrics ntt_archetypes ntt_kernels - ntt_pgen) + ntt_pgen${pgen_suffix}) if(${output}) list(APPEND libs ntt_output) endif() -add_dependencies(ntt_engines ${libs}) -target_link_libraries(ntt_engines PUBLIC ${libs}) -target_compile_definitions(ntt_engines PRIVATE PGEN=\"${PGEN}\") +add_dependencies(${ENGINES} ${libs}) +target_link_libraries(${ENGINES} PUBLIC ${libs}) +target_compile_definitions(${ENGINES} PUBLIC PGEN=\"${PGEN}\") target_include_directories( - ntt_engines + ${ENGINES} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../) diff --git a/src/engines/engine.hpp b/src/engines/engine.hpp index 17103f1de..b20e163ca 100644 --- a/src/engines/engine.hpp +++ b/src/engines/engine.hpp @@ -3,16 +3,11 @@ * @brief Base simulation class which just initializes the metadomain * @implements * - ntt::Engine<> - * @cpp: - * - engine_init.cpp - * - engine_printer.cpp * @namespaces: * - ntt:: * @macros: - * - MPI - * - DEBUG + * - MPI_ENABLED * - OUTPUT_ENABLED - * - CUDA_ENABLED */ #ifndef ENGINES_ENGINE_H @@ -21,22 +16,36 @@ #include "enums.h" #include "global.h" -#include "arch/traits.h" -#include "utils/error.h" +#include "arch/mpi_aliases.h" +#include "traits/metric.h" +#include "traits/pgen.h" +#include "utils/diag.h" +#include "utils/reporter.h" #include "utils/timer.h" -#include "utils/toml.h" +#include "archetypes/field_setter.h" +#include "engines/reporter.h" #include "framework/containers/species.h" +#include "framework/domain/domain.h" #include "framework/domain/metadomain.h" -#include "framework/parameters.h" +#include "framework/parameters/parameters.h" #include "pgen.hpp" +#include + +#if defined(OUTPUT_ENABLED) + #include +#endif + #include +#include +#include + #if defined(OUTPUT_ENABLED) #include - #include + #include #endif // OUTPUT_ENABLED #if defined(MPI_ENABLED) @@ -48,10 +57,13 @@ namespace ntt { - template + template + concept PGenClass = requires(SimulationParams& p, Metadomain& m) { + PG { p, m }; + }; + + template class Engine { - static_assert(M::is_metric, "template arg for Engine class has to be a metric"); - static_assert(user::PGen::is_pgen, "unrecognized problem generator"); protected: #if defined(OUTPUT_ENABLED) @@ -76,14 +88,7 @@ namespace ntt { timestep_t step; public: - static constexpr bool pgen_is_ok { - traits::check_compatibility::value(user::PGen::engines) and - traits::check_compatibility::value(user::PGen::metrics) and - traits::check_compatibility::value(user::PGen::dimensions) - }; - static constexpr Dimension D { M::Dim }; - static constexpr bool is_engine { true }; Engine(const SimulationParams& params) : m_params { params } @@ -108,20 +113,256 @@ namespace ntt { , start_step { m_params.get("checkpoint.start_step") } , start_time { m_params.get("checkpoint.start_time") } , time { start_time } - , step { start_step } { - raise::ErrorIf(not pgen_is_ok, "Problem generator is not compatible with the picked engine/metric/dimension", HERE); - } - - ~Engine() = default; + , step { start_step } {} void init(); void print_report() const; + virtual ~Engine() = default; virtual void step_forward(timer::Timers&, Domain&) = 0; void run(); + + auto engineParams() const -> prm::Parameters { + auto parameters = prm::Parameters {}; + parameters.set("dt", static_cast(dt)); + parameters.set("time", static_cast(time)); + return parameters; + } }; + template + void Engine::init() { + m_metadomain.InitStatsWriter(m_params, is_resuming); +#if defined(OUTPUT_ENABLED) + m_metadomain.InitWriter(&m_adios, m_params); + m_metadomain.InitCheckpointWriter(&m_adios, m_params); +#endif + logger::Checkpoint("Initializing Engine", HERE); + if (not is_resuming) { + // start a new simulation with initial conditions + logger::Checkpoint("Loading initial conditions", HERE); + if constexpr (::traits::pgen::HasInitFlds>) { + logger::Checkpoint("Initializing fields from problem generator", HERE); + m_metadomain.runOnLocalDomains([&](auto& loc_dom) { + Kokkos::parallel_for( + "InitFields", + loc_dom.mesh.rangeActiveCells(), + arch::SetEMFields_kernel { + loc_dom.fields.em, + m_pgen.init_flds, + loc_dom.mesh.metric }); + }); + } + if constexpr (::traits::pgen::HasInitPrtls, Domain>) { + logger::Checkpoint("Initializing particles from problem generator", HERE); + m_metadomain.runOnLocalDomains([&](auto& loc_dom) { + m_pgen.InitPrtls(loc_dom); + }); + } + } else { +#if defined(OUTPUT_ENABLED) + // read simulation data from the checkpoint + raise::ErrorIf( + m_params.template get("checkpoint.start_step") == 0, + "Resuming simulation from a checkpoint requires a valid start_step", + HERE); + logger::Checkpoint("Resuming simulation from a checkpoint", HERE); + m_metadomain.ContinueFromCheckpoint(&m_adios, m_params); +#else + raise::Error( + "Resuming simulation from a checkpoint requires -D output=ON", + HERE); +#endif + } + print_report(); + } + + template + void Engine::print_report() const { + const auto colored_stdout = m_params.template get( + "diagnostics.colored_stdout"); + std::string report; + CallOnce( + [&](auto& metadomain, auto& params) { + report += reporter::Backend(); + + report += ReportSimulationConfig(params, + S, + Metric(M::MetricType), + dt, + runtime, + max_steps, + metadomain.ndomains_per_dim(), + metadomain.ndomains()); + const auto pgen_name = std::string(PGEN); + report += ReportPgenConfig>( + m_pgen, + pgen_name); + if (metadomain.species_params().size() > 0) { + report += "\n"; + reporter::AddCategory(report, 4, "Particles"); + } + for (const auto& species : metadomain.species_params()) { + report += species.Report(); + } + report.pop_back(); + }, + m_metadomain, + m_params); + info::Print(report, colored_stdout); + + report = "\n"; + CallOnce([&]() { + reporter::AddCategory(report, 4, "Domains"); + report.pop_back(); + }); + info::Print(report, colored_stdout); + + for (unsigned int idx { 0 }; idx < m_metadomain.ndomains(); ++idx) { + auto is_local = false; + for (const auto& lidx : m_metadomain.l_subdomain_indices()) { + is_local |= (idx == lidx); + } + if (is_local) { + const auto& domain = m_metadomain.subdomain(idx); + report = domain.Report(); + if (idx == m_metadomain.ndomains() - 1) { + report += "\n\n"; + } + info::Print(report, colored_stdout, true, false); + } +#if defined(MPI_ENABLED) + MPI_Barrier(MPI_COMM_WORLD); +#endif + } + } + + template + void Engine::run() { + init(); + + auto timers = timer::Timers { + { "FieldSolver", + "CurrentFiltering", "CurrentDeposit", + "ParticlePusher", "FieldBoundaries", + "ParticleBoundaries", "Communications", + "Injector", "Custom", + "ParticleSort", "Output", + "Checkpoint" }, + []() { + Kokkos::fence(); + }, + m_params.get("diagnostics.blocking_timers") + }; + const auto diag_interval = m_params.template get( + "diagnostics.interval"); + + auto time_history = pbar::DurationHistory { 1000 }; + const auto clear_interval = m_params.template get( + "particles.clear_interval"); + + // main algorithm loop + while (step < max_steps) { + // run the engine-dependent algorithm step + m_metadomain.runOnLocalDomains([&timers, this](auto& dom) { + step_forward(timers, dom); + }); + // poststep (if defined) + if constexpr ( + ::traits::pgen::HasCustomPostStep>) { + timers.start("Custom"); + m_metadomain.runOnLocalDomains([&timers, this](auto& dom) { + m_pgen.CustomPostStep(step, time, dom); + }); + timers.stop("Custom"); + } + auto print_prtl_clear = (clear_interval > 0 and + step % clear_interval == 0 and step > 0); + + // advance time & step + time += dt; + ++step; + + auto print_output = false; + auto print_checkpoint = false; +#if defined(OUTPUT_ENABLED) + timers.start("Output"); + if constexpr ( + ::traits::pgen::HasCustomFieldOutput>) { + auto lambda_custom_field_output = [&](const std::string& name, + ndfield_t& buff, + cellidx_t idx, + timestep_t step, + simtime_t time, + const Domain& dom) { + m_pgen.CustomFieldOutput(name, buff, idx, step, time, dom); + }; + print_output &= m_metadomain.Write(m_params, + step, + step - 1, + time, + time - dt, + lambda_custom_field_output); + } else { + print_output &= m_metadomain.Write(m_params, step, step - 1, time, time - dt); + } + if constexpr ( + ::traits::pgen::HasCustomStatOutput>) { + auto lambda_custom_stat = [&](const std::string& name, + timestep_t step, + simtime_t time, + const Domain& dom) -> real_t { + return m_pgen.CustomStat(name, step, time, dom); + }; + print_output &= m_metadomain.WriteStats(m_params, + step, + step - 1, + time, + time - dt, + lambda_custom_stat); + } else { + print_output &= m_metadomain.WriteStats(m_params, + step, + step - 1, + time, + time - dt); + } + timers.stop("Output"); + + timers.start("Checkpoint"); + print_checkpoint = m_metadomain.WriteCheckpoint(m_params, + step, + step - 1, + time, + time - dt); + timers.stop("Checkpoint"); +#endif + + // advance time_history + time_history.tick(); + // print timestep report + if (diag_interval > 0 and step % diag_interval == 0) { + diag::printDiagnostics( + step - 1, + max_steps, + time - dt, + dt, + timers, + time_history, + m_metadomain.l_ncells(), + m_metadomain.species_labels(), + m_metadomain.l_npart_perspec(), + m_metadomain.l_maxnpart_perspec(), + print_prtl_clear, + print_output, + print_checkpoint, + m_params.get("diagnostics.colored_stdout")); + } + timers.resetAll(); + } + } + } // namespace ntt #endif // ENGINES_ENGINE_H diff --git a/src/engines/engine_init.cpp b/src/engines/engine_init.cpp deleted file mode 100644 index f98b117dd..000000000 --- a/src/engines/engine_init.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "enums.h" -#include "global.h" - -#include "arch/traits.h" - -#include "archetypes/field_setter.h" -#include "framework/specialization_registry.h" - -#include "engines/engine.hpp" - -#include - -#include - -namespace ntt { - - template - void Engine::init() { - if constexpr (pgen_is_ok) { - m_metadomain.InitStatsWriter(m_params, is_resuming); -#if defined(OUTPUT_ENABLED) - m_metadomain.InitWriter(&m_adios, m_params); - m_metadomain.InitCheckpointWriter(&m_adios, m_params); -#endif - logger::Checkpoint("Initializing Engine", HERE); - if (not is_resuming) { - // start a new simulation with initial conditions - logger::Checkpoint("Loading initial conditions", HERE); - if constexpr ( - traits::has_member>::value) { - logger::Checkpoint("Initializing fields from problem generator", HERE); - m_metadomain.runOnLocalDomains([&](auto& loc_dom) { - Kokkos::parallel_for( - "InitFields", - loc_dom.mesh.rangeActiveCells(), - arch::SetEMFields_kernel { - loc_dom.fields.em, - m_pgen.init_flds, - loc_dom.mesh.metric }); - }); - } - if constexpr ( - traits::has_member>::value) { - logger::Checkpoint("Initializing particles from problem generator", HERE); - m_metadomain.runOnLocalDomains([&](auto& loc_dom) { - m_pgen.InitPrtls(loc_dom); - }); - } - } else { -#if defined(OUTPUT_ENABLED) - // read simulation data from the checkpoint - raise::ErrorIf( - m_params.template get("checkpoint.start_step") == 0, - "Resuming simulation from a checkpoint requires a valid start_step", - HERE); - logger::Checkpoint("Resuming simulation from a checkpoint", HERE); - m_metadomain.ContinueFromCheckpoint(&m_adios, m_params); -#else - raise::Error( - "Resuming simulation from a checkpoint requires -D output=ON", - HERE); -#endif - } - } - print_report(); - } - -#define ENGINE_INIT(S, M, D) template class Engine>; - - NTT_FOREACH_SPECIALIZATION(ENGINE_INIT) - -#undef ENGINE_INIT - -} // namespace ntt diff --git a/src/engines/engine_printer.cpp b/src/engines/engine_printer.cpp deleted file mode 100644 index f3d6f3b38..000000000 --- a/src/engines/engine_printer.cpp +++ /dev/null @@ -1,500 +0,0 @@ -#include "enums.h" -#include "global.h" - -#include "arch/directions.h" -#include "arch/mpi_aliases.h" -#include "utils/colors.h" -#include "utils/formatting.h" - -#include "framework/specialization_registry.h" - -#include "engines/engine.hpp" - -#if defined(CUDA_ENABLED) - #include -#elif defined(HIP_ENABLED) - #include -#endif - -#if defined(OUTPUT_ENABLED) - #include -#endif - -#include -#include -#include - -namespace ntt { - - namespace { - void add_header(std::string& report, - const std::vector& lines, - const std::vector& colors) { - report += fmt::format("%s╔%s╗%s\n", - color::BRIGHT_BLACK, - fmt::repeat("═", 58).c_str(), - color::RESET); - for (auto i { 0u }; i < lines.size(); ++i) { - report += fmt::format("%s║%s %s%s%s%s%s║%s\n", - color::BRIGHT_BLACK, - color::RESET, - colors[i], - lines[i].c_str(), - color::RESET, - fmt::repeat(" ", 57 - lines[i].size()).c_str(), - color::BRIGHT_BLACK, - color::RESET); - } - report += fmt::format("%s╚%s╝%s\n", - color::BRIGHT_BLACK, - fmt::repeat("═", 58).c_str(), - color::RESET); - } - - void add_category(std::string& report, unsigned short indent, const char* name) { - report += fmt::format("%s%s%s%s\n", - std::string(indent, ' ').c_str(), - color::BLUE, - name, - color::RESET); - } - - void add_subcategory(std::string& report, unsigned short indent, const char* name) { - report += fmt::format("%s%s-%s %s:\n", - std::string(indent, ' ').c_str(), - color::BRIGHT_BLACK, - color::RESET, - name); - } - - void add_label(std::string& report, unsigned short indent, const char* label) { - report += fmt::format("%s%s\n", std::string(indent, ' ').c_str(), label); - } - - template - void add_param(std::string& report, - unsigned short indent, - const char* name, - const char* format, - Args... args) { - report += fmt::format("%s%s-%s %s: %s%s%s\n", - std::string(indent, ' ').c_str(), - color::BRIGHT_BLACK, - color::RESET, - name, - color::BRIGHT_YELLOW, - fmt::format(format, args...).c_str(), - color::RESET); - } - - template - void add_unlabeled_param(std::string& report, - unsigned short indent, - const char* name, - const char* format, - Args... args) { - report += fmt::format("%s%s: %s%s%s\n", - std::string(indent, ' ').c_str(), - name, - color::BRIGHT_YELLOW, - fmt::format(format, args...).c_str(), - color::RESET); - } - - auto bytes_to_human_readable( - std::size_t bytes) -> std::pair { - const std::vector units { "B", "KB", "MB", "GB", "TB" }; - idx_t unit_idx = 0; - auto size = static_cast(bytes); - while ((size >= 1024.0) and (unit_idx < units.size() - 1)) { - size /= 1024.0; - ++unit_idx; - } - return { size, units[unit_idx] }; - } - } // namespace - - template - void Engine::print_report() const { - const auto colored_stdout = m_params.template get( - "diagnostics.colored_stdout"); - std::string report = ""; - CallOnce( - [&](auto& metadomain, auto& params) { -#if defined(MPI_ENABLED) - int mpi_v, mpi_subv; - MPI_Get_version(&mpi_v, &mpi_subv); - const std::string mpi_version = fmt::format("%d.%d", mpi_v, mpi_subv); -#else // not MPI_ENABLED - const std::string mpi_version = "OFF"; -#endif // MPI_ENABLED - - const auto entity_version = "Entity v" + std::string(ENTITY_VERSION); - const auto hash = std::string(ENTITY_GIT_HASH); - const auto pgen = std::string(PGEN); - const auto nspec = metadomain.species_params().size(); - const auto precision = (sizeof(real_t) == 4) ? "single" : "double"; - -#if defined(__clang__) - const std::string ccx = "Clang/LLVM " __clang_version__; -#elif defined(__ICC) || defined(__INTEL_COMPILER) - const std::string ccx = "Intel ICC/ICPC " __VERSION__; -#elif defined(__GNUC__) || defined(__GNUG__) - const std::string ccx = "GNU GCC/G++ " __VERSION__; -#elif defined(__HP_cc) || defined(__HP_aCC) - const std::string ccx = "Hewlett-Packard C/aC++ " __HP_aCC; -#elif defined(__IBMC__) || defined(__IBMCPP__) - const std::string ccx = "IBM XL C/C++ " __IBMCPP__; -#elif defined(_MSC_VER) - const std::string ccx = "Microsoft Visual Studio " _MSC_VER; -#else - const std::string ccx = "Unknown compiler"; -#endif - std::string cpp_standard; - if (__cplusplus == 202101L) { - cpp_standard = "C++23"; - } else if (__cplusplus == 202002L) { - cpp_standard = "C++20"; - } else if (__cplusplus == 201703L) { - cpp_standard = "C++17"; - } else if (__cplusplus == 201402L) { - cpp_standard = "C++14"; - } else if (__cplusplus == 201103L) { - cpp_standard = "C++11"; - } else if (__cplusplus == 199711L) { - cpp_standard = "C++98"; - } else { - cpp_standard = "pre-standard " + std::to_string(__cplusplus); - } - -#if defined(CUDA_ENABLED) - int cuda_v; - cudaRuntimeGetVersion(&cuda_v); - const auto major { cuda_v / 1000 }; - const auto minor { cuda_v % 1000 / 10 }; - const auto patch { cuda_v % 10 }; - const auto cuda_version = fmt::format("%d.%d.%d", major, minor, patch); -#elif defined(HIP_ENABLED) - int hip_v; - auto status = hipDriverGetVersion(&hip_v); - raise::ErrorIf(status != hipSuccess, - "hipDriverGetVersion failed with error code %d", - HERE); - const auto major { hip_v / 10000000 }; - const auto minor { (hip_v % 10000000) / 100000 }; - const auto patch { hip_v % 100000 }; - const auto hip_version = fmt::format("%d.%d.%d", major, minor, patch); -#endif - - const auto kokkos_version = fmt::format("%d.%d.%d", - KOKKOS_VERSION / 10000, - KOKKOS_VERSION / 100 % 100, - KOKKOS_VERSION % 100); - -#if defined(OUTPUT_ENABLED) - const std::string adios2_version = fmt::format("%d.%d.%d", - ADIOS2_VERSION / 10000, - ADIOS2_VERSION / 100 % 100, - ADIOS2_VERSION % 100); -#else // not OUTPUT_ENABLED - const std::string adios2_version = "OFF"; -#endif - -#if defined(DEBUG) - const std::string dbg = "ON"; -#else // not DEBUG - const std::string dbg = "OFF"; -#endif - - report += "\n\n"; - add_header(report, { entity_version }, { color::BRIGHT_GREEN }); - report += "\n"; - - /* - * Backend - */ - add_category(report, 4, "Backend"); - add_param(report, 4, "Build hash", "%s", hash.c_str()); - add_param(report, 4, "CXX", "%s [%s]", ccx.c_str(), cpp_standard.c_str()); -#if defined(CUDA_ENABLED) - add_param(report, 4, "CUDA", "%s", cuda_version.c_str()); -#elif defined(HIP_VERSION) - add_param(report, 4, "HIP", "%s", hip_version.c_str()); -#endif - add_param(report, 4, "MPI", "%s", mpi_version.c_str()); -#if defined(MPI_ENABLED) && defined(DEVICE_ENABLED) - #if defined(GPU_AWARE_MPI) - const std::string gpu_aware_mpi = "ON"; - #else - const std::string gpu_aware_mpi = "OFF"; - #endif - add_param(report, 4, "GPU-aware MPI", "%s", gpu_aware_mpi.c_str()); -#endif - add_param(report, 4, "Kokkos", "%s", kokkos_version.c_str()); - add_param(report, 4, "ADIOS2", "%s", adios2_version.c_str()); - add_param(report, 4, "Precision", "%s", precision); - add_param(report, 4, "Debug", "%s", dbg.c_str()); - report += "\n"; - - /* - * Compilation flags - */ - add_category(report, 4, "Compilation flags"); -#if defined(SINGLE_PRECISION) - add_param(report, 4, "SINGLE_PRECISION", "%s", "ON"); -#else - add_param(report, 4, "SINGLE_PRECISION", "%s", "OFF"); -#endif - -#if defined(OUTPUT_ENABLED) - add_param(report, 4, "OUTPUT_ENABLED", "%s", "ON"); -#else - add_param(report, 4, "OUTPUT_ENABLED", "%s", "OFF"); -#endif - -#if defined(DEBUG) - add_param(report, 4, "DEBUG", "%s", "ON"); -#else - add_param(report, 4, "DEBUG", "%s", "OFF"); -#endif - -#if defined(CUDA_ENABLED) - add_param(report, 4, "CUDA_ENABLED", "%s", "ON"); -#else - add_param(report, 4, "CUDA_ENABLED", "%s", "OFF"); -#endif - -#if defined(HIP_ENABLED) - add_param(report, 4, "HIP_ENABLED", "%s", "ON"); -#else - add_param(report, 4, "HIP_ENABLED", "%s", "OFF"); -#endif - -#if defined(DEVICE_ENABLED) - add_param(report, 4, "DEVICE_ENABLED", "%s", "ON"); -#else - add_param(report, 4, "DEVICE_ENABLED", "%s", "OFF"); -#endif - -#if defined(MPI_ENABLED) - add_param(report, 4, "MPI_ENABLED", "%s", "ON"); -#else - add_param(report, 4, "MPI_ENABLED", "%s", "OFF"); -#endif - -#if defined(GPU_AWARE_MPI) - add_param(report, 4, "GPU_AWARE_MPI", "%s", "ON"); -#else - add_param(report, 4, "GPU_AWARE_MPI", "%s", "OFF"); -#endif - report += "\n"; - - /* - * Simulation configs - */ - add_category(report, 4, "Configuration"); - add_param(report, - 4, - "Name", - "%s", - params.template get("simulation.name").c_str()); - add_param(report, 4, "Problem generator", "%s", pgen.c_str()); - add_param(report, 4, "Engine", "%s", SimEngine(S).to_string()); - add_param(report, 4, "Metric", "%s", Metric(M::MetricType).to_string()); -#if SHAPE_ORDER == 0 - add_param(report, 4, "Deposit", "%s", "zigzag"); -#else - add_param(report, 4, "Deposit", "%s", "esirkepov"); - add_param(report, 4, "Interpolation order", "%i", SHAPE_ORDER); -#endif - add_param(report, 4, "Timestep [dt]", "%.3e", dt); - add_param(report, 4, "Runtime", "%.3e [%d steps]", runtime, max_steps); - report += "\n"; - add_category(report, 4, "Global domain"); - add_param(report, - 4, - "Resolution", - "%s", - params.template stringize("grid.resolution").c_str()); - add_param(report, - 4, - "Extent", - "%s", - params.template stringize("grid.extent").c_str()); - add_param(report, - 4, - "Fiducial cell size [dx0]", - "%.3e", - params.template get("scales.dx0")); - add_subcategory(report, 4, "Boundary conditions"); - add_param( - report, - 6, - "Fields", - "%s", - params.template stringize("grid.boundaries.fields").c_str()); - add_param( - report, - 6, - "Particles", - "%s", - params.template stringize("grid.boundaries.particles").c_str()); - add_param(report, - 4, - "Domain decomposition", - "%s [%d total]", - fmt::formatVector(m_metadomain.ndomains_per_dim()).c_str(), - m_metadomain.ndomains()); - report += "\n"; - add_category(report, 4, "Fiducial parameters"); - add_param(report, - 4, - "Particles per cell [ppc0]", - "%.1f", - params.template get("particles.ppc0")); - add_param(report, - 4, - "Larmor radius [larmor0]", - "%.3e [%.3f dx0]", - params.template get("scales.larmor0"), - params.template get("scales.larmor0") / - params.template get("scales.dx0")); - add_param(report, - 4, - "Larmor frequency [omegaB0 * dt]", - "%.3e", - params.template get("scales.omegaB0") * - params.template get("algorithms.timestep.dt")); - add_param(report, - 4, - "Skin depth [skindepth0]", - "%.3e [%.3f dx0]", - params.template get("scales.skindepth0"), - params.template get("scales.skindepth0") / - params.template get("scales.dx0")); - add_param(report, - 4, - "Plasma frequency [omp0 * dt]", - "%.3e", - params.template get("algorithms.timestep.dt") / - params.template get("scales.skindepth0")); - add_param(report, - 4, - "Magnetization [sigma0]", - "%.3e", - params.template get("scales.sigma0")); - - if (nspec > 0) { - report += "\n"; - add_category(report, 4, "Particles"); - } - for (const auto& species : metadomain.species_params()) { - add_subcategory(report, - 4, - fmt::format("Species #%d", species.index()).c_str()); - add_param(report, 6, "Label", "%s", species.label().c_str()); - add_param(report, 6, "Mass", "%.1f", species.mass()); - add_param(report, 6, "Charge", "%.1f", species.charge()); - add_param(report, 6, "Max #", "%d [per domain]", species.maxnpart()); - add_param(report, 6, "Pusher", "%s", species.pusher().to_string()); - if (species.mass() != 0.0) { - add_param(report, 6, "GCA", "%s", species.use_gca() ? "ON" : "OFF"); - } - add_param(report, 6, "Cooling", "%s", species.cooling().to_string()); - add_param(report, 6, "# of real-value payloads", "%d", species.npld_r()); - add_param(report, 6, "# of integer-value payloads", "%d", species.npld_i()); - } - report.pop_back(); - }, - m_metadomain, - m_params); - info::Print(report, colored_stdout); - - report = "\n"; - CallOnce([&]() { - add_category(report, 4, "Domains"); - report.pop_back(); - }); - info::Print(report, colored_stdout); - - for (unsigned int idx { 0 }; idx < m_metadomain.ndomains(); ++idx) { - auto is_local = false; - for (const auto& lidx : m_metadomain.l_subdomain_indices()) { - is_local |= (idx == lidx); - } - if (is_local) { - report = ""; - const auto& domain = m_metadomain.subdomain(idx); - add_subcategory(report, - 4, - fmt::format("Domain #%d", domain.index()).c_str()); -#if defined(MPI_ENABLED) - add_param(report, 6, "Rank", "%d", domain.mpi_rank()); -#endif - add_param(report, - 6, - "Resolution", - "%s", - fmt::formatVector(domain.mesh.n_active()).c_str()); - add_param(report, - 6, - "Extent", - "%s", - fmt::formatVector(domain.mesh.extent()).c_str()); - add_subcategory(report, 6, "Boundary conditions"); - - add_label( - report, - 8 + 2 + 2 * M::Dim, - fmt::format("%-10s %-10s %-10s", "[flds]", "[prtl]", "[neighbor]").c_str()); - for (auto& direction : dir::Directions::all) { - const auto flds_bc = domain.mesh.flds_bc_in(direction); - const auto prtl_bc = domain.mesh.prtl_bc_in(direction); - bool has_sync = false; - auto neighbor_idx = domain.neighbor_idx_in(direction); - if (flds_bc == FldsBC::SYNC || prtl_bc == PrtlBC::SYNC) { - has_sync = true; - } - add_unlabeled_param(report, - 8, - direction.to_string().c_str(), - "%-10s %-10s %-10s", - flds_bc.to_string(), - prtl_bc.to_string(), - has_sync ? std::to_string(neighbor_idx).c_str() - : "."); - } - add_subcategory(report, 6, "Memory footprint"); - auto flds_footprint = domain.fields.memory_footprint(); - auto [flds_size, flds_unit] = bytes_to_human_readable(flds_footprint); - add_param(report, 8, "Fields", "%.2f %s", flds_size, flds_unit.c_str()); - if (domain.species.size() > 0) { - add_subcategory(report, 8, "Particles"); - } - for (auto& species : domain.species) { - const auto str = fmt::format("Species #%d (%s)", - species.index(), - species.label().c_str()); - auto [size, unit] = bytes_to_human_readable(species.memory_footprint()); - add_param(report, 10, str.c_str(), "%.2f %s", size, unit.c_str()); - } - report.pop_back(); - if (idx == m_metadomain.ndomains() - 1) { - report += "\n\n"; - } - info::Print(report, colored_stdout, true, false); - } -#if defined(MPI_ENABLED) - MPI_Barrier(MPI_COMM_WORLD); -#endif - } - } - -#define ENGINE_PRINTER(S, M, D) \ - template void Engine>::print_report() const; - - NTT_FOREACH_SPECIALIZATION(ENGINE_PRINTER) - -#undef ENGINE_PRINTER - -} // namespace ntt diff --git a/src/engines/engine_run.cpp b/src/engines/engine_run.cpp deleted file mode 100644 index 961e39206..000000000 --- a/src/engines/engine_run.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "enums.h" - -#include "arch/traits.h" -#include "utils/diag.h" - -#include "framework/domain/domain.h" -#include "framework/specialization_registry.h" - -#include "engines/engine.hpp" - -namespace ntt { - - template - void Engine::run() { - if constexpr (pgen_is_ok) { - init(); - - auto timers = timer::Timers { - { "FieldSolver", - "CurrentFiltering", "CurrentDeposit", - "ParticlePusher", "FieldBoundaries", - "ParticleBoundaries", "Communications", - "Injector", "Custom", - "PrtlClear", "Output", - "Checkpoint" }, - []() { - Kokkos::fence(); - }, - m_params.get("diagnostics.blocking_timers") - }; - const auto diag_interval = m_params.get( - "diagnostics.interval"); - - auto time_history = pbar::DurationHistory { 1000 }; - const auto clear_interval = m_params.template get( - "particles.clear_interval"); - - // main algorithm loop - while (step < max_steps) { - // run the engine-dependent algorithm step - m_metadomain.runOnLocalDomains([&timers, this](auto& dom) { - step_forward(timers, dom); - }); - // poststep (if defined) - if constexpr ( - traits::has_method::value) { - timers.start("Custom"); - m_metadomain.runOnLocalDomains([&timers, this](auto& dom) { - m_pgen.CustomPostStep(step, time, dom); - }); - timers.stop("Custom"); - } - auto print_prtl_clear = (clear_interval > 0 and - step % clear_interval == 0 and step > 0); - - // advance time & step - time += dt; - ++step; - - auto print_output = false; - auto print_checkpoint = false; -#if defined(OUTPUT_ENABLED) - timers.start("Output"); - if constexpr ( - traits::has_method::value) { - auto lambda_custom_field_output = [&](const std::string& name, - ndfield_t& buff, - index_t idx, - timestep_t step, - simtime_t time, - const Domain& dom) { - m_pgen.CustomFieldOutput(name, buff, idx, step, time, dom); - }; - print_output &= m_metadomain.Write(m_params, - step, - step - 1, - time, - time - dt, - lambda_custom_field_output); - } else { - print_output &= m_metadomain.Write(m_params, step, step - 1, time, time - dt); - } - if constexpr ( - traits::has_method::value) { - auto lambda_custom_stat = [&](const std::string& name, - timestep_t step, - simtime_t time, - const Domain& dom) -> real_t { - return m_pgen.CustomStat(name, step, time, dom); - }; - print_output &= m_metadomain.WriteStats(m_params, - step, - step - 1, - time, - time - dt, - lambda_custom_stat); - } else { - print_output &= m_metadomain.WriteStats(m_params, - step, - step - 1, - time, - time - dt); - } - timers.stop("Output"); - - timers.start("Checkpoint"); - print_checkpoint = m_metadomain.WriteCheckpoint(m_params, - step, - step - 1, - time, - time - dt); - timers.stop("Checkpoint"); -#endif - - // advance time_history - time_history.tick(); - // print timestep report - if (diag_interval > 0 and step % diag_interval == 0) { - diag::printDiagnostics( - step - 1, - max_steps, - time - dt, - dt, - timers, - time_history, - m_metadomain.l_ncells(), - m_metadomain.species_labels(), - m_metadomain.l_npart_perspec(), - m_metadomain.l_maxnpart_perspec(), - print_prtl_clear, - print_output, - print_checkpoint, - m_params.get("diagnostics.colored_stdout")); - } - timers.resetAll(); - } - } - } - -#define ENGINE_RUN(S, M, D) template void Engine>::run(); - - NTT_FOREACH_SPECIALIZATION(ENGINE_RUN) - -#undef ENGINE_RUN - -} // namespace ntt diff --git a/src/engines/engine_traits.h b/src/engines/engine_traits.h deleted file mode 100644 index 28a74bea3..000000000 --- a/src/engines/engine_traits.h +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause - -#ifndef ENGINES_ENGINE_TRAITS_H -#define ENGINES_ENGINE_TRAITS_H - -#include "enums.h" - -#include "engines/grpic.hpp" -#include "engines/srpic.hpp" - -namespace ntt { - - template - struct EngineSelector; - - template <> - struct EngineSelector { - template - using type = SRPICEngine; - }; - - template <> - struct EngineSelector { - template - using type = GRPICEngine; - }; - -} // namespace ntt - -#endif // ENGINES_ENGINE_TRAITS_H diff --git a/src/engines/grpic.hpp b/src/engines/grpic.hpp deleted file mode 100644 index 4d9d89d92..000000000 --- a/src/engines/grpic.hpp +++ /dev/null @@ -1,1145 +0,0 @@ -/** - * @file engines/grpic.hpp - * @brief Simulation engien class which specialized on GRPIC - * @implements - * - ntt::GRPICEngine<> : ntt::Engine<> - * @cpp: - * - grpic.cpp - * @namespaces: - * - ntt:: - */ - -#ifndef ENGINES_GRPIC_GRPIC_H -#define ENGINES_GRPIC_GRPIC_H - -#include "enums.h" -#include "global.h" - -#include "arch/kokkos_aliases.h" -#include "utils/log.h" -#include "utils/numeric.h" -#include "utils/timer.h" -#include "utils/toml.h" - -#include "framework/domain/domain.h" -#include "framework/parameters.h" - -#include "engines/engine.hpp" -#include "kernels/ampere_gr.hpp" -#include "kernels/aux_fields_gr.hpp" -#include "kernels/currents_deposit.hpp" -#include "kernels/digital_filter.hpp" -#include "kernels/faraday_gr.hpp" -#include "kernels/fields_bcs.hpp" -#include "kernels/particle_pusher_gr.hpp" -#include "pgen.hpp" - -#include -#include - -#include -#include - -namespace ntt { - - enum class gr_getE { - D0_B, - D_B0 - }; - enum class gr_getH { - D_B0, - D0_B0 - }; - enum class gr_faraday { - aux, - main - }; - enum class gr_ampere { - init, - aux, - main - }; - enum class gr_bc { - main, - aux, - curr - }; - - template - class GRPICEngine : public Engine { - using base_t = Engine; - using pgen_t = user::PGen; - using domain_t = Domain; - // constexprs - using base_t::pgen_is_ok; - // contents - using base_t::m_metadomain; - using base_t::m_params; - using base_t::m_pgen; - // methods - using base_t::init; - // variables - using base_t::dt; - using base_t::max_steps; - using base_t::runtime; - using base_t::step; - using base_t::time; - - public: - static constexpr auto S { SimEngine::GRPIC }; - - GRPICEngine(SimulationParams& params) : base_t { params } {} - - ~GRPICEngine() = default; - - void step_forward(timer::Timers& timers, domain_t& dom) override { - const auto fieldsolver_enabled = m_params.template get( - "algorithms.fieldsolver.enable"); - const auto deposit_enabled = m_params.template get( - "algorithms.deposit.enable"); - const auto clear_interval = m_params.template get( - "particles.clear_interval"); - - if (step == 0) { - if (fieldsolver_enabled) { - // communicate fields and apply BCs on the first timestep - /** - * Initially: em0::B -- - * em0::D -- - * em::B at -1/2 - * em::D at -1/2 - * - * cur0::J -- - * cur::J -- - * - * aux::E -- - * aux::H -- - * - * x_prtl at -1/2 - * u_prtl at -1/2 - */ - - /** - * em0::D, em::D, em0::B, em::B <- boundary conditions - */ - m_metadomain.CommunicateFields(dom, - Comm::B | Comm::B0 | Comm::D | Comm::D0); - FieldBoundaries(dom, BC::B | BC::D, gr_bc::main); - - /** - * em0::B <- em::B - * em0::D <- em::D - * - * Now: em0::B & em0::D at -1/2 - */ - CopyFields(dom); - - /** - * aux::E <- alpha * em::D + beta x em0::B - * aux::H <- alpha * em::B0 - beta x em::D - * - * Now: aux::E & aux::H at -1/2 - */ - ComputeAuxE(dom, gr_getE::D_B0); - ComputeAuxH(dom, gr_getH::D_B0); - - /** - * aux::E, aux::H <- boundary conditions - */ - m_metadomain.CommunicateFields(dom, Comm::H | Comm::E); - FieldBoundaries(dom, BC::H | BC::E, gr_bc::aux); - - /** - * em0::B <- (em0::B) <- -curl aux::E - * - * Now: em0::B at 0 - */ - Faraday(dom, gr_faraday::aux, HALF); - - /** - * em0::B, em::B <- boundary conditions - */ - m_metadomain.CommunicateFields(dom, Comm::B | Comm::B0); - FieldBoundaries(dom, BC::B, gr_bc::main); - - /** - * em::D <- (em0::D) <- curl aux::H - * - * Now: em::D at 0 - */ - Ampere(dom, gr_ampere::init, HALF); - - /** - * em0::D, em::D <- boundary conditions - */ - m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); - FieldBoundaries(dom, BC::D, gr_bc::main); - - /** - * aux::E <- alpha * em::D + beta x em0::B - * aux::H <- alpha * em0::B - beta x em::D - * - * Now: aux::E & aux::H at 0 - */ - ComputeAuxE(dom, gr_getE::D_B0); - ComputeAuxH(dom, gr_getH::D_B0); - - /** - * aux::E, aux::H <- boundary conditions - */ - m_metadomain.CommunicateFields(dom, Comm::H | Comm::E); - FieldBoundaries(dom, BC::H | BC::E, gr_bc::aux); - - // !ADD: GR -- particles? - - /** - * em0::B <- (em::B) <- -curl aux::E - * - * Now: em0::B at 1/2 - */ - Faraday(dom, gr_faraday::main, ONE); - /** - * em0::B, em::B <- boundary conditions - */ - m_metadomain.CommunicateFields(dom, Comm::B | Comm::B0); - FieldBoundaries(dom, BC::B, gr_bc::main); - - /** - * em0::D <- (em0::D) <- curl aux::H - * - * Now: em0::D at 1/2 - */ - Ampere(dom, gr_ampere::aux, ONE); - /** - * em0::D, em::D <- boundary conditions - */ - m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); - FieldBoundaries(dom, BC::D, gr_bc::main); - - /** - * aux::H <- alpha * em0::B - beta x em0::D - * - * Now: aux::H at 1/2 - */ - ComputeAuxH(dom, gr_getH::D0_B0); - /** - * aux::H <- boundary conditions - */ - m_metadomain.CommunicateFields(dom, Comm::H); - FieldBoundaries(dom, BC::H, gr_bc::aux); - - /** - * em0::D <- (em::D) <- curl aux::H - * - * Now: em0::D at 1 - * em::D at 0 - */ - Ampere(dom, gr_ampere::main, ONE); - /** - * em0::D, em::D <- boundary conditions - */ - m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); - FieldBoundaries(dom, BC::D, gr_bc::main); - - /** - * em::D <-> em0::D - * em::B <-> em0::B - * em::J <-> em0::J - */ - SwapFields(dom); - /** - * Finally: em0::B at -1/2 - * em0::D at 0 - * em::B at 1/2 - * em::D at 1 - * - * cur0::J -- - * cur::J -- - * - * aux::E -- - * aux::H -- - * - * x_prtl at 1 - * u_prtl at 1/2 - */ - } else { - /** - * em0::B <- em::B - * em0::D <- em::D - * - * Now: em0::B & em0::D at -1/2 - */ - CopyFields(dom); - } - } - - /** - * Initially: em0::B at n-3/2 - * em0::D at n-1 - * em::B at n-1/2 - * em::D at n - * - * cur0::J -- - * cur::J at n-1/2 - * - * aux::E -- - * aux::H -- - * - * x_prtl at n - * u_prtl at n-1/2 - */ - - if (fieldsolver_enabled) { - timers.start("FieldSolver"); - /** - * em0::D <- (em0::D + em::D) / 2 - * em0::B <- (em0::B + em::B) / 2 - * - * Now: em0::D at n-1/2 - * em0::B at n-1 - */ - TimeAverageDB(dom); - /** - * aux::E <- alpha * em0::D + beta x em::B - * - * Now: aux::E at n-1/2 - */ - ComputeAuxE(dom, gr_getE::D0_B); - timers.stop("FieldSolver"); - - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::E); - timers.stop("Communications"); - timers.start("FieldBoundaries"); - /** - * aux::E <- boundary conditions - */ - FieldBoundaries(dom, BC::E, gr_bc::aux); - timers.stop("FieldBoundaries"); - - timers.start("FieldSolver"); - /** - * em0::B <- (em0::B) <- -curl aux::E - * - * Now: em0::B at n - */ - Faraday(dom, gr_faraday::aux, ONE); - timers.stop("FieldSolver"); - - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::B | Comm::B0); - timers.stop("Communications"); - /** - * em0::B, em::B <- boundary conditions - */ - timers.start("FieldBoundaries"); - FieldBoundaries(dom, BC::B, gr_bc::main); - timers.stop("FieldBoundaries"); - - timers.start("FieldSolver"); - /** - * aux::H <- alpha * em0::B - beta x em::D - * - * Now: aux::H at n - */ - ComputeAuxH(dom, gr_getH::D_B0); - timers.stop("FieldSolver"); - - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::H); - timers.stop("Communications"); - timers.start("FieldBoundaries"); - /** - * aux::H <- boundary conditions - */ - FieldBoundaries(dom, BC::H, gr_bc::aux); - timers.stop("FieldBoundaries"); - } - - { - /** - * x_prtl, u_prtl <- em::D, em0::B - * - * Now: x_prtl at n + 1, u_prtl at n + 1/2 - */ - timers.start("ParticlePusher"); - ParticlePush(dom); - timers.stop("ParticlePusher"); - - /** - * cur0::J <- current deposition - * - * Now: cur0::J at n+1/2 - */ - if (deposit_enabled) { - timers.start("CurrentDeposit"); - Kokkos::deep_copy(dom.fields.cur0, ZERO); - CurrentsDeposit(dom); - timers.stop("CurrentDeposit"); - - timers.start("Communications"); - m_metadomain.SynchronizeFields(dom, Comm::J); - m_metadomain.CommunicateFields(dom, Comm::J); - timers.stop("Communications"); - - timers.start("FieldBoundaries"); - FieldBoundaries(dom, BC::J, gr_bc::curr); - timers.stop("FieldBoundaries"); - - timers.start("CurrentFiltering"); - CurrentsFilter(dom); - timers.stop("CurrentFiltering"); - } - - timers.start("Communications"); - m_metadomain.CommunicateParticles(dom); - timers.stop("Communications"); - } - - if (fieldsolver_enabled) { - timers.start("FieldSolver"); - if (deposit_enabled) { - /** - * cur::J <- (cur0::J + cur::J) / 2 - * - * Now: cur::J at n - */ - TimeAverageJ(dom); - } - /** - * aux::Е <- alpha * em::D + beta x em0::B - * - * Now: aux::Е at n - */ - ComputeAuxE(dom, gr_getE::D_B0); - timers.stop("FieldSolver"); - - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::E); - timers.stop("Communications"); - timers.start("FieldBoundaries"); - /** - * aux::Е <- boundary conditions - */ - FieldBoundaries(dom, BC::E, gr_bc::aux); - timers.stop("FieldBoundaries"); - - timers.start("FieldSolver"); - /** - * em0::B <- (em::B) <- -curl aux::E - * - * Now: em0::B at n+1/2 - * em::B at n-1/2 - */ - Faraday(dom, gr_faraday::main, ONE); - timers.stop("FieldSolver"); - - /** - * em0::B, em::B <- boundary conditions - */ - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::B | Comm::B0); - timers.stop("Communications"); - timers.start("FieldBoundaries"); - FieldBoundaries(dom, BC::B, gr_bc::main); - timers.stop("FieldBoundaries"); - - timers.start("FieldSolver"); - /** - * em0::D <- (em0::D) <- curl aux::H - * - * Now: em0::D at n+1/2 - */ - Ampere(dom, gr_ampere::aux, ONE); - timers.stop("FieldSolver"); - - if (deposit_enabled) { - timers.start("FieldSolver"); - /** - * em0::D <- (em0::D) <- cur::J - * - * Now: em0::D at n+1/2 - */ - AmpereCurrents(dom, gr_ampere::aux); - timers.stop("FieldSolver"); - } - - /** - * em0::D, em::D <- boundary conditions - */ - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); - timers.stop("Communications"); - timers.start("FieldBoundaries"); - FieldBoundaries(dom, BC::D, gr_bc::main); - timers.stop("FieldBoundaries"); - - timers.start("FieldSolver"); - /** - * aux::H <- alpha * em0::B - beta x em0::D - * - * Now: aux::H at n+1/2 - */ - ComputeAuxH(dom, gr_getH::D0_B0); - timers.stop("FieldSolver"); - - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::H); - timers.stop("Communications"); - timers.start("FieldBoundaries"); - /** - * aux::H <- boundary conditions - */ - FieldBoundaries(dom, BC::B, gr_bc::aux); - timers.stop("FieldBoundaries"); - - timers.start("FieldSolver"); - /** - * em0::D <- (em::D) <- curl aux::H - * - * Now: em0::D at n+1 - * em::D at n - */ - Ampere(dom, gr_ampere::main, ONE); - timers.stop("FieldSolver"); - - if (deposit_enabled) { - timers.start("FieldSolver"); - /** - * em0::D <- (em0::D) <- cur0::J - * - * Now: em0::D at n+1 - */ - AmpereCurrents(dom, gr_ampere::main); - timers.stop("FieldSolver"); - } - timers.start("FieldSolver"); - /** - * em::D <-> em0::D - * em::B <-> em0::B - * cur::J <-> cur0::J - */ - SwapFields(dom); - timers.stop("FieldSolver"); - - /** - * em0::D, em::D <- boundary conditions - */ - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); - timers.stop("Communications"); - timers.start("FieldBoundaries"); - FieldBoundaries(dom, BC::D, gr_bc::main); - timers.stop("FieldBoundaries"); - } - - if (clear_interval > 0 and step % clear_interval == 0 and step > 0) { - timers.start("PrtlClear"); - m_metadomain.RemoveDeadParticles(dom); - timers.stop("PrtlClear"); - } - - /** - * Finally: em0::B at n-1/2 - * em0::D at n - * em::B at n+1/2 - * em::D at n+1 - * - * cur0::J (at n) - * cur::J at n+1/2 - * - * aux::E (at n+1/2) - * aux::H (at n) - * - * x_prtl at n+1 - * u_prtl at n+1/2 - */ - } - - /* algorithm substeps --------------------------------------------------- */ - void FieldBoundaries(domain_t& domain, BCTags tags, const gr_bc& g) { - if (g == gr_bc::main) { - for (auto& direction : dir::Directions::orth) { - if (m_metadomain.mesh().flds_bc_in(direction) == FldsBC::MATCH) { - MatchFieldsIn(direction, domain, tags, g); - } else if (domain.mesh.flds_bc_in(direction) == FldsBC::AXIS) { - AxisFieldsIn(direction, domain, tags); - } else if (m_metadomain.mesh().flds_bc_in(direction) == FldsBC::CUSTOM) { - CustomFieldsIn(direction, domain, tags, g); - } else if (domain.mesh.flds_bc_in(direction) == FldsBC::HORIZON) { - HorizonFieldsIn(direction, domain, tags, g); - } - } // loop over directions - } else if (g == gr_bc::aux) { - for (auto& direction : dir::Directions::orth) { - if (domain.mesh.flds_bc_in(direction) == FldsBC::HORIZON) { - HorizonFieldsIn(direction, domain, tags, g); - } - } - } else if (g == gr_bc::curr) { - for (auto& direction : dir::Directions::orth) { - if (domain.mesh.prtl_bc_in(direction) == PrtlBC::ABSORB) { - MatchFieldsIn(direction, domain, tags, g); - } - } - } - } - - void MatchFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags, - const gr_bc& g) { - /** - * match boundaries - */ - const auto ds_array = m_params.template get>( - "grid.boundaries.match.ds"); - const auto dim = direction.get_dim(); - real_t xg_min, xg_max, xg_edge; - auto sign = direction.get_sign(); - - raise::ErrorIf(((dim != in::x1) or (sign < 0)) and (g == gr_bc::curr), - "Absorption of currents only possible in +x1 (+r)", - HERE); - - real_t ds; - if (sign > 0) { // + direction - ds = ds_array[(short)dim].second; - xg_max = m_metadomain.mesh().extent(dim).second; - xg_min = xg_max - ds; - xg_edge = xg_max; - } else { // - direction - ds = ds_array[(short)dim].first; - xg_min = m_metadomain.mesh().extent(dim).first; - xg_max = xg_min + ds; - xg_edge = xg_min; - } - boundaries_t box; - boundaries_t incl_ghosts; - for (unsigned short d { 0 }; d < M::Dim; ++d) { - if (d == static_cast(dim)) { - box.push_back({ xg_min, xg_max }); - incl_ghosts.push_back({ false, true }); - } else { - box.push_back(Range::All); - incl_ghosts.push_back({ true, true }); - } - } - if (not domain.mesh.Intersects(box)) { - return; - } - const auto intersect_range = domain.mesh.ExtentToRange(box, incl_ghosts); - tuple_t range_min { 0 }; - tuple_t range_max { 0 }; - - for (unsigned short d { 0 }; d < M::Dim; ++d) { - range_min[d] = intersect_range[d].first; - range_max[d] = intersect_range[d].second; - } - if (dim == in::x1) { - if (g != gr_bc::curr) { - Kokkos::parallel_for( - "MatchBoundaries", - CreateRangePolicy(range_min, range_max), - kernel::bc::MatchBoundaries_kernel( - domain.fields.em, - m_pgen.init_flds, - domain.mesh.metric, - xg_edge, - ds, - tags, - domain.mesh.flds_bc())); - Kokkos::parallel_for( - "MatchBoundaries", - CreateRangePolicy(range_min, range_max), - kernel::bc::MatchBoundaries_kernel( - domain.fields.em0, - m_pgen.init_flds, - domain.mesh.metric, - xg_edge, - ds, - tags, - domain.mesh.flds_bc())); - } else { - Kokkos::parallel_for( - "AbsorbCurrents", - CreateRangePolicy(range_min, range_max), - kernel::bc::gr::AbsorbCurrents_kernel(domain.fields.cur0, - domain.mesh.metric, - xg_edge, - ds)); - } - } else { - raise::Error("Invalid dimension", HERE); - } - } - - void HorizonFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags, - const gr_bc& g) { - /** - * open boundaries - */ - raise::ErrorIf(M::CoordType == Coord::Cart, - "Invalid coordinate type for horizon BCs", - HERE); - raise::ErrorIf(direction.get_dim() != in::x1, - "Invalid horizon direction, should be x1", - HERE); - const auto i1_min = domain.mesh.i_min(in::x1); - auto range = CreateRangePolicy({ domain.mesh.i_min(in::x2) }, - { domain.mesh.i_max(in::x2) + 1 }); - const auto nfilter = m_params.template get( - "algorithms.current_filters"); - if (g == gr_bc::main) { - Kokkos::parallel_for( - "OpenBCFields", - range, - kernel::bc::gr::HorizonBoundaries_kernel(domain.fields.em, - i1_min, - tags, - nfilter)); - Kokkos::parallel_for( - "OpenBCFields", - range, - kernel::bc::gr::HorizonBoundaries_kernel(domain.fields.em0, - i1_min, - tags, - nfilter)); - } - } - - void AxisFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags) { - /** - * axis boundaries - */ - raise::ErrorIf(M::CoordType == Coord::Cart, - "Invalid coordinate type for axis BCs", - HERE); - raise::ErrorIf(direction.get_dim() != in::x2, - "Invalid axis direction, should be x2", - HERE); - const auto i2_min = domain.mesh.i_min(in::x2); - const auto i2_max = domain.mesh.i_max(in::x2); - if (direction.get_sign() < 0) { - Kokkos::parallel_for( - "AxisBCFields", - domain.mesh.n_all(in::x1), - kernel::bc::AxisBoundaries_kernel(domain.fields.em, - i2_min, - tags)); - Kokkos::parallel_for( - "AxisBCFields", - domain.mesh.n_all(in::x1), - kernel::bc::AxisBoundaries_kernel(domain.fields.em0, - i2_min, - tags)); - } else { - Kokkos::parallel_for( - "AxisBCFields", - domain.mesh.n_all(in::x1), - kernel::bc::AxisBoundaries_kernel(domain.fields.em, - i2_max, - tags)); - Kokkos::parallel_for( - "AxisBCFields", - domain.mesh.n_all(in::x1), - kernel::bc::AxisBoundaries_kernel(domain.fields.em0, - i2_max, - tags)); - } - } - - void CustomFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags, - const gr_bc& g) { - (void)direction; - (void)domain; - (void)tags; - (void)g; - raise::Error("Custom boundaries not implemented", HERE); - // if constexpr ( - // traits::has_member::value) { - // const auto [box, custom_fields] = m_pgen.CustomFields(time); - // if (domain.mesh.Intersects(box)) { - // } - // - // } else { - // raise::Error("Custom boundaries not implemented", HERE); - // } - } - - /** - * @brief Swaps em and em0 fields, cur and cur0 currents. - */ - void SwapFields(domain_t& domain) { - std::swap(domain.fields.em, domain.fields.em0); - std::swap(domain.fields.cur, domain.fields.cur0); - } - - /** - * @brief Copies em fields into em0 - */ - void CopyFields(domain_t& domain) { - Kokkos::deep_copy(domain.fields.em0, domain.fields.em); - } - - void ComputeAuxE(domain_t& domain, const gr_getE& g) { - auto range = range_with_axis_BCs(domain); - if (g == gr_getE::D0_B) { - Kokkos::parallel_for( - "ComputeAuxE", - range, - kernel::gr::ComputeAuxE_kernel(domain.fields.em0, // D - domain.fields.em, // B - domain.fields.aux, // E - domain.mesh.metric)); - } else if (g == gr_getE::D_B0) { - Kokkos::parallel_for("ComputeAuxE", - range, - kernel::gr::ComputeAuxE_kernel(domain.fields.em, - domain.fields.em0, - domain.fields.aux, - domain.mesh.metric)); - } else { - raise::Error("Wrong option for `g`", HERE); - } - } - - void ComputeAuxH(domain_t& domain, const gr_getH& g) { - auto range = range_with_axis_BCs(domain); - if (g == gr_getH::D_B0) { - Kokkos::parallel_for( - "ComputeAuxH", - range, - kernel::gr::ComputeAuxH_kernel(domain.fields.em, // D - domain.fields.em0, // B - domain.fields.aux, // H - domain.mesh.metric)); - } else if (g == gr_getH::D0_B0) { - Kokkos::parallel_for("ComputeAuxH", - range, - kernel::gr::ComputeAuxH_kernel(domain.fields.em0, - domain.fields.em0, - domain.fields.aux, - domain.mesh.metric)); - } else { - raise::Error("Wrong option for `g`", HERE); - } - } - - auto range_with_axis_BCs(const domain_t& domain) -> range_t { - auto range = domain.mesh.rangeActiveCells(); - /** - * @brief taking one extra cell in the x1 and x2 directions if AXIS BCs - */ - if constexpr (M::Dim == Dim::_2D) { - if (domain.mesh.flds_bc_in({ 0, +1 }) == FldsBC::AXIS) { - range = CreateRangePolicy( - { domain.mesh.i_min(in::x1) - 1, domain.mesh.i_min(in::x2) }, - { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); - } else { - range = CreateRangePolicy( - { domain.mesh.i_min(in::x1) - 1, domain.mesh.i_min(in::x2) }, - { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) }); - } - } else if constexpr (M::Dim == Dim::_3D) { - raise::Error("Invalid dimension", HERE); - } - return range; - } - - void Faraday(domain_t& domain, const gr_faraday& g, real_t fraction = ONE) { - logger::Checkpoint("Launching Faraday kernel", HERE); - const auto dT = fraction * - m_params.template get( - "algorithms.timestep.correction") * - dt; - if (g == gr_faraday::aux) { - Kokkos::parallel_for( - "Faraday", - domain.mesh.rangeActiveCells(), - kernel::gr::Faraday_kernel(domain.fields.em0, // Bin - domain.fields.em0, // Bout - domain.fields.aux, // E - domain.mesh.metric, - dT, - domain.mesh.n_active(in::x2), - domain.mesh.flds_bc())); - } else if (g == gr_faraday::main) { - Kokkos::parallel_for( - "Faraday", - domain.mesh.rangeActiveCells(), - kernel::gr::Faraday_kernel(domain.fields.em, - domain.fields.em0, - domain.fields.aux, - domain.mesh.metric, - dT, - domain.mesh.n_active(in::x2), - domain.mesh.flds_bc())); - - } else { - raise::Error("Wrong option for `g`", HERE); - } - } - - void Ampere(domain_t& domain, const gr_ampere& g, real_t fraction = ONE) { - logger::Checkpoint("Launching Ampere kernel", HERE); - const auto dT = fraction * - m_params.template get( - "algorithms.timestep.correction") * - dt; - auto range = CreateRangePolicy( - { domain.mesh.i_min(in::x1), domain.mesh.i_min(in::x2) }, - { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); - const auto ni2 = domain.mesh.n_active(in::x2); - - if (g == gr_ampere::aux) { - // First push, updates D0 with J. - Kokkos::parallel_for("Ampere-1", - range, - kernel::gr::Ampere_kernel(domain.fields.em0, // Din - domain.fields.em0, // Dout - domain.fields.aux, - domain.mesh.metric, - dT, - ni2, - domain.mesh.flds_bc())); - } else if (g == gr_ampere::main) { - // Second push, updates D with J0 but assigns it to D0. - Kokkos::parallel_for("Ampere-2", - range, - kernel::gr::Ampere_kernel(domain.fields.em, - domain.fields.em0, - domain.fields.aux, - domain.mesh.metric, - dT, - ni2, - domain.mesh.flds_bc())); - } else if (g == gr_ampere::init) { - // Second push, updates D with J0 and assigns it to D. - Kokkos::parallel_for("Ampere-3", - range, - kernel::gr::Ampere_kernel(domain.fields.em, - domain.fields.em, - domain.fields.aux, - domain.mesh.metric, - dT, - ni2, - domain.mesh.flds_bc())); - } else { - raise::Error("Wrong option for `g`", HERE); - } - } - - void AmpereCurrents(domain_t& domain, const gr_ampere& g) { - logger::Checkpoint("Launching Ampere kernel for adding currents", HERE); - const auto q0 = m_params.template get("scales.q0"); - const auto B0 = m_params.template get("scales.B0"); - const auto coeff = -dt * q0 / B0; - auto range = CreateRangePolicy( - { domain.mesh.i_min(in::x1), domain.mesh.i_min(in::x2) }, - { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); - const auto ni2 = domain.mesh.n_active(in::x2); - - if (g == gr_ampere::aux) { - // Updates D0 with J: D0(n-1/2) -> (J(n)) -> D0(n+1/2) - Kokkos::parallel_for( - "AmpereCurrentsAux", - range, - kernel::gr::CurrentsAmpere_kernel(domain.fields.em0, - domain.fields.cur, - domain.mesh.metric, - coeff, - ni2, - domain.mesh.flds_bc())); - } else if (g == gr_ampere::main) { - // Updates D0 with J0: D0(n) -> (J0(n+1/2)) -> D0(n+1) - Kokkos::parallel_for( - "AmpereCurrentsMain", - range, - kernel::gr::CurrentsAmpere_kernel(domain.fields.em0, - domain.fields.cur0, - domain.mesh.metric, - coeff, - ni2, - domain.mesh.flds_bc())); - } else { - raise::Error("Wrong option for `g`", HERE); - } - } - - void TimeAverageDB(domain_t& domain) { - Kokkos::parallel_for("TimeAverageDB", - domain.mesh.rangeActiveCells(), - kernel::gr::TimeAverageDB_kernel(domain.fields.em, - domain.fields.em0, - domain.mesh.metric)); - } - - void TimeAverageJ(domain_t& domain) { - Kokkos::parallel_for("TimeAverageJ", - domain.mesh.rangeActiveCells(), - kernel::gr::TimeAverageJ_kernel(domain.fields.cur, - domain.fields.cur0, - domain.mesh.metric)); - } - - void CurrentsDeposit(domain_t& domain) { - auto scatter_cur0 = Kokkos::Experimental::create_scatter_view( - domain.fields.cur0); - for (auto& species : domain.species) { - logger::Checkpoint( - fmt::format("Launching currents deposit kernel for %d [%s] : %lu %f", - species.index(), - species.label().c_str(), - species.npart(), - (double)species.charge()), - HERE); - if (species.npart() == 0 || cmp::AlmostZero(species.charge())) { - continue; - } - Kokkos::parallel_for("CurrentsDeposit", - species.rangeActiveParticles(), - kernel::DepositCurrents_kernel( - scatter_cur0, - species.i1, - species.i2, - species.i3, - species.i1_prev, - species.i2_prev, - species.i3_prev, - species.dx1, - species.dx2, - species.dx3, - species.dx1_prev, - species.dx2_prev, - species.dx3_prev, - species.ux1, - species.ux2, - species.ux3, - species.phi, - species.weight, - species.tag, - domain.mesh.metric, - (real_t)(species.charge()), - dt)); - } - Kokkos::Experimental::contribute(domain.fields.cur0, scatter_cur0); - } - - void CurrentsFilter(domain_t& domain) { - logger::Checkpoint("Launching currents filtering kernels", HERE); - auto range = CreateRangePolicy( - { domain.mesh.i_min(in::x1), domain.mesh.i_min(in::x2) }, - { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); - const auto nfilter = m_params.template get( - "algorithms.current_filters"); - tuple_t size; - size[0] = domain.mesh.n_active(in::x1); - size[1] = domain.mesh.n_active(in::x2); - - // !TODO: this needs to be done more efficiently - for (unsigned short i = 0; i < nfilter; ++i) { - Kokkos::deep_copy(domain.fields.buff, domain.fields.cur0); - Kokkos::parallel_for("CurrentsFilter", - range, - kernel::DigitalFilter_kernel( - domain.fields.cur0, - domain.fields.buff, - size, - domain.mesh.flds_bc())); - m_metadomain.CommunicateFields(domain, Comm::J); // J0 - } - } - - void ParticlePush(domain_t& domain) { - for (auto& species : domain.species) { - species.set_unsorted(); - logger::Checkpoint( - fmt::format("Launching particle pusher kernel for %d [%s] : %lu", - species.index(), - species.label().c_str(), - species.npart()), - HERE); - if (species.npart() == 0) { - continue; - } - const auto q_ovr_m = species.mass() > ZERO - ? species.charge() / species.mass() - : ZERO; - // coeff = q / m (dt / 2) omegaB0 - const auto coeff = q_ovr_m * HALF * dt * - m_params.template get( - "algorithms.timestep.correction") * - m_params.template get("scales.omegaB0"); - const auto eps = m_params.template get( - "algorithms.gr.pusher_eps"); - const auto niter = m_params.template get( - "algorithms.gr.pusher_niter"); - // clang-format off - if (species.pusher() == PrtlPusher::PHOTON) { - auto range_policy = Kokkos::RangePolicy( - 0, - species.npart()); - - Kokkos::parallel_for( - "ParticlePusher", - range_policy, - kernel::gr::Pusher_kernel( - domain.fields.em, - domain.fields.em0, - species.i1, species.i2, species.i3, - species.i1_prev, species.i2_prev, species.i3_prev, - species.dx1, species.dx2, species.dx3, - species.dx1_prev, species.dx2_prev, species.dx3_prev, - species.ux1, species.ux2, species.ux3, - species.phi, species.tag, - domain.mesh.metric, - coeff, dt, - domain.mesh.n_active(in::x1), - domain.mesh.n_active(in::x2), - domain.mesh.n_active(in::x3), - eps, niter, - domain.mesh.prtl_bc() - )); - } else if (species.pusher() == PrtlPusher::BORIS) { - auto range_policy = Kokkos::RangePolicy( - 0, - species.npart()); - Kokkos::parallel_for( - "ParticlePusher", - range_policy, - kernel::gr::Pusher_kernel( - domain.fields.em, - domain.fields.em0, - species.i1, species.i2, species.i3, - species.i1_prev, species.i2_prev, species.i3_prev, - species.dx1, species.dx2, species.dx3, - species.dx1_prev, species.dx2_prev, species.dx3_prev, - species.ux1, species.ux2, species.ux3, - species.phi, species.tag, - domain.mesh.metric, - coeff, dt, - domain.mesh.n_active(in::x1), - domain.mesh.n_active(in::x2), - domain.mesh.n_active(in::x3), - eps, niter, - domain.mesh.prtl_bc() - )); - } else if (species.pusher() == PrtlPusher::NONE) { - // do nothing - } else { - raise::Error("not implemented", HERE); - } - // clang-format on - } - } - }; -} // namespace ntt - -#endif // ENGINES_GRPIC_GRPIC_H diff --git a/src/engines/grpic/currents.h b/src/engines/grpic/currents.h new file mode 100644 index 000000000..11c847533 --- /dev/null +++ b/src/engines/grpic/currents.h @@ -0,0 +1,106 @@ +/** + * @file engines/grpic/currents.h + * @brief Current deposition and filtering routines for the GRPIC engine + * @implements + * - ntt::grpic::CurrentsDeposit<> -> void + * - ntt::grpic::CurrentsFilter<> -> void + * @namespaces: + * - ntt::grpic:: + */ + +#ifndef ENGINES_GRPIC_CURRENTS_H +#define ENGINES_GRPIC_CURRENTS_H + +#include "enums.h" + +#include "traits/metric.h" +#include "utils/log.h" +#include "utils/param_container.h" + +#include "framework/domain/domain.h" +#include "framework/domain/metadomain.h" +#include "framework/parameters/parameters.h" +#include "kernels/currents_deposit.hpp" +#include "kernels/digital_filter.hpp" + +namespace ntt { + namespace grpic { + + template + void CurrentsDeposit(Domain& domain, + const prm::Parameters& engine_params) { + auto scatter_cur0 = Kokkos::Experimental::create_scatter_view( + domain.fields.cur0); + const auto dt = engine_params.get("dt"); + for (auto& species : domain.species) { + logger::Checkpoint( + fmt::format("Launching currents deposit kernel for %d [%s] : %lu %f", + species.index(), + species.label().c_str(), + species.npart(), + (double)species.charge()), + HERE); + if (species.npart() == 0 || cmp::AlmostZero(species.charge())) { + continue; + } + Kokkos::parallel_for("CurrentsDeposit", + species.rangeActiveParticles(), + kernel::DepositCurrents_kernel( + scatter_cur0, + species.i1, + species.i2, + species.i3, + species.i1_prev, + species.i2_prev, + species.i3_prev, + species.dx1, + species.dx2, + species.dx3, + species.dx1_prev, + species.dx2_prev, + species.dx3_prev, + species.ux1, + species.ux2, + species.ux3, + species.phi, + species.weight, + species.tag, + domain.mesh.metric, + (real_t)(species.charge()), + dt)); + } + Kokkos::Experimental::contribute(domain.fields.cur0, scatter_cur0); + } + + template + void CurrentsFilter(Metadomain& metadomain, + Domain& domain, + const SimulationParams& params) { + logger::Checkpoint("Launching currents filtering kernels", HERE); + auto range = CreateRangePolicy( + { domain.mesh.i_min(in::x1), domain.mesh.i_min(in::x2) }, + { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); + const auto nfilter = params.template get( + "algorithms.current_filters"); + tuple_t size; + size[0] = domain.mesh.n_active(in::x1); + size[1] = domain.mesh.n_active(in::x2); + + // !TODO: this needs to be done more efficiently + for (unsigned short i = 0; i < nfilter; ++i) { + Kokkos::deep_copy(domain.fields.buff, domain.fields.cur0); + Kokkos::parallel_for("CurrentsFilter", + range, + kernel::DigitalFilter_kernel( + domain.fields.cur0, + domain.fields.buff, + size, + domain.mesh.flds_bc())); + metadomain.CommunicateFields(domain, Comm::J); // J0 + } + } + + } // namespace grpic +} // namespace ntt + +#endif // ENGINES_GRPIC_CURRENTS_H \ No newline at end of file diff --git a/src/engines/grpic/fields_bcs.h b/src/engines/grpic/fields_bcs.h new file mode 100644 index 000000000..3297a17df --- /dev/null +++ b/src/engines/grpic/fields_bcs.h @@ -0,0 +1,271 @@ +/** + * @file engines/grpic/fields_bcs.h + * @brief Field boundary condition routines for the GRPIC engine + * @implements + * - enum ntt::grpic::gr_bc + * - ntt::grpic::MatchFieldsIn<> -> void + * - ntt::grpic::HorizonFieldsIn<> -> void + * - ntt::grpic::AxisFieldsIn<> -> void + * - ntt::grpic::CustomFieldsIn<> -> void + * - ntt::grpic::FieldBoundaries<> -> void + * @namespaces: + * - ntt::grpic:: + */ + +#ifndef ENGINES_GRPIC_FIELDS_BCS_H +#define ENGINES_GRPIC_FIELDS_BCS_H + +#include "enums.h" +#include "global.h" + +#include "traits/metric.h" +#include "traits/pgen.h" + +#include "framework/domain/domain.h" +#include "framework/parameters/parameters.h" +#include "kernels/fields_bcs.hpp" + +#include "engines/engine.hpp" + +#include + +namespace ntt { + namespace grpic { + + enum class gr_bc : uint8_t { + main, + aux, + curr + }; + + template PG> + void MatchFieldsIn(dir::direction_t direction, + Domain& domain, + const Grid& global_grid, + const PG& pgen, + const SimulationParams& params, + BCTags tags, + const gr_bc& g) { + /** + * match boundaries + */ + const auto ds_array = params.template get>( + "grid.boundaries.match.ds"); + const auto dim = direction.get_dim(); + real_t xg_min, xg_max, xg_edge; + auto sign = direction.get_sign(); + + raise::ErrorIf(((dim != in::x1) or (sign < 0)) and (g == gr_bc::curr), + "Absorption of currents only possible in +x1 (+r)", + HERE); + + real_t ds; + if (sign > 0) { // + direction + ds = ds_array[(short)dim].second; + xg_max = global_grid.extent(dim).second; + xg_min = xg_max - ds; + xg_edge = xg_max; + } else { // - direction + ds = ds_array[(short)dim].first; + xg_min = global_grid.extent(dim).first; + xg_max = xg_min + ds; + xg_edge = xg_min; + } + boundaries_t box; + boundaries_t incl_ghosts; + for (unsigned short d { 0 }; d < M::Dim; ++d) { + if (d == static_cast(dim)) { + box.emplace_back(xg_min, xg_max); + incl_ghosts.emplace_back(false, true); + } else { + box.push_back(Range::All); + incl_ghosts.emplace_back(true, true); + } + } + if (not domain.mesh.Intersects(box)) { + return; + } + const auto intersect_range = domain.mesh.ExtentToRange(box, incl_ghosts); + tuple_t range_min { 0 }; + tuple_t range_max { 0 }; + + for (unsigned short d { 0 }; d < M::Dim; ++d) { + range_min[d] = intersect_range[d].first; + range_max[d] = intersect_range[d].second; + } + if (dim == in::x1) { + if (g != gr_bc::curr) { + if constexpr (::traits::pgen::HasInitFlds) { + Kokkos::parallel_for( + "MatchBoundaries", + CreateRangePolicy(range_min, range_max), + kernel::bc::MatchBoundaries_kernel(domain.fields.em, + pgen.init_flds, + domain.mesh.metric, + xg_edge, + ds, + tags, + domain.mesh.flds_bc())); + Kokkos::parallel_for( + "MatchBoundaries", + CreateRangePolicy(range_min, range_max), + kernel::bc::MatchBoundaries_kernel(domain.fields.em0, + pgen.init_flds, + domain.mesh.metric, + xg_edge, + ds, + tags, + domain.mesh.flds_bc())); + } + } else { + Kokkos::parallel_for( + "AbsorbCurrents", + CreateRangePolicy(range_min, range_max), + kernel::bc::gr::AbsorbCurrents_kernel(domain.fields.cur0, + domain.mesh.metric, + xg_edge, + ds)); + } + } else { + raise::Error("Invalid dimension", HERE); + } + } + + template + void HorizonFieldsIn(dir::direction_t direction, + Domain& domain, + const SimulationParams& params, + BCTags tags, + const gr_bc& g) { + /** + * open boundaries + */ + raise::ErrorIf(M::CoordType == Coord::Cartesian, + "Invalid coordinate type for horizon BCs", + HERE); + raise::ErrorIf(direction.get_dim() != in::x1, + "Invalid horizon direction, should be x1", + HERE); + const auto i1_min = domain.mesh.i_min(in::x1); + auto range = CreateRangePolicy({ domain.mesh.i_min(in::x2) }, + { domain.mesh.i_max(in::x2) + 1 }); + const auto nfilter = params.template get( + "algorithms.current_filters"); + if (g == gr_bc::main) { + Kokkos::parallel_for( + "OpenBCFields", + range, + kernel::bc::gr::HorizonBoundaries_kernel(domain.fields.em, + i1_min, + tags, + nfilter)); + Kokkos::parallel_for( + "OpenBCFields", + range, + kernel::bc::gr::HorizonBoundaries_kernel(domain.fields.em0, + i1_min, + tags, + nfilter)); + } + } + + template + void AxisFieldsIn(dir::direction_t direction, + Domain& domain, + BCTags tags) { + /** + * axis boundaries + */ + raise::ErrorIf(M::CoordType == Coord::Cartesian, + "Invalid coordinate type for axis BCs", + HERE); + raise::ErrorIf(direction.get_dim() != in::x2, + "Invalid axis direction, should be x2", + HERE); + const auto i2_min = domain.mesh.i_min(in::x2); + const auto i2_max = domain.mesh.i_max(in::x2); + if (direction.get_sign() < 0) { + Kokkos::parallel_for( + "AxisBCFields", + domain.mesh.n_all(in::x1), + kernel::bc::AxisBoundaries_kernel(domain.fields.em, + i2_min, + tags)); + Kokkos::parallel_for( + "AxisBCFields", + domain.mesh.n_all(in::x1), + kernel::bc::AxisBoundaries_kernel(domain.fields.em0, + i2_min, + tags)); + } else { + Kokkos::parallel_for( + "AxisBCFields", + domain.mesh.n_all(in::x1), + kernel::bc::AxisBoundaries_kernel(domain.fields.em, + i2_max, + tags)); + Kokkos::parallel_for( + "AxisBCFields", + domain.mesh.n_all(in::x1), + kernel::bc::AxisBoundaries_kernel(domain.fields.em0, + i2_max, + tags)); + } + } + + template + void CustomFieldsIn(dir::direction_t direction, + Domain& domain, + BCTags tags, + const gr_bc& g) { + (void)direction; + (void)domain; + (void)tags; + (void)g; + raise::Error("Custom boundaries not implemented", HERE); + } + + template PG> + void FieldBoundaries(Domain& domain, + const Grid& global_grid, + const PG& pgen, + const SimulationParams& params, + BCTags tags, + const gr_bc& g) { + if (g == gr_bc::main) { + for (auto& direction : dir::Directions::orth) { + if (global_grid.flds_bc_in(direction) == FldsBC::MATCH) { + MatchFieldsIn(direction, domain, global_grid, pgen, params, tags, g); + } else if (domain.mesh.flds_bc_in(direction) == FldsBC::AXIS) { + AxisFieldsIn(direction, domain, tags); + } else if (global_grid.flds_bc_in(direction) == FldsBC::CUSTOM) { + CustomFieldsIn(direction, domain, tags, g); + } else if (domain.mesh.flds_bc_in(direction) == FldsBC::HORIZON) { + HorizonFieldsIn(direction, domain, params, tags, g); + } + } // loop over directions + } else if (g == gr_bc::aux) { + for (auto& direction : dir::Directions::orth) { + if (domain.mesh.flds_bc_in(direction) == FldsBC::HORIZON) { + HorizonFieldsIn(direction, domain, params, tags, g); + } + } + } else if (g == gr_bc::curr) { + for (auto& direction : dir::Directions::orth) { + if (domain.mesh.prtl_bc_in(direction) == PrtlBC::ABSORB) { + MatchFieldsIn(direction, domain, global_grid, pgen, params, tags, g); + } + } + } + } + + } // namespace grpic +} // namespace ntt + +#endif // ENGINES_GRPIC_FIELDS_BCS_H \ No newline at end of file diff --git a/src/engines/grpic/fieldsolvers.h b/src/engines/grpic/fieldsolvers.h new file mode 100644 index 000000000..476583908 --- /dev/null +++ b/src/engines/grpic/fieldsolvers.h @@ -0,0 +1,267 @@ +/** + * @file engines/grpic/fieldsolvers.h + * @brief Field solver routines (Faraday, Ampere, auxiliary fields) for the GRPIC engine + * @implements + * - enum ntt::grpic::gr_getE + * - enum ntt::grpic::gr_getH + * - enum ntt::grpic::gr_faraday + * - enum ntt::grpic::gr_ampere + * - ntt::grpic::range_with_axis_BCs<> -> auto + * - ntt::grpic::ComputeAuxE<> -> void + * - ntt::grpic::ComputeAuxH<> -> void + * - ntt::grpic::Faraday<> -> void + * - ntt::grpic::Ampere<> -> void + * - ntt::grpic::AmpereCurrents<> -> void + * @namespaces: + * - ntt::grpic:: + */ + +#ifndef ENGINES_GRPIC_FIELDSOLVERS_H +#define ENGINES_GRPIC_FIELDSOLVERS_H + +#include "enums.h" + +#include "utils/log.h" + +#include "framework/domain/domain.h" +#include "framework/parameters/parameters.h" +#include "kernels/ampere_gr.hpp" +#include "kernels/aux_fields_gr.hpp" +#include "kernels/faraday_gr.hpp" + +#include + +namespace ntt { + namespace grpic { + + enum class gr_getE : uint8_t { + D0_B, + D_B0 + }; + enum class gr_getH : uint8_t { + D_B0, + D0_B0 + }; + enum class gr_faraday : uint8_t { + aux, + main + }; + enum class gr_ampere : uint8_t { + init, + aux, + main + }; + + template + auto range_with_axis_BCs(const Domain& domain) + -> range_t { + auto range = domain.mesh.rangeActiveCells(); + /** + * @brief taking one extra cell in the x1 and x2 directions if AXIS BCs + */ + if constexpr (M::Dim == Dim::_2D) { + if (domain.mesh.flds_bc_in({ 0, +1 }) == FldsBC::AXIS) { + range = CreateRangePolicy( + { domain.mesh.i_min(in::x1) - 1, domain.mesh.i_min(in::x2) }, + { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); + } else { + range = CreateRangePolicy( + { domain.mesh.i_min(in::x1) - 1, domain.mesh.i_min(in::x2) }, + { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) }); + } + } else if constexpr (M::Dim == Dim::_3D) { + raise::Error("Invalid dimension", HERE); + } + return range; + } + + template + void ComputeAuxE(Domain& domain, const gr_getE& g) { + auto range = range_with_axis_BCs(domain); + if (g == gr_getE::D0_B) { + Kokkos::parallel_for( + "ComputeAuxE", + range, + kernel::gr::ComputeAuxE_kernel(domain.fields.em0, // D + domain.fields.em, // B + domain.fields.aux, // E + domain.mesh.metric)); + } else if (g == gr_getE::D_B0) { + Kokkos::parallel_for("ComputeAuxE", + range, + kernel::gr::ComputeAuxE_kernel(domain.fields.em, + domain.fields.em0, + domain.fields.aux, + domain.mesh.metric)); + } else { + raise::Error("Wrong option for `g`", HERE); + } + } + + template + void ComputeAuxH(Domain& domain, const gr_getH& g) { + auto range = range_with_axis_BCs(domain); + if (g == gr_getH::D_B0) { + Kokkos::parallel_for( + "ComputeAuxH", + range, + kernel::gr::ComputeAuxH_kernel(domain.fields.em, // D + domain.fields.em0, // B + domain.fields.aux, // H + domain.mesh.metric)); + } else if (g == gr_getH::D0_B0) { + Kokkos::parallel_for("ComputeAuxH", + range, + kernel::gr::ComputeAuxH_kernel(domain.fields.em0, + domain.fields.em0, + domain.fields.aux, + domain.mesh.metric)); + } else { + raise::Error("Wrong option for `g`", HERE); + } + } + + template + void Faraday(Domain& domain, + const SimulationParams& params, + const prm::Parameters& engine_params, + const gr_faraday& g, + real_t fraction = ONE) { + logger::Checkpoint("Launching Faraday kernel", HERE); + const auto dt = engine_params.get("dt"); + const auto dT = fraction * + params.template get( + "algorithms.timestep.correction") * + dt; + if (g == gr_faraday::aux) { + Kokkos::parallel_for( + "Faraday", + domain.mesh.rangeActiveCells(), + kernel::gr::Faraday_kernel(domain.fields.em0, // Bin + domain.fields.em0, // Bout + domain.fields.aux, // E + domain.mesh.metric, + dT, + domain.mesh.n_active(in::x2), + domain.mesh.flds_bc())); + } else if (g == gr_faraday::main) { + Kokkos::parallel_for( + "Faraday", + domain.mesh.rangeActiveCells(), + kernel::gr::Faraday_kernel(domain.fields.em, + domain.fields.em0, + domain.fields.aux, + domain.mesh.metric, + dT, + domain.mesh.n_active(in::x2), + domain.mesh.flds_bc())); + + } else { + raise::Error("Wrong option for `g`", HERE); + } + } + + template + void Ampere(Domain& domain, + const SimulationParams& params, + const prm::Parameters& engine_params, + const gr_ampere& g, + real_t fraction = ONE) { + logger::Checkpoint("Launching Ampere kernel", HERE); + + const auto dt = engine_params.get("dt"); + const auto dT = fraction * + params.template get( + "algorithms.timestep.correction") * + dt; + auto range = CreateRangePolicy( + { domain.mesh.i_min(in::x1), domain.mesh.i_min(in::x2) }, + { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); + const auto ni2 = domain.mesh.n_active(in::x2); + + if (g == gr_ampere::aux) { + // First push, updates D0 with J. + Kokkos::parallel_for("Ampere-1", + range, + kernel::gr::Ampere_kernel(domain.fields.em0, // Din + domain.fields.em0, // Dout + domain.fields.aux, + domain.mesh.metric, + dT, + ni2, + domain.mesh.flds_bc())); + } else if (g == gr_ampere::main) { + // Second push, updates D with J0 but assigns it to D0. + Kokkos::parallel_for("Ampere-2", + range, + kernel::gr::Ampere_kernel(domain.fields.em, + domain.fields.em0, + domain.fields.aux, + domain.mesh.metric, + dT, + ni2, + domain.mesh.flds_bc())); + } else if (g == gr_ampere::init) { + // Second push, updates D with J0 and assigns it to D. + Kokkos::parallel_for("Ampere-3", + range, + kernel::gr::Ampere_kernel(domain.fields.em, + domain.fields.em, + domain.fields.aux, + domain.mesh.metric, + dT, + ni2, + domain.mesh.flds_bc())); + } else { + raise::Error("Wrong option for `g`", HERE); + } + } + + template + void AmpereCurrents(Domain& domain, + const SimulationParams& params, + const prm::Parameters& engine_params, + const gr_ampere& g) { + logger::Checkpoint("Launching Ampere kernel for adding currents", HERE); + + const auto dt = engine_params.get("dt"); + + const auto q0 = params.template get("scales.q0"); + const auto B0 = params.template get("scales.B0"); + const auto coeff = -dt * q0 / B0; + auto range = CreateRangePolicy( + { domain.mesh.i_min(in::x1), domain.mesh.i_min(in::x2) }, + { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); + const auto ni2 = domain.mesh.n_active(in::x2); + + if (g == gr_ampere::aux) { + // Updates D0 with J: D0(n-1/2) -> (J(n)) -> D0(n+1/2) + Kokkos::parallel_for( + "AmpereCurrentsAux", + range, + kernel::gr::CurrentsAmpere_kernel(domain.fields.em0, + domain.fields.cur, + domain.mesh.metric, + coeff, + ni2, + domain.mesh.flds_bc())); + } else if (g == gr_ampere::main) { + // Updates D0 with J0: D0(n) -> (J0(n+1/2)) -> D0(n+1) + Kokkos::parallel_for( + "AmpereCurrentsMain", + range, + kernel::gr::CurrentsAmpere_kernel(domain.fields.em0, + domain.fields.cur0, + domain.mesh.metric, + coeff, + ni2, + domain.mesh.flds_bc())); + } else { + raise::Error("Wrong option for `g`", HERE); + } + } + + } // namespace grpic +} // namespace ntt + +#endif // ENGINES_GRPIC_FIELDSOLVERS_H \ No newline at end of file diff --git a/src/engines/grpic/grpic.hpp b/src/engines/grpic/grpic.hpp new file mode 100644 index 000000000..fc2daa4de --- /dev/null +++ b/src/engines/grpic/grpic.hpp @@ -0,0 +1,638 @@ +/** + * @file engines/grpic/grpic.hpp + * @brief Simulation engine class which specializes on GRPIC + * @implements + * - ntt::GRPICEngine<> : ntt::Engine<> + * @cpp: + * - grpic.cpp + * @namespaces: + * - ntt:: + */ + +#ifndef ENGINES_GRPIC_GRPIC_HPP +#define ENGINES_GRPIC_GRPIC_HPP + +#include "enums.h" +#include "global.h" + +#include "traits/metric.h" +#include "utils/numeric.h" +#include "utils/timer.h" + +#include "engines/grpic/currents.h" +#include "engines/grpic/fields_bcs.h" +#include "engines/grpic/fieldsolvers.h" +#include "engines/grpic/particle_pusher.h" +#include "engines/grpic/utils.h" +#include "framework/domain/domain.h" +#include "framework/parameters/parameters.h" + +#include "engines/engine.hpp" +#include "pgen.hpp" + +#include +#include +#include + +#include + +namespace ntt { + + template + class GRPICEngine : public Engine { + using base_t = Engine; + using pgen_t = user::PGen; + using domain_t = Domain; + // contents + using base_t::m_metadomain; + using base_t::m_params; + using base_t::m_pgen; + // methods + using base_t::init; + // variables + using base_t::dt; + using base_t::max_steps; + using base_t::runtime; + using base_t::step; + using base_t::time; + + public: + static constexpr auto S { SimEngine::GRPIC }; + + GRPICEngine(SimulationParams& params) : base_t { params } {} + + ~GRPICEngine() override = default; + + void step_forward(timer::Timers& timers, domain_t& dom) override { + const auto fieldsolver_enabled = m_params.template get( + "algorithms.fieldsolver.enable"); + const auto deposit_enabled = m_params.template get( + "algorithms.deposit.enable"); + const auto clear_interval = m_params.template get( + "particles.clear_interval"); + + if (step == 0) { + if (fieldsolver_enabled) { + // communicate fields and apply BCs on the first timestep + /** + * Initially: em0::B -- + * em0::D -- + * em::B at -1/2 + * em::D at -1/2 + * + * cur0::J -- + * cur::J -- + * + * aux::E -- + * aux::H -- + * + * x_prtl at -1/2 + * u_prtl at -1/2 + */ + + /** + * em0::D, em::D, em0::B, em::B <- boundary conditions + */ + m_metadomain.CommunicateFields(dom, + Comm::B | Comm::B0 | Comm::D | Comm::D0); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::B | BC::D, + grpic::gr_bc::main); + + /** + * em0::B <- em::B + * em0::D <- em::D + * + * Now: em0::B & em0::D at -1/2 + */ + grpic::CopyFields(dom); + + /** + * aux::E <- alpha * em::D + beta x em0::B + * aux::H <- alpha * em::B0 - beta x em::D + * + * Now: aux::E & aux::H at -1/2 + */ + grpic::ComputeAuxE(dom, grpic::gr_getE::D_B0); + grpic::ComputeAuxH(dom, grpic::gr_getH::D_B0); + + /** + * aux::E, aux::H <- boundary conditions + */ + m_metadomain.CommunicateFields(dom, Comm::H | Comm::E); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::H | BC::E, + grpic::gr_bc::aux); + + /** + * em0::B <- (em0::B) <- -curl aux::E + * + * Now: em0::B at 0 + */ + grpic::Faraday(dom, + m_params, + this->engineParams(), + grpic::gr_faraday::aux, + HALF); + + /** + * em0::B, em::B <- boundary conditions + */ + m_metadomain.CommunicateFields(dom, Comm::B | Comm::B0); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::B, + grpic::gr_bc::main); + + /** + * em::D <- (em0::D) <- curl aux::H + * + * Now: em::D at 0 + */ + grpic::Ampere(dom, + m_params, + this->engineParams(), + grpic::gr_ampere::init, + HALF); + + /** + * em0::D, em::D <- boundary conditions + */ + m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::D, + grpic::gr_bc::main); + + /** + * aux::E <- alpha * em::D + beta x em0::B + * aux::H <- alpha * em0::B - beta x em::D + * + * Now: aux::E & aux::H at 0 + */ + grpic::ComputeAuxE(dom, grpic::gr_getE::D_B0); + grpic::ComputeAuxH(dom, grpic::gr_getH::D_B0); + + /** + * aux::E, aux::H <- boundary conditions + */ + m_metadomain.CommunicateFields(dom, Comm::H | Comm::E); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::H | BC::E, + grpic::gr_bc::aux); + + // !ADD: GR -- particles? + + /** + * em0::B <- (em::B) <- -curl aux::E + * + * Now: em0::B at 1/2 + */ + grpic::Faraday(dom, + m_params, + this->engineParams(), + grpic::gr_faraday::main, + ONE); + /** + * em0::B, em::B <- boundary conditions + */ + m_metadomain.CommunicateFields(dom, Comm::B | Comm::B0); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::B, + grpic::gr_bc::main); + + /** + * em0::D <- (em0::D) <- curl aux::H + * + * Now: em0::D at 1/2 + */ + grpic::Ampere(dom, m_params, this->engineParams(), grpic::gr_ampere::aux, ONE); + /** + * em0::D, em::D <- boundary conditions + */ + m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::D, + grpic::gr_bc::main); + + /** + * aux::H <- alpha * em0::B - beta x em0::D + * + * Now: aux::H at 1/2 + */ + grpic::ComputeAuxH(dom, grpic::gr_getH::D0_B0); + /** + * aux::H <- boundary conditions + */ + m_metadomain.CommunicateFields(dom, Comm::H); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::H, + grpic::gr_bc::aux); + + /** + * em0::D <- (em::D) <- curl aux::H + * + * Now: em0::D at 1 + * em::D at 0 + */ + grpic::Ampere(dom, m_params, this->engineParams(), grpic::gr_ampere::main, ONE); + /** + * em0::D, em::D <- boundary conditions + */ + m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::D, + grpic::gr_bc::main); + + /** + * em::D <-> em0::D + * em::B <-> em0::B + * em::J <-> em0::J + */ + grpic::SwapFields(dom); + /** + * Finally: em0::B at -1/2 + * em0::D at 0 + * em::B at 1/2 + * em::D at 1 + * + * cur0::J -- + * cur::J -- + * + * aux::E -- + * aux::H -- + * + * x_prtl at 1 + * u_prtl at 1/2 + */ + } else { + /** + * em0::B <- em::B + * em0::D <- em::D + * + * Now: em0::B & em0::D at -1/2 + */ + grpic::CopyFields(dom); + } + } + + /** + * Initially: em0::B at n-3/2 + * em0::D at n-1 + * em::B at n-1/2 + * em::D at n + * + * cur0::J -- + * cur::J at n-1/2 + * + * aux::E -- + * aux::H -- + * + * x_prtl at n + * u_prtl at n-1/2 + */ + + if (fieldsolver_enabled) { + timers.start("FieldSolver"); + /** + * em0::D <- (em0::D + em::D) / 2 + * em0::B <- (em0::B + em::B) / 2 + * + * Now: em0::D at n-1/2 + * em0::B at n-1 + */ + grpic::TimeAverageDB(dom); + /** + * aux::E <- alpha * em0::D + beta x em::B + * + * Now: aux::E at n-1/2 + */ + grpic::ComputeAuxE(dom, grpic::gr_getE::D0_B); + timers.stop("FieldSolver"); + + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::E); + timers.stop("Communications"); + timers.start("FieldBoundaries"); + /** + * aux::E <- boundary conditions + */ + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::E, + grpic::gr_bc::aux); + timers.stop("FieldBoundaries"); + + timers.start("FieldSolver"); + /** + * em0::B <- (em0::B) <- -curl aux::E + * + * Now: em0::B at n + */ + grpic::Faraday(dom, m_params, this->engineParams(), grpic::gr_faraday::aux, ONE); + timers.stop("FieldSolver"); + + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::B | Comm::B0); + timers.stop("Communications"); + /** + * em0::B, em::B <- boundary conditions + */ + timers.start("FieldBoundaries"); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::B, + grpic::gr_bc::main); + timers.stop("FieldBoundaries"); + + timers.start("FieldSolver"); + /** + * aux::H <- alpha * em0::B - beta x em::D + * + * Now: aux::H at n + */ + grpic::ComputeAuxH(dom, grpic::gr_getH::D_B0); + timers.stop("FieldSolver"); + + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::H); + timers.stop("Communications"); + timers.start("FieldBoundaries"); + /** + * aux::H <- boundary conditions + */ + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::H, + grpic::gr_bc::aux); + timers.stop("FieldBoundaries"); + } + + { + /** + * x_prtl, u_prtl <- em::D, em0::B + * + * Now: x_prtl at n + 1, u_prtl at n + 1/2 + */ + timers.start("ParticlePusher"); + grpic::ParticlePush(dom, m_params, this->engineParams()); + timers.stop("ParticlePusher"); + + /** + * cur0::J <- current deposition + * + * Now: cur0::J at n+1/2 + */ + if (deposit_enabled) { + timers.start("CurrentDeposit"); + Kokkos::deep_copy(dom.fields.cur0, ZERO); + grpic::CurrentsDeposit(dom, this->engineParams()); + timers.stop("CurrentDeposit"); + + timers.start("Communications"); + m_metadomain.SynchronizeFields(dom, Comm::J); + m_metadomain.CommunicateFields(dom, Comm::J); + timers.stop("Communications"); + + timers.start("FieldBoundaries"); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::J, + grpic::gr_bc::curr); + timers.stop("FieldBoundaries"); + + timers.start("CurrentFiltering"); + grpic::CurrentsFilter(m_metadomain, dom, m_params); + timers.stop("CurrentFiltering"); + } + + timers.start("Communications"); + m_metadomain.CommunicateParticles(dom); + timers.stop("Communications"); + } + + if (fieldsolver_enabled) { + timers.start("FieldSolver"); + if (deposit_enabled) { + /** + * cur::J <- (cur0::J + cur::J) / 2 + * + * Now: cur::J at n + */ + grpic::TimeAverageJ(dom); + } + /** + * aux::Е <- alpha * em::D + beta x em0::B + * + * Now: aux::Е at n + */ + grpic::ComputeAuxE(dom, grpic::gr_getE::D_B0); + timers.stop("FieldSolver"); + + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::E); + timers.stop("Communications"); + timers.start("FieldBoundaries"); + /** + * aux::Е <- boundary conditions + */ + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::E, + grpic::gr_bc::aux); + timers.stop("FieldBoundaries"); + + timers.start("FieldSolver"); + /** + * em0::B <- (em::B) <- -curl aux::E + * + * Now: em0::B at n+1/2 + * em::B at n-1/2 + */ + grpic::Faraday(dom, m_params, this->engineParams(), grpic::gr_faraday::main, ONE); + timers.stop("FieldSolver"); + + /** + * em0::B, em::B <- boundary conditions + */ + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::B | Comm::B0); + timers.stop("Communications"); + timers.start("FieldBoundaries"); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::B, + grpic::gr_bc::main); + timers.stop("FieldBoundaries"); + + timers.start("FieldSolver"); + /** + * em0::D <- (em0::D) <- curl aux::H + * + * Now: em0::D at n+1/2 + */ + grpic::Ampere(dom, m_params, this->engineParams(), grpic::gr_ampere::aux, ONE); + timers.stop("FieldSolver"); + + if (deposit_enabled) { + timers.start("FieldSolver"); + /** + * em0::D <- (em0::D) <- cur::J + * + * Now: em0::D at n+1/2 + */ + grpic::AmpereCurrents(dom, + m_params, + this->engineParams(), + grpic::gr_ampere::aux); + timers.stop("FieldSolver"); + } + + /** + * em0::D, em::D <- boundary conditions + */ + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); + timers.stop("Communications"); + timers.start("FieldBoundaries"); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::D, + grpic::gr_bc::main); + timers.stop("FieldBoundaries"); + + timers.start("FieldSolver"); + /** + * aux::H <- alpha * em0::B - beta x em0::D + * + * Now: aux::H at n+1/2 + */ + grpic::ComputeAuxH(dom, grpic::gr_getH::D0_B0); + timers.stop("FieldSolver"); + + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::H); + timers.stop("Communications"); + timers.start("FieldBoundaries"); + /** + * aux::H <- boundary conditions + */ + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::H, + grpic::gr_bc::aux); + timers.stop("FieldBoundaries"); + + timers.start("FieldSolver"); + /** + * em0::D <- (em::D) <- curl aux::H + * + * Now: em0::D at n+1 + * em::D at n + */ + grpic::Ampere(dom, m_params, this->engineParams(), grpic::gr_ampere::main, ONE); + timers.stop("FieldSolver"); + + if (deposit_enabled) { + timers.start("FieldSolver"); + /** + * em0::D <- (em0::D) <- cur0::J + * + * Now: em0::D at n+1 + */ + grpic::AmpereCurrents(dom, + m_params, + this->engineParams(), + grpic::gr_ampere::main); + timers.stop("FieldSolver"); + } + timers.start("FieldSolver"); + /** + * em::D <-> em0::D + * em::B <-> em0::B + * cur::J <-> cur0::J + */ + grpic::SwapFields(dom); + timers.stop("FieldSolver"); + + /** + * em0::D, em::D <- boundary conditions + */ + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::D | Comm::D0); + timers.stop("Communications"); + timers.start("FieldBoundaries"); + grpic::FieldBoundaries(dom, + m_metadomain.mesh(), + m_pgen, + m_params, + BC::D, + grpic::gr_bc::main); + timers.stop("FieldBoundaries"); + } + + timers.start("ParticleSort"); + m_metadomain.SortParticles(time, step, m_params, dom); + timers.stop("ParticleSort"); + + /** + * Finally: em0::B at n-1/2 + * em0::D at n + * em::B at n+1/2 + * em::D at n+1 + * + * cur0::J (at n) + * cur::J at n+1/2 + * + * aux::E (at n+1/2) + * aux::H (at n) + * + * x_prtl at n+1 + * u_prtl at n+1/2 + */ + } + }; +} // namespace ntt + +#endif // ENGINES_GRPIC_GRPIC_HPP diff --git a/src/engines/grpic/particle_pusher.h b/src/engines/grpic/particle_pusher.h new file mode 100644 index 000000000..ebcfe2912 --- /dev/null +++ b/src/engines/grpic/particle_pusher.h @@ -0,0 +1,94 @@ +/** + * @file engines/grpic/particle_pusher.h + * @brief Particle pusher routines for the GRPIC engine + * @implements + * - ntt::grpic::ParticlePush<> -> void + * @namespaces: + * - ntt::grpic:: + */ + +#ifndef ENGINES_GRPIC_PARTICLE_PUSHER_H +#define ENGINES_GRPIC_PARTICLE_PUSHER_H + +#include "enums.h" + +#include "traits/metric.h" +#include "utils/log.h" +#include "utils/param_container.h" + +#include "framework/domain/domain.h" +#include "framework/parameters/parameters.h" +#include "kernels/pushers/gr.hpp" + +namespace ntt { + namespace grpic { + + template + void ParticlePush(Domain& domain, + const SimulationParams& params, + const prm::Parameters& engine_params) { + const auto dt = engine_params.get("dt"); + for (auto& species : domain.species) { + species.set_unsorted(); + logger::Checkpoint( + fmt::format("Launching particle pusher kernel for %d [%s] : %lu", + species.index(), + species.label().c_str(), + species.npart()), + HERE); + if ((species.npart() == 0) or (species.pusher() == ParticlePusher::NONE)) { + continue; + } + const auto pusher_ctx = kernel::gr::PusherContext { + species.mass(), + species.charge(), + dt, + params.template get("scales.omegaB0"), + params.template get("algorithms.gr.pusher_eps"), + params.template get("algorithms.gr.pusher_niter"), + static_cast(domain.mesh.n_active(in::x1)), + static_cast(domain.mesh.n_active(in::x2)), + static_cast(domain.mesh.n_active(in::x3)) + }; + + const auto pusher_boundaries = kernel::gr::PusherBoundaries { + domain.mesh.prtl_bc() + }; + // auto pusher_arrays = species.PusherKernelArrays(); + + if (species.pusher() == ParticlePusher::PHOTON) { + const auto range_policy = + Kokkos::RangePolicy( + 0, + species.npart()); + Kokkos::parallel_for("ParticlePusher", + range_policy, + kernel::gr::Pusher_kernel(pusher_ctx, + pusher_boundaries, + species, + domain.fields.em, + domain.fields.em0, + domain.mesh.metric)); + } else if (species.pusher() == ParticlePusher::BORIS) { + const auto range_policy = + Kokkos::RangePolicy( + 0, + species.npart()); + Kokkos::parallel_for("ParticlePusher", + range_policy, + kernel::gr::Pusher_kernel(pusher_ctx, + pusher_boundaries, + species, + domain.fields.em, + domain.fields.em0, + domain.mesh.metric)); + } else { + raise::Error("not implemented", HERE); + } + } + } + + } // namespace grpic +} // namespace ntt + +#endif // ENGINES_GRPIC_PARTICLE_PUSHER_H diff --git a/src/engines/grpic/utils.h b/src/engines/grpic/utils.h new file mode 100644 index 000000000..c6f43ec2c --- /dev/null +++ b/src/engines/grpic/utils.h @@ -0,0 +1,66 @@ +/** + * @file engines/grpic/utils.h + * @brief Utility functions used by the GRPIC engine + * @implements + * - ntt::grpic::SwapFields<> -> void + * - ntt::grpic::CopyFields<> -> void + * - ntt::grpic::TimeAverageDB<> -> void + * - ntt::grpic::TimeAverageJ<> -> void + * @namespaces: + * - ntt::grpic:: + */ + +#ifndef ENGINES_GRPIC_UTILS_H +#define ENGINES_GRPIC_UTILS_H + +#include "enums.h" + +#include "traits/metric.h" + +#include "framework/domain/domain.h" +#include "kernels/aux_fields_gr.hpp" + +#include + +namespace ntt { + namespace grpic { + + /** + * @brief Swaps em and em0 fields, cur and cur0 currents. + */ + template + void SwapFields(Domain& domain) { + std::swap(domain.fields.em, domain.fields.em0); + std::swap(domain.fields.cur, domain.fields.cur0); + } + + /** + * @brief Copies em fields into em0 + */ + template + void CopyFields(Domain& domain) { + Kokkos::deep_copy(domain.fields.em0, domain.fields.em); + } + + template + void TimeAverageDB(Domain& domain) { + Kokkos::parallel_for( + "TimeAverageDB", + domain.mesh.rangeActiveCells(), + kernel::gr::TimeAverageDB_kernel(domain.fields.em, + domain.fields.em0)); + } + + template + void TimeAverageJ(Domain& domain) { + Kokkos::parallel_for( + "TimeAverageJ", + domain.mesh.rangeActiveCells(), + kernel::gr::TimeAverageJ_kernel(domain.fields.cur, + domain.fields.cur0)); + } + + } // namespace grpic +} // namespace ntt + +#endif // ENGINES_GRPIC_UTILS_H \ No newline at end of file diff --git a/src/engines/reporter.cpp b/src/engines/reporter.cpp new file mode 100644 index 000000000..f56874d23 --- /dev/null +++ b/src/engines/reporter.cpp @@ -0,0 +1,155 @@ +#include "engines/reporter.h" + +#include "enums.h" +#include "global.h" + +#include "utils/formatting.h" +#include "utils/reporter.h" + +#include "framework/parameters/parameters.h" + +#include +#include + +namespace ntt { + + auto ReportSimulationConfig(const SimulationParams& params, + SimEngine S, + Metric M, + real_t dt, + simtime_t runtime, + timestep_t max_steps, + const std::vector& ndomains_per_dim, + unsigned int ndomains) -> std::string { + std::string report; + /* + * Simulation configs + */ + reporter::AddCategory(report, 4, "Configuration"); + reporter::AddParam(report, + 4, + "Name", + "%s", + params.template get("simulation.name").c_str()); + reporter::AddParam(report, 4, "Engine", "%s", SimEngine(S).to_string()); + reporter::AddParam(report, 4, "Metric", "%s", M.to_string()); +#if SHAPE_ORDER == 0 + reporter::AddParam(report, 4, "Deposit", "%s", "zigzag"); +#else + reporter::AddParam(report, 4, "Deposit", "%s", "esirkepov"); + reporter::AddParam(report, 4, "Interpolation order", "%i", SHAPE_ORDER); +#endif + reporter::AddParam(report, 4, "Timestep [dt]", "%.3e", dt); + reporter::AddParam(report, 4, "Runtime", "%.3e [%d steps]", runtime, max_steps); + report += "\n"; + reporter::AddCategory(report, 4, "Global domain"); + reporter::AddParam( + report, + 4, + "Resolution", + "%s", + params.template stringize("grid.resolution").c_str()); + reporter::AddParam(report, + 4, + "Extent", + "%s", + params.template stringize("grid.extent").c_str()); + reporter::AddParam(report, + 4, + "Fiducial cell size [dx0]", + "%.3e", + params.template get("scales.dx0")); + reporter::AddSubcategory(report, 4, "Boundary conditions"); + reporter::AddParam( + report, + 6, + "Fields", + "%s", + params.template stringize("grid.boundaries.fields").c_str()); + reporter::AddParam( + report, + 6, + "Particles", + "%s", + params.template stringize("grid.boundaries.particles").c_str()); + reporter::AddParam(report, + 4, + "Domain decomposition", + "%s [%d total]", + fmt::formatVector(ndomains_per_dim).c_str(), + ndomains); + report += "\n"; + reporter::AddCategory(report, 4, "Nominal parameters"); + reporter::AddParam(report, + 4, + "Particles per cell [ppc0]", + "%.1f", + params.template get("particles.ppc0")); + reporter::AddParam(report, + 4, + "Larmor radius [larmor0]", + "%.3e [%.3f dx0]", + params.template get("scales.larmor0"), + params.template get("scales.larmor0") / + params.template get("scales.dx0")); + reporter::AddParam(report, + 4, + "Larmor frequency [omegaB0 * dt]", + "%.3e", + params.template get("scales.omegaB0") * + params.template get("algorithms.timestep.dt")); + reporter::AddParam(report, + 4, + "Skin depth [skindepth0]", + "%.3e [%.3f dx0]", + params.template get("scales.skindepth0"), + params.template get("scales.skindepth0") / + params.template get("scales.dx0")); + reporter::AddParam(report, + 4, + "Plasma frequency [omp0 * dt]", + "%.3e", + params.template get("algorithms.timestep.dt") / + params.template get("scales.skindepth0")); + reporter::AddParam(report, + 4, + "Magnetization [sigma0]", + "%.3e", + params.template get("scales.sigma0")); + + if (params.contains("radiation.emission.compton.photon_species")) { + reporter::AddCategory(report, 4, "- Compton emission"); + reporter::AddParam(report, + 6, + "Nominal probability", + "%.3e", + params.template get( + "radiation.emission.compton.nominal_probability")); + reporter::AddParam(report, + 6, + "Nominal photon energy", + "%.3e", + params.template get( + "radiation.emission.compton.nominal_photon_energy")); + } + if (params.contains("radiation.emission.synchrotron.photon_species")) { + reporter::AddCategory(report, 4, "- Synchrotron emission"); + reporter::AddParam( + report, + 6, + "Nominal probability", + "%.3e", + params.template get( + "radiation.emission.synchrotron.nominal_probability")); + reporter::AddParam( + report, + 6, + "Nominal photon energy", + "%.3e", + params.template get( + "radiation.emission.synchrotron.nominal_photon_energy")); + } + return report; + } + +} // namespace ntt diff --git a/src/engines/reporter.h b/src/engines/reporter.h new file mode 100644 index 000000000..1cc0a3495 --- /dev/null +++ b/src/engines/reporter.h @@ -0,0 +1,124 @@ +/** + * @file engines/reporter.h + * @brief Functions for reporting simulation and problem generator configuration + * @implements + * - ntt::ReportSimulationConfig -> std::string + * - ntt::ReportPgenConfig<> -> std::string + * @namespaces: + * - ntt:: + */ + +#ifndef ENGINES_REPORTER_H +#define ENGINES_REPORTER_H + +#include "enums.h" + +#include "traits/pgen.h" +#include "utils/reporter.h" + +#include "framework/parameters/parameters.h" + +#include +#include + +namespace ntt { + + auto ReportSimulationConfig(const SimulationParams&, + SimEngine, + Metric, + real_t, + simtime_t, + timestep_t, + const std::vector&, + unsigned int) -> std::string; + + template + inline auto ReportPgenConfig(const PG& /*pgen*/, const std::string& pgen_name) + -> std::string { + std::string report; + report += "\n"; + reporter::AddCategory(report, 4, "Problem generator"); + reporter::AddParam(report, 6, "Name", "%s", pgen_name.c_str()); + reporter::AddSubcategory(report, 6, "Methods defined"); + + const auto BoolToOnOff = [](bool toggle) -> const char* { + return toggle ? "ON" : "OFF"; + }; + + reporter::AddParam(report, + 8, + "InitFlds", + "%s", + BoolToOnOff(::traits::pgen::HasInitFlds)); + reporter::AddParam(report, + 8, + "InitPrtls", + "%s", + BoolToOnOff(::traits::pgen::HasInitPrtls)); + reporter::AddParam(report, + 8, + "CustomPostStep", + "%s", + BoolToOnOff(::traits::pgen::HasCustomPostStep)); + reporter::AddParam(report, + 8, + "ExternalFields", + "%s", + BoolToOnOff(::traits::pgen::HasExternalFields)); + reporter::AddParam(report, + 8, + "ext_current", + "%s", + BoolToOnOff(::traits::pgen::HasExtCurrent)); + reporter::AddParam(report, + 8, + "AtmFields", + "%s", + BoolToOnOff(::traits::pgen::HasAtmFields)); + reporter::AddParam(report, + 8, + "MatchFields", + "%s", + BoolToOnOff(::traits::pgen::HasMatchFields)); + reporter::AddParam(report, + 8, + "MatchFieldsInX1", + "%s", + BoolToOnOff(::traits::pgen::HasMatchFieldsInX1)); + reporter::AddParam(report, + 8, + "MatchFieldsInX2", + "%s", + BoolToOnOff(::traits::pgen::HasMatchFieldsInX2)); + reporter::AddParam(report, + 8, + "MatchFieldsInX3", + "%s", + BoolToOnOff(::traits::pgen::HasMatchFieldsInX3)); + reporter::AddParam(report, + 8, + "FixFieldsConst", + "%s", + BoolToOnOff(::traits::pgen::HasFixFieldsConst)); + reporter::AddParam(report, + 8, + "EmissionPolicy", + "%s", + BoolToOnOff(::traits::pgen::HasEmissionPolicy)); + reporter::AddParam( + report, + 8, + "CustomFieldOutput", + "%s", + BoolToOnOff(::traits::pgen::HasCustomFieldOutput)); + reporter::AddParam(report, + 8, + "CustomStatOutput", + "%s", + BoolToOnOff(::traits::pgen::HasCustomStatOutput)); + return report; + } + +} // namespace ntt + +#endif // ENGINES_REPORTER_H diff --git a/src/engines/srpic.hpp b/src/engines/srpic.hpp deleted file mode 100644 index 0dad75e91..000000000 --- a/src/engines/srpic.hpp +++ /dev/null @@ -1,1569 +0,0 @@ -/** - * @file engines/srpic.hpp - * @brief Simulation engien class which specialized on SRPIC - * @implements - * - ntt::SRPICEngine<> : ntt::Engine<> - * @cpp: - * - srpic.cpp - * @namespaces: - * - ntt:: - * @macros: - */ - -#ifndef ENGINES_SRPIC_SRPIC_H -#define ENGINES_SRPIC_SRPIC_H - -#include "enums.h" -#include "global.h" - -#include "arch/kokkos_aliases.h" -#include "arch/traits.h" -#include "utils/log.h" -#include "utils/numeric.h" -#include "utils/timer.h" -#include "utils/toml.h" - -#include "archetypes/energy_dist.h" -#include "archetypes/particle_injector.h" -#include "archetypes/spatial_dist.h" -#include "framework/domain/domain.h" -#include "framework/parameters.h" - -#include "engines/engine.hpp" -#include "kernels/ampere_mink.hpp" -#include "kernels/ampere_sr.hpp" -#include "kernels/currents_deposit.hpp" -#include "kernels/digital_filter.hpp" -#include "kernels/faraday_mink.hpp" -#include "kernels/faraday_sr.hpp" -#include "kernels/fields_bcs.hpp" -#include "kernels/particle_moments.hpp" -#include "kernels/particle_pusher_sr.hpp" -#include "pgen.hpp" - -#include -#include - -#include - -namespace ntt { - - template - class SRPICEngine : public Engine { - - using base_t = Engine; - using pgen_t = user::PGen; - using domain_t = Domain; - // constexprs - using base_t::pgen_is_ok; - // contents - using base_t::m_metadomain; - using base_t::m_params; - using base_t::m_pgen; - // methods - using base_t::init; - // variables - using base_t::dt; - using base_t::max_steps; - using base_t::runtime; - using base_t::step; - using base_t::time; - - public: - static constexpr auto S { SimEngine::SRPIC }; - - SRPICEngine(const SimulationParams& params) : base_t { params } {} - - ~SRPICEngine() = default; - - void step_forward(timer::Timers& timers, domain_t& dom) override { - const auto fieldsolver_enabled = m_params.template get( - "algorithms.fieldsolver.enable"); - const auto deposit_enabled = m_params.template get( - "algorithms.deposit.enable"); - const auto clear_interval = m_params.template get( - "particles.clear_interval"); - - if (step == 0) { - // communicate fields and apply BCs on the first timestep - m_metadomain.CommunicateFields(dom, Comm::B | Comm::E); - FieldBoundaries(dom, BC::B | BC::E); - ParticleInjector(dom); - } - - if (fieldsolver_enabled) { - timers.start("FieldSolver"); - Faraday(dom, HALF); - timers.stop("FieldSolver"); - - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::B); - timers.stop("Communications"); - - timers.start("FieldBoundaries"); - FieldBoundaries(dom, BC::B); - timers.stop("FieldBoundaries"); - Kokkos::fence(); - } - - { - timers.start("ParticlePusher"); - ParticlePush(dom); - timers.stop("ParticlePusher"); - - if (deposit_enabled) { - timers.start("CurrentDeposit"); - Kokkos::deep_copy(dom.fields.cur, ZERO); - CurrentsDeposit(dom); - timers.stop("CurrentDeposit"); - - timers.start("Communications"); - m_metadomain.SynchronizeFields(dom, Comm::J); - m_metadomain.CommunicateFields(dom, Comm::J); - timers.stop("Communications"); - - timers.start("CurrentFiltering"); - CurrentsFilter(dom); - timers.stop("CurrentFiltering"); - } - - timers.start("Communications"); - m_metadomain.CommunicateParticles(dom); - timers.stop("Communications"); - } - - if (fieldsolver_enabled) { - timers.start("FieldSolver"); - Faraday(dom, HALF); - timers.stop("FieldSolver"); - - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::B); - timers.stop("Communications"); - - timers.start("FieldBoundaries"); - FieldBoundaries(dom, BC::B); - timers.stop("FieldBoundaries"); - - timers.start("FieldSolver"); - Ampere(dom, ONE); - timers.stop("FieldSolver"); - - if (deposit_enabled) { - timers.start("FieldSolver"); - CurrentsAmpere(dom); - timers.stop("FieldSolver"); - } - - timers.start("Communications"); - m_metadomain.CommunicateFields(dom, Comm::E | Comm::J); - timers.stop("Communications"); - - timers.start("FieldBoundaries"); - FieldBoundaries(dom, BC::E); - timers.stop("FieldBoundaries"); - } - - { - timers.start("Injector"); - ParticleInjector(dom); - timers.stop("Injector"); - } - - if (clear_interval > 0 and step % clear_interval == 0 and step > 0) { - timers.start("PrtlClear"); - m_metadomain.RemoveDeadParticles(dom); - timers.stop("PrtlClear"); - } - } - - /* algorithm substeps --------------------------------------------------- */ - void Faraday(domain_t& domain, real_t fraction = ONE) { - logger::Checkpoint("Launching Faraday kernel", HERE); - const auto dT = fraction * - m_params.template get( - "algorithms.timestep.correction") * - dt; - if constexpr (M::CoordType == Coord::Cart) { - // minkowski case - const auto dx = math::sqrt(domain.mesh.metric.template h_<1, 1>({})); - const auto deltax = m_params.template get( - "algorithms.fieldsolver.delta_x"); - const auto deltay = m_params.template get( - "algorithms.fieldsolver.delta_y"); - const auto betaxy = m_params.template get( - "algorithms.fieldsolver.beta_xy"); - const auto betayx = m_params.template get( - "algorithms.fieldsolver.beta_yx"); - const auto deltaz = m_params.template get( - "algorithms.fieldsolver.delta_z"); - const auto betaxz = m_params.template get( - "algorithms.fieldsolver.beta_xz"); - const auto betazx = m_params.template get( - "algorithms.fieldsolver.beta_zx"); - const auto betayz = m_params.template get( - "algorithms.fieldsolver.beta_yz"); - const auto betazy = m_params.template get( - "algorithms.fieldsolver.beta_zy"); - real_t coeff1, coeff2; - if constexpr (M::Dim == Dim::_2D) { - coeff1 = dT / SQR(dx); - coeff2 = dT; - } else { - coeff1 = dT / dx; - coeff2 = ZERO; - } - Kokkos::parallel_for("Faraday", - domain.mesh.rangeActiveCells(), - kernel::mink::Faraday_kernel(domain.fields.em, - coeff1, - coeff2, - deltax, - deltay, - betaxy, - betayx, - deltaz, - betaxz, - betazx, - betayz, - betazy)); - } else { - Kokkos::parallel_for("Faraday", - domain.mesh.rangeActiveCells(), - kernel::sr::Faraday_kernel(domain.fields.em, - domain.mesh.metric, - dT, - domain.mesh.flds_bc())); - } - } - - void Ampere(domain_t& domain, real_t fraction = ONE) { - logger::Checkpoint("Launching Ampere kernel", HERE); - const auto dT = fraction * - m_params.template get( - "algorithms.timestep.correction") * - dt; - auto range = range_with_axis_BCs(domain); - if constexpr (M::CoordType == Coord::Cart) { - // minkowski case - const auto dx = math::sqrt(domain.mesh.metric.template h_<1, 1>({})); - real_t coeff1, coeff2; - if constexpr (M::Dim == Dim::_2D) { - coeff1 = dT / SQR(dx); - coeff2 = dT; - } else { - coeff1 = dT / dx; - coeff2 = ZERO; - } - - Kokkos::parallel_for( - "Ampere", - range, - kernel::mink::Ampere_kernel(domain.fields.em, coeff1, coeff2)); - } else { - const auto ni2 = domain.mesh.n_active(in::x2); - Kokkos::parallel_for("Ampere", - range, - kernel::sr::Ampere_kernel(domain.fields.em, - domain.mesh.metric, - dT, - ni2, - domain.mesh.flds_bc())); - } - } - - void ParticlePush(domain_t& domain) { - real_t gx1 { ZERO }, gx2 { ZERO }, gx3 { ZERO }, ds { ZERO }; - real_t x_surf { ZERO }; - bool has_atmosphere = false; - for (auto& direction : dir::Directions::orth) { - if (m_metadomain.mesh().prtl_bc_in(direction) == PrtlBC::ATMOSPHERE) { - raise::ErrorIf(has_atmosphere, - "Only one direction is allowed to have atm boundaries", - HERE); - has_atmosphere = true; - const auto g = m_params.template get( - "grid.boundaries.atmosphere.g"); - ds = m_params.template get("grid.boundaries.atmosphere.ds"); - const auto [sign, dim, xg_min, xg_max] = get_atm_extent(direction); - if (dim == in::x1) { - gx1 = sign > 0 ? g : -g; - gx2 = ZERO; - gx3 = ZERO; - } else if (dim == in::x2) { - gx1 = ZERO; - gx2 = sign > 0 ? g : -g; - gx3 = ZERO; - } else if (dim == in::x3) { - gx1 = ZERO; - gx2 = ZERO; - gx3 = sign > 0 ? g : -g; - } else { - raise::Error("Invalid dimension", HERE); - } - if (sign > 0) { - x_surf = xg_min; - } else { - x_surf = xg_max; - } - } - } - for (auto& species : domain.species) { - if ((species.pusher() == PrtlPusher::NONE) or (species.npart() == 0)) { - continue; - } - species.set_unsorted(); - logger::Checkpoint( - fmt::format("Launching particle pusher kernel for %d [%s] : %lu", - species.index(), - species.label().c_str(), - species.npart()), - HERE); - const auto q_ovr_m = species.mass() > ZERO - ? species.charge() / species.mass() - : ZERO; - // coeff = q / m (dt / 2) omegaB0 - const auto coeff = q_ovr_m * HALF * dt * - m_params.template get("scales.omegaB0"); - PrtlPusher::type pusher; - if (species.pusher() == PrtlPusher::PHOTON) { - pusher = PrtlPusher::PHOTON; - } else if (species.pusher() == PrtlPusher::BORIS) { - pusher = PrtlPusher::BORIS; - } else if (species.pusher() == PrtlPusher::VAY) { - pusher = PrtlPusher::VAY; - } else { - raise::Fatal("Invalid particle pusher", HERE); - } - const auto cooling = species.cooling(); - - // coefficients to be forwarded to the dispatcher - // gca - const auto has_gca = species.use_gca(); - const auto gca_larmor_max = has_gca ? m_params.template get( - "algorithms.gca.larmor_max") - : ZERO; - const auto gca_eovrb_max = has_gca ? m_params.template get( - "algorithms.gca.e_ovr_b_max") - : ZERO; - // cooling - const auto has_synchrotron = (cooling == Cooling::SYNCHROTRON); - const auto has_compton = (cooling == Cooling::COMPTON); - const auto sync_grad = has_synchrotron - ? m_params.template get( - "algorithms.synchrotron.gamma_rad") - : ZERO; - const auto sync_coeff = has_synchrotron - ? (real_t)(0.1) * dt * - m_params.template get( - "scales.omegaB0") / - (SQR(sync_grad) * species.mass()) - : ZERO; - const auto comp_grad = has_compton ? m_params.template get( - "algorithms.compton.gamma_rad") - : ZERO; - const auto comp_coeff = has_compton ? (real_t)(0.1) * dt * - m_params.template get( - "scales.omegaB0") / - (SQR(comp_grad) * species.mass()) - : ZERO; - // toggle to indicate whether pgen defines the external force - bool has_extforce = false; - if constexpr (traits::has_member::value) { - has_extforce = true; - // toggle to indicate whether the ext force applies to current species - if (traits::has_member::value) { - has_extforce &= std::find(m_pgen.ext_force.species.begin(), - m_pgen.ext_force.species.end(), - species.index()) != - m_pgen.ext_force.species.end(); - } - } - - kernel::sr::CoolingTags cooling_tags = 0; - if (cooling == Cooling::SYNCHROTRON) { - cooling_tags = kernel::sr::Cooling::Synchrotron; - } - if (cooling == Cooling::COMPTON) { - cooling_tags = kernel::sr::Cooling::Compton; - } - // clang-format off - if (not has_atmosphere and not has_extforce) { - Kokkos::parallel_for( - "ParticlePusher", - species.rangeActiveParticles(), - kernel::sr::Pusher_kernel( - pusher, has_gca, false, - cooling_tags, - domain.fields.em, - species.index(), - species.i1, species.i2, species.i3, - species.i1_prev, species.i2_prev, species.i3_prev, - species.dx1, species.dx2, species.dx3, - species.dx1_prev, species.dx2_prev, species.dx3_prev, - species.ux1, species.ux2, species.ux3, - species.phi, species.tag, - domain.mesh.metric, - time, coeff, dt, - domain.mesh.n_active(in::x1), - domain.mesh.n_active(in::x2), - domain.mesh.n_active(in::x3), - domain.mesh.prtl_bc(), - gca_larmor_max, gca_eovrb_max, sync_coeff, comp_coeff - )); - } else if (has_atmosphere and not has_extforce) { - const auto force = - kernel::sr::Force { - {gx1, gx2, gx3}, - x_surf, - ds - }; - Kokkos::parallel_for( - "ParticlePusher", - species.rangeActiveParticles(), - kernel::sr::Pusher_kernel( - pusher, has_gca, false, - cooling_tags, - domain.fields.em, - species.index(), - species.i1, species.i2, species.i3, - species.i1_prev, species.i2_prev, species.i3_prev, - species.dx1, species.dx2, species.dx3, - species.dx1_prev, species.dx2_prev, species.dx3_prev, - species.ux1, species.ux2, species.ux3, - species.phi, species.tag, - domain.mesh.metric, - force, - time, coeff, dt, - domain.mesh.n_active(in::x1), - domain.mesh.n_active(in::x2), - domain.mesh.n_active(in::x3), - domain.mesh.prtl_bc(), - gca_larmor_max, gca_eovrb_max, sync_coeff, comp_coeff - )); - } else if (not has_atmosphere and has_extforce) { - if constexpr (traits::has_member::value) { - const auto force = - kernel::sr::Force { - m_pgen.ext_force - }; - Kokkos::parallel_for( - "ParticlePusher", - species.rangeActiveParticles(), - kernel::sr::Pusher_kernel( - pusher, has_gca, true, - cooling_tags, - domain.fields.em, - species.index(), - species.i1, species.i2, species.i3, - species.i1_prev, species.i2_prev, species.i3_prev, - species.dx1, species.dx2, species.dx3, - species.dx1_prev, species.dx2_prev, species.dx3_prev, - species.ux1, species.ux2, species.ux3, - species.phi, species.tag, - domain.mesh.metric, - force, - time, coeff, dt, - domain.mesh.n_active(in::x1), - domain.mesh.n_active(in::x2), - domain.mesh.n_active(in::x3), - domain.mesh.prtl_bc(), - gca_larmor_max, gca_eovrb_max, sync_coeff, comp_coeff - )); - } else { - raise::Error("External force not implemented", HERE); - } - } else { // has_atmosphere and has_extforce - if constexpr (traits::has_member::value) { - const auto force = - kernel::sr::Force { - m_pgen.ext_force, {gx1, gx2, gx3}, x_surf, ds - }; - Kokkos::parallel_for( - "ParticlePusher", - species.rangeActiveParticles(), - kernel::sr::Pusher_kernel( - pusher, has_gca, true, - cooling_tags, - domain.fields.em, - species.index(), - species.i1, species.i2, species.i3, - species.i1_prev, species.i2_prev, species.i3_prev, - species.dx1, species.dx2, species.dx3, - species.dx1_prev, species.dx2_prev, species.dx3_prev, - species.ux1, species.ux2, species.ux3, - species.phi, species.tag, - domain.mesh.metric, - force, - time, coeff, dt, - domain.mesh.n_active(in::x1), - domain.mesh.n_active(in::x2), - domain.mesh.n_active(in::x3), - domain.mesh.prtl_bc(), - gca_larmor_max, gca_eovrb_max, sync_coeff, comp_coeff - )); - } else { - raise::Error("External force not implemented", HERE); - } - } - // clang-format on - } - } - - void ParticleInjector(domain_t& domain, InjTags tags = Inj::None) { - for (auto& direction : dir::Directions::orth) { - if (m_metadomain.mesh().prtl_bc_in(direction) == PrtlBC::ATMOSPHERE) { - AtmosphereParticlesIn(direction, domain, tags); - } - } - } - - template - void deposit_with(const Particles& species, - const M& metric, - const scatter_ndfield_t& scatter_cur, - real_t dt) { - // clang-format off - Kokkos::parallel_for("CurrentsDeposit", - species.rangeActiveParticles(), - kernel::DepositCurrents_kernel( - scatter_cur, - species.i1, species.i2, species.i3, - species.i1_prev, species.i2_prev, species.i3_prev, - species.dx1, species.dx2, species.dx3, - species.dx1_prev, species.dx2_prev, species.dx3_prev, - species.ux1, species.ux2, species.ux3, - species.phi, species.weight, species.tag, - metric, (real_t)(species.charge()), dt)); - // clang-format on - } - - void CurrentsDeposit(domain_t& domain) { - auto scatter_cur = Kokkos::Experimental::create_scatter_view( - domain.fields.cur); - auto shape_order = m_params.template get("algorithms.deposit.order"); - for (auto& species : domain.species) { - if ((species.pusher() == PrtlPusher::NONE) or (species.npart() == 0) or - cmp::AlmostZero_host(species.charge())) { - continue; - } - logger::Checkpoint( - fmt::format("Launching currents deposit kernel for %d [%s] : %lu %f", - species.index(), - species.label().c_str(), - species.npart(), - (double)species.charge()), - HERE); - - deposit_with(species, domain.mesh.metric, scatter_cur, dt); - } - Kokkos::Experimental::contribute(domain.fields.cur, scatter_cur); - } - - void CurrentsAmpere(domain_t& domain) { - logger::Checkpoint("Launching Ampere kernel for adding currents", HERE); - const auto q0 = m_params.template get("scales.q0"); - const auto n0 = m_params.template get("scales.n0"); - const auto B0 = m_params.template get("scales.B0"); - if constexpr (M::CoordType == Coord::Cart) { - // minkowski case - const auto V0 = m_params.template get("scales.V0"); - const auto ppc0 = m_params.template get("particles.ppc0"); - const auto coeff = -dt * q0 / (B0 * V0); - if constexpr ( - traits::has_member::value) { - const std::vector xmin { domain.mesh.extent(in::x1).first, - domain.mesh.extent(in::x2).first, - domain.mesh.extent(in::x3).first }; - const auto ext_current = m_pgen.ext_current; - const auto dx = domain.mesh.metric.template sqrt_h_<1, 1>({}); - // clang-format off - Kokkos::parallel_for( - "Ampere", - domain.mesh.rangeActiveCells(), - kernel::mink::CurrentsAmpere_kernel( - domain.fields.em, domain.fields.cur, - coeff, ppc0, ext_current, xmin, dx)); - // clang-format on - } else { - Kokkos::parallel_for( - "Ampere", - domain.mesh.rangeActiveCells(), - kernel::mink::CurrentsAmpere_kernel(domain.fields.em, - domain.fields.cur, - coeff, - ppc0)); - } - } else { - // non-minkowski - const auto coeff = -dt * q0 * n0 / B0; - auto range = range_with_axis_BCs(domain); - const auto ni2 = domain.mesh.n_active(in::x2); - Kokkos::parallel_for( - "Ampere", - range, - kernel::sr::CurrentsAmpere_kernel(domain.fields.em, - domain.fields.cur, - domain.mesh.metric, - coeff, - ONE / n0, - ni2, - domain.mesh.flds_bc())); - } - } - - void CurrentsFilter(domain_t& domain) { - logger::Checkpoint("Launching currents filtering kernels", HERE); - auto range = range_with_axis_BCs(domain); - const auto nfilter = m_params.template get( - "algorithms.current_filters"); - tuple_t size; - if constexpr (M::Dim == Dim::_1D || M::Dim == Dim::_2D || M::Dim == Dim::_3D) { - size[0] = domain.mesh.n_active(in::x1); - } - if constexpr (M::Dim == Dim::_2D || M::Dim == Dim::_3D) { - size[1] = domain.mesh.n_active(in::x2); - } - if constexpr (M::Dim == Dim::_3D) { - size[2] = domain.mesh.n_active(in::x3); - } - // !TODO: this needs to be done more efficiently - for (auto i { 0u }; i < nfilter; ++i) { - Kokkos::deep_copy(domain.fields.buff, domain.fields.cur); - Kokkos::parallel_for("CurrentsFilter", - range, - kernel::DigitalFilter_kernel( - domain.fields.cur, - domain.fields.buff, - size, - domain.mesh.flds_bc())); - m_metadomain.CommunicateFields(domain, Comm::J); - } - } - - void FieldBoundaries(domain_t& domain, BCTags tags) { - for (auto& direction : dir::Directions::orth) { - if (m_metadomain.mesh().flds_bc_in(direction) == FldsBC::MATCH) { - MatchFieldsIn(direction, domain, tags); - } else if (m_metadomain.mesh().flds_bc_in(direction) == FldsBC::AXIS) { - if (domain.mesh.flds_bc_in(direction) == FldsBC::AXIS) { - AxisFieldsIn(direction, domain, tags); - } - } else if (m_metadomain.mesh().flds_bc_in(direction) == FldsBC::ATMOSPHERE) { - AtmosphereFieldsIn(direction, domain, tags); - } else if (m_metadomain.mesh().flds_bc_in(direction) == FldsBC::FIXED) { - if (domain.mesh.flds_bc_in(direction) == FldsBC::FIXED) { - FixedFieldsIn(direction, domain, tags); - } - } else if (m_metadomain.mesh().flds_bc_in(direction) == FldsBC::CONDUCTOR) { - if (domain.mesh.flds_bc_in(direction) == FldsBC::CONDUCTOR) { - PerfectConductorFieldsIn(direction, domain, tags); - } - } else if (m_metadomain.mesh().flds_bc_in(direction) == FldsBC::CUSTOM) { - if (domain.mesh.flds_bc_in(direction) == FldsBC::CUSTOM) { - CustomFieldsIn(direction, domain, tags); - } - } else if (m_metadomain.mesh().flds_bc_in(direction) == FldsBC::HORIZON) { - raise::Error("HORIZON BCs only applicable for GR", HERE); - } - } // loop over directions - } - - void MatchFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags) { - /** - * matching boundaries - */ - const auto ds_array = m_params.template get>( - "grid.boundaries.match.ds"); - const auto dim = direction.get_dim(); - real_t xg_min, xg_max, xg_edge; - auto sign = direction.get_sign(); - real_t ds; - if (sign > 0) { // + direction - ds = ds_array[(short)dim].second; - xg_max = m_metadomain.mesh().extent(dim).second; - xg_min = xg_max - ds; - xg_edge = xg_max; - } else { // - direction - ds = ds_array[(short)dim].first; - xg_min = m_metadomain.mesh().extent(dim).first; - xg_max = xg_min + ds; - xg_edge = xg_min; - } - boundaries_t box; - boundaries_t incl_ghosts; - for (dim_t d { 0 }; d < M::Dim; ++d) { - if (d == static_cast(dim)) { - box.push_back({ xg_min, xg_max }); - if (sign > 0) { - incl_ghosts.push_back({ false, true }); - } else { - incl_ghosts.push_back({ true, false }); - } - } else { - box.push_back(Range::All); - incl_ghosts.push_back({ true, true }); - } - } - if (not domain.mesh.Intersects(box)) { - return; - } - const auto intersect_range = domain.mesh.ExtentToRange(box, incl_ghosts); - tuple_t range_min { 0 }; - tuple_t range_max { 0 }; - - for (auto d { 0u }; d < M::Dim; ++d) { - range_min[d] = intersect_range[d].first; - range_max[d] = intersect_range[d].second; - } - - if (dim == in::x1) { - if constexpr ( - traits::has_member::value) { - auto match_fields = m_pgen.MatchFields(time); - call_match_fields(domain.fields.em, - domain.mesh.flds_bc(), - match_fields, - domain.mesh.metric, - xg_edge, - ds, - tags, - range_min, - range_max); - } else if constexpr ( - traits::has_member::value) { - auto match_fields = m_pgen.MatchFieldsInX1(time); - call_match_fields(domain.fields.em, - domain.mesh.flds_bc(), - match_fields, - domain.mesh.metric, - xg_edge, - ds, - tags, - range_min, - range_max); - } - } else if (dim == in::x2) { - if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { - if constexpr ( - traits::has_member::value) { - auto match_fields = m_pgen.MatchFields(time); - call_match_fields(domain.fields.em, - domain.mesh.flds_bc(), - match_fields, - domain.mesh.metric, - xg_edge, - ds, - tags, - range_min, - range_max); - } else if constexpr ( - traits::has_member::value) { - auto match_fields = m_pgen.MatchFieldsInX2(time); - call_match_fields(domain.fields.em, - domain.mesh.flds_bc(), - match_fields, - domain.mesh.metric, - xg_edge, - ds, - tags, - range_min, - range_max); - } - } else { - raise::Error("Invalid dimension", HERE); - } - } else if (dim == in::x3) { - if constexpr (M::Dim == Dim::_3D) { - if constexpr ( - traits::has_member::value) { - auto match_fields = m_pgen.MatchFields(time); - call_match_fields(domain.fields.em, - domain.mesh.flds_bc(), - match_fields, - domain.mesh.metric, - xg_edge, - ds, - tags, - range_min, - range_max); - } else if constexpr ( - traits::has_member::value) { - auto match_fields = m_pgen.MatchFieldsInX3(time); - call_match_fields(domain.fields.em, - domain.mesh.flds_bc(), - match_fields, - domain.mesh.metric, - xg_edge, - ds, - tags, - range_min, - range_max); - } - } - } else { - raise::Error("Invalid dimension", HERE); - } - } - - void AxisFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags) { - /** - * axis boundaries - */ - if constexpr (M::CoordType != Coord::Cart) { - raise::ErrorIf(direction.get_dim() != in::x2, - "Invalid axis direction, should be x2", - HERE); - const auto i2_min = domain.mesh.i_min(in::x2); - const auto i2_max = domain.mesh.i_max(in::x2); - if (direction.get_sign() < 0) { - Kokkos::parallel_for( - "AxisBCFields", - domain.mesh.n_all(in::x1), - kernel::bc::AxisBoundaries_kernel(domain.fields.em, - i2_min, - tags)); - } else { - Kokkos::parallel_for( - "AxisBCFields", - domain.mesh.n_all(in::x1), - kernel::bc::AxisBoundaries_kernel(domain.fields.em, - i2_max, - tags)); - } - } else { - (void)direction; - (void)domain; - (void)tags; - raise::Error("Invalid coordinate type for axis BCs", HERE); - } - } - - void FixedFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags) { - /** - * fixed field boundaries - */ - const auto sign = direction.get_sign(); - const auto dim = direction.get_dim(); - raise::ErrorIf(dim != in::x1 and M::CoordType != Coord::Cart, - "Fixed BCs only implemented for x1 in " - "non-cartesian coordinates", - HERE); - em normal_b_comp, tang_e_comp1, tang_e_comp2; - if (dim == in::x1) { - normal_b_comp = em::bx1; - tang_e_comp1 = em::ex2; - tang_e_comp2 = em::ex3; - } else if (dim == in::x2) { - normal_b_comp = em::bx2; - tang_e_comp1 = em::ex1; - tang_e_comp2 = em::ex3; - } else if (dim == in::x3) { - normal_b_comp = em::bx3; - tang_e_comp1 = em::ex1; - tang_e_comp2 = em::ex2; - } else { - raise::Error("Invalid dimension", HERE); - } - std::vector xi_min, xi_max; - const std::vector all_dirs { in::x1, in::x2, in::x3 }; - for (dim_t d { 0u }; d < M::Dim; ++d) { - const auto dd = all_dirs[d]; - if (dim == dd) { - if (sign > 0) { // + direction - xi_min.push_back(domain.mesh.n_all(dd) - N_GHOSTS); - xi_max.push_back(domain.mesh.n_all(dd)); - } else { // - direction - xi_min.push_back(0); - xi_max.push_back(N_GHOSTS); - } - } else { - xi_min.push_back(0); - xi_max.push_back(domain.mesh.n_all(dd)); - } - } - raise::ErrorIf(xi_min.size() != xi_max.size() or - xi_min.size() != static_cast(M::Dim), - "Invalid range size", - HERE); - std::vector comps; - if (tags & BC::E) { - comps.push_back(tang_e_comp1); - comps.push_back(tang_e_comp2); - } - if (tags & BC::B) { - comps.push_back(normal_b_comp); - } - if constexpr (traits::has_member::value) { - raise::Error("Non-const fixed fields not implemented", HERE); - } else if constexpr ( - traits::has_member::value) { - for (const auto& comp : comps) { - auto value = ZERO; - bool shouldset = false; - if constexpr ( - traits::has_member::value) { - // if fix field function present, read from it - const auto newset = m_pgen.FixFieldsConst( - (bc_in)(sign * ((short)dim + 1)), - (em)comp); - value = newset.first; - shouldset = newset.second; - } - if (shouldset) { - if constexpr (M::Dim == Dim::_1D) { - Kokkos::deep_copy( - Kokkos::subview(domain.fields.em, - std::make_pair(xi_min[0], xi_max[0]), - comp), - value); - } else if constexpr (M::Dim == Dim::_2D) { - Kokkos::deep_copy( - Kokkos::subview(domain.fields.em, - std::make_pair(xi_min[0], xi_max[0]), - std::make_pair(xi_min[1], xi_max[1]), - comp), - value); - } else if constexpr (M::Dim == Dim::_3D) { - Kokkos::deep_copy( - Kokkos::subview(domain.fields.em, - std::make_pair(xi_min[0], xi_max[0]), - std::make_pair(xi_min[1], xi_max[1]), - std::make_pair(xi_min[2], xi_max[2]), - comp), - value); - } else { - raise::Error("Invalid dimension", HERE); - } - } - } - } else { - (void)direction; - (void)domain; - (void)tags; - raise::Error("Fixed fields not present (both const and non-const)", HERE); - } - } - - void PerfectConductorFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags) { - /** - * perfect conductor field boundaries - */ - if constexpr (M::CoordType != Coord::Cart) { - (void)direction; - (void)domain; - (void)tags; - raise::Error( - "Perfect conductor BCs only applicable to cartesian coordinates", - HERE); - } else { - const auto sign = direction.get_sign(); - const auto dim = direction.get_dim(); - - std::vector xi_min, xi_max; - - const std::vector all_dirs { in::x1, in::x2, in::x3 }; - - for (auto d { 0u }; d < M::Dim; ++d) { - const auto dd = all_dirs[d]; - if (dim == dd) { - xi_min.push_back(0); - xi_max.push_back((sign < 0) ? (N_GHOSTS + 1) : N_GHOSTS); - } else { - xi_min.push_back(0); - xi_max.push_back(domain.mesh.n_all(dd)); - } - } - raise::ErrorIf(xi_min.size() != xi_max.size() or - xi_min.size() != static_cast(M::Dim), - "Invalid range size", - HERE); - - range_t range; - if constexpr (M::Dim == Dim::_1D) { - range = CreateRangePolicy({ xi_min[0] }, { xi_max[0] }); - } else if constexpr (M::Dim == Dim::_2D) { - range = CreateRangePolicy({ xi_min[0], xi_min[1] }, - { xi_max[0], xi_max[1] }); - } else if constexpr (M::Dim == Dim::_3D) { - range = CreateRangePolicy({ xi_min[0], xi_min[1], xi_min[2] }, - { xi_max[0], xi_max[1], xi_max[2] }); - } else { - raise::Error("Invalid dimension", HERE); - } - std::size_t i_edge; - if (sign > 0) { - i_edge = domain.mesh.i_max(dim); - } else { - i_edge = domain.mesh.i_min(dim); - } - - if (dim == in::x1) { - if (sign > 0) { - Kokkos::parallel_for( - "ConductorFields", - range, - kernel::bc::ConductorBoundaries_kernel( - domain.fields.em, - i_edge, - tags)); - } else { - Kokkos::parallel_for( - "ConductorFields", - range, - kernel::bc::ConductorBoundaries_kernel( - domain.fields.em, - i_edge, - tags)); - } - } else if (dim == in::x2) { - if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { - if (sign > 0) { - Kokkos::parallel_for( - "ConductorFields", - range, - kernel::bc::ConductorBoundaries_kernel( - domain.fields.em, - i_edge, - tags)); - } else { - Kokkos::parallel_for( - "ConductorFields", - range, - kernel::bc::ConductorBoundaries_kernel( - domain.fields.em, - i_edge, - tags)); - } - } else { - raise::Error("Invalid dimension", HERE); - } - } else { - if constexpr (M::Dim == Dim::_3D) { - if (sign > 0) { - Kokkos::parallel_for( - "ConductorFields", - range, - kernel::bc::ConductorBoundaries_kernel( - domain.fields.em, - i_edge, - tags)); - } else { - Kokkos::parallel_for( - "ConductorFields", - range, - kernel::bc::ConductorBoundaries_kernel( - domain.fields.em, - i_edge, - tags)); - } - } else { - raise::Error("Invalid dimension", HERE); - } - } - } - } - - void AtmosphereFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags) { - /** - * atmosphere field boundaries - */ - if constexpr (traits::has_member::value) { - const auto [sign, dim, xg_min, xg_max] = get_atm_extent(direction); - const auto dd = static_cast(dim); - boundaries_t box; - boundaries_t incl_ghosts; - for (auto d { 0u }; d < M::Dim; ++d) { - if (d == dd) { - box.push_back({ xg_min, xg_max }); - if (sign > 0) { - incl_ghosts.push_back({ false, true }); - } else { - incl_ghosts.push_back({ true, false }); - } - } else { - box.push_back(Range::All); - incl_ghosts.push_back({ true, true }); - } - } - if (not domain.mesh.Intersects(box)) { - return; - } - const auto intersect_range = domain.mesh.ExtentToRange(box, incl_ghosts); - tuple_t range_min { 0 }; - tuple_t range_max { 0 }; - - for (auto d { 0u }; d < M::Dim; ++d) { - range_min[d] = intersect_range[d].first; - range_max[d] = intersect_range[d].second; - } - auto atm_fields = m_pgen.AtmFields(time); - std::size_t il_edge; - if (sign > 0) { - il_edge = range_min[dd] - N_GHOSTS; - } else { - il_edge = range_max[dd] - 1 - N_GHOSTS; - } - const auto range = CreateRangePolicy(range_min, range_max); - if (dim == in::x1) { - if (sign > 0) { - Kokkos::parallel_for( - "AtmosphereBCFields", - range, - kernel::bc::EnforcedBoundaries_kernel( - domain.fields.em, - atm_fields, - domain.mesh.metric, - il_edge, - tags)); - } else { - Kokkos::parallel_for( - "AtmosphereBCFields", - range, - kernel::bc::EnforcedBoundaries_kernel( - domain.fields.em, - atm_fields, - domain.mesh.metric, - il_edge, - tags)); - } - } else if (dim == in::x2) { - if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { - if (sign > 0) { - Kokkos::parallel_for( - "AtmosphereBCFields", - range, - kernel::bc::EnforcedBoundaries_kernel( - domain.fields.em, - atm_fields, - domain.mesh.metric, - il_edge, - tags)); - } else { - Kokkos::parallel_for( - "AtmosphereBCFields", - range, - kernel::bc::EnforcedBoundaries_kernel( - domain.fields.em, - atm_fields, - domain.mesh.metric, - il_edge, - tags)); - } - } else { - raise::Error("Invalid dimension", HERE); - } - } else if (dim == in::x3) { - if constexpr (M::Dim == Dim::_3D) { - if (sign > 0) { - Kokkos::parallel_for( - "AtmosphereBCFields", - range, - kernel::bc::EnforcedBoundaries_kernel( - domain.fields.em, - atm_fields, - domain.mesh.metric, - il_edge, - tags)); - } else { - Kokkos::parallel_for( - "AtmosphereBCFields", - range, - kernel::bc::EnforcedBoundaries_kernel( - domain.fields.em, - atm_fields, - domain.mesh.metric, - il_edge, - tags)); - } - } else { - raise::Error("Invalid dimension", HERE); - } - } else { - raise::Error("Invalid dimension", HERE); - } - } else { - (void)direction; - (void)domain; - (void)tags; - raise::Error("Atm fields not implemented in PGEN for atmosphere BCs", HERE); - } - } - - void CustomFieldsIn(dir::direction_t direction, - domain_t& domain, - BCTags tags) { - (void)direction; - (void)domain; - (void)tags; - raise::Error("Custom boundaries not implemented", HERE); - // if constexpr ( - // traits::has_member::value) { - // const auto [box, custom_fields] = m_pgen.CustomFields(time); - // if (domain.mesh.Intersects(box)) { - // } - // - // } else { - // raise::Error("Custom boundaries not implemented", HERE); - // } - } - - void AtmosphereParticlesIn(const dir::direction_t& direction, - domain_t& domain, - InjTags tags) { - const auto [sign, dim, xg_min, xg_max] = get_atm_extent(direction); - - const auto x_surf = sign > 0 ? xg_min : xg_max; - const auto ds = m_params.template get( - "grid.boundaries.atmosphere.ds"); - const auto temp = m_params.template get( - "grid.boundaries.atmosphere.temperature"); - const auto height = m_params.template get( - "grid.boundaries.atmosphere.height"); - const auto species = m_params.template get>( - "grid.boundaries.atmosphere.species"); - const auto nmax = m_params.template get( - "grid.boundaries.atmosphere.density"); - - Kokkos::deep_copy(domain.fields.bckp, ZERO); - auto scatter_bckp = Kokkos::Experimental::create_scatter_view( - domain.fields.bckp); - const auto use_weights = M::CoordType != Coord::Cart; - const auto ni2 = domain.mesh.n_active(in::x2); - const auto inv_n0 = ONE / m_params.template get("scales.n0"); - - // compute the density of the two species - if (tags & Inj::AssumeEmpty) { - if constexpr (M::Dim == Dim::_1D) { - Kokkos::deep_copy( - Kokkos::subview(domain.fields.bckp, Kokkos::ALL, std::make_pair(0, 1)), - ZERO); - } else if constexpr (M::Dim == Dim::_2D) { - Kokkos::deep_copy(Kokkos::subview(domain.fields.bckp, - Kokkos::ALL, - Kokkos::ALL, - std::make_pair(0, 1)), - ZERO); - } else if constexpr (M::Dim == Dim::_3D) { - Kokkos::deep_copy(Kokkos::subview(domain.fields.bckp, - Kokkos::ALL, - Kokkos::ALL, - Kokkos::ALL, - std::make_pair(0, 1)), - ZERO); - } - } else { - for (const auto& sp : - std::vector { species.first, species.second }) { - auto& prtl_spec = domain.species[sp - 1]; - if (prtl_spec.npart() == 0) { - continue; - } - // clang-format off - Kokkos::parallel_for( - "ComputeMoments", - prtl_spec.rangeActiveParticles(), - kernel::ParticleMoments_kernel( - {}, scatter_bckp, 0, - prtl_spec.i1, prtl_spec.i2, prtl_spec.i3, - prtl_spec.dx1, prtl_spec.dx2, prtl_spec.dx3, - prtl_spec.ux1, prtl_spec.ux2, prtl_spec.ux3, - prtl_spec.phi, prtl_spec.weight, prtl_spec.tag, - prtl_spec.mass(), prtl_spec.charge(), - use_weights, - domain.mesh.metric, domain.mesh.flds_bc(), - ni2, inv_n0, 0)); - // clang-format on - prtl_spec.set_unsorted(); - } - Kokkos::Experimental::contribute(domain.fields.bckp, scatter_bckp); - m_metadomain.SynchronizeFields(domain, Comm::Bckp, { 0, 1 }); - } - - const auto maxwellian = arch::Maxwellian { domain.mesh.metric, - domain.random_pool(), - temp }; - - if (dim == in::x1) { - if (sign > 0) { - auto target_density = - arch::AtmosphereDensityProfile { - nmax, - height, - x_surf, - ds - }; - const auto spatial_dist = arch::Replenish { - domain.mesh.metric, - domain.fields.bckp, - 0, - target_density, - nmax - }; - arch::InjectNonUniform( - m_params, - domain, - { species.first, species.second }, - { maxwellian, maxwellian }, - spatial_dist, - nmax, - use_weights); - } else { - auto target_density = - arch::AtmosphereDensityProfile { - nmax, - height, - x_surf, - ds - }; - const auto spatial_dist = arch::Replenish { - domain.mesh.metric, - domain.fields.bckp, - 0, - target_density, - nmax - }; - arch::InjectNonUniform( - m_params, - domain, - { species.first, species.second }, - { maxwellian, maxwellian }, - spatial_dist, - nmax, - use_weights); - } - } else if (dim == in::x2) { - if (sign > 0) { - auto target_density = - arch::AtmosphereDensityProfile { - nmax, - height, - x_surf, - ds - }; - const auto spatial_dist = arch::Replenish { - domain.mesh.metric, - domain.fields.bckp, - 0, - target_density, - nmax - }; - arch::InjectNonUniform( - m_params, - domain, - { species.first, species.second }, - { maxwellian, maxwellian }, - spatial_dist, - nmax, - use_weights); - } else { - auto target_density = - arch::AtmosphereDensityProfile { - nmax, - height, - x_surf, - ds - }; - const auto spatial_dist = arch::Replenish { - domain.mesh.metric, - domain.fields.bckp, - 0, - target_density, - nmax - }; - arch::InjectNonUniform( - m_params, - domain, - { species.first, species.second }, - { maxwellian, maxwellian }, - spatial_dist, - nmax, - use_weights); - } - } else if (dim == in::x3) { - if (sign > 0) { - auto target_density = - arch::AtmosphereDensityProfile { - nmax, - height, - x_surf, - ds - }; - const auto spatial_dist = arch::Replenish { - domain.mesh.metric, - domain.fields.bckp, - 0, - target_density, - nmax - }; - arch::InjectNonUniform( - m_params, - domain, - { species.first, species.second }, - { maxwellian, maxwellian }, - spatial_dist, - nmax, - use_weights); - } else { - auto target_density = - arch::AtmosphereDensityProfile { - nmax, - height, - x_surf, - ds - }; - const auto spatial_dist = arch::Replenish { - domain.mesh.metric, - domain.fields.bckp, - 0, - target_density, - nmax - }; - arch::InjectNonUniform( - m_params, - domain, - { species.first, species.second }, - { maxwellian, maxwellian }, - spatial_dist, - nmax, - use_weights); - } - } else { - raise::Error("Invalid dimension", HERE); - } - return; - } - - private: - /** - * @brief Get the buffer region of the atmosphere and the direction - * @param direction direction in which the atmosphere is applied - * @return tuple: [sign of the direction, the direction (as in::), the min and max extent - * @note xg_min and xg_max are the extents where the fields are set, not the atmosphere itself - * @note i.e. - * - * fields set particles injected - * ghost zone | | - * v v v - * |....|...........|*******************..... -> x1 - * ^ ^ - * xg_min xg_max - * | | | - * |<-- buffer -->|<-- atmosphere -->| - * - * in this case the function returns { -1, in::x1, xg_min, xg_max } - */ - auto get_atm_extent(dir::direction_t direction) const - -> std::tuple { - const auto sign = direction.get_sign(); - const auto dim = direction.get_dim(); - const auto min_buff = m_params.template get( - "algorithms.current_filters") + - 2; - const auto buffer_ncells = min_buff > 5 ? min_buff : 5; - if (M::CoordType != Coord::Cart and (dim != in::x1 or sign > 0)) { - raise::Error("For non-cartesian coordinates atmosphere BCs is " - "possible only in -x1 (@ rmin)", - HERE); - } - real_t xg_min { ZERO }, xg_max { ZERO }; - ncells_t ig_min, ig_max; - if (sign > 0) { // + direction - ig_min = m_metadomain.mesh().n_active(dim) - buffer_ncells; - ig_max = m_metadomain.mesh().n_active(dim); - } else { // - direction - ig_min = 0; - ig_max = buffer_ncells; - } - - if (dim == in::x1) { - xg_min = m_metadomain.mesh().metric.template convert<1, Crd::Cd, Crd::Ph>( - static_cast(ig_min)); - xg_max = m_metadomain.mesh().metric.template convert<1, Crd::Cd, Crd::Ph>( - static_cast(ig_max)); - } else if (dim == in::x2) { - if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { - xg_min = m_metadomain.mesh().metric.template convert<2, Crd::Cd, Crd::Ph>( - static_cast(ig_min)); - xg_max = m_metadomain.mesh().metric.template convert<2, Crd::Cd, Crd::Ph>( - static_cast(ig_max)); - } else { - raise::Error("Invalid dimension", HERE); - } - } else if (dim == in::x3) { - if constexpr (M::Dim == Dim::_3D) { - xg_min = m_metadomain.mesh().metric.template convert<3, Crd::Cd, Crd::Ph>( - static_cast(ig_min)); - xg_max = m_metadomain.mesh().metric.template convert<3, Crd::Cd, Crd::Ph>( - static_cast(ig_max)); - } else { - raise::Error("Invalid dimension", HERE); - } - } else { - raise::Error("Invalid dimension", HERE); - } - return { sign, dim, xg_min, xg_max }; - } - - auto range_with_axis_BCs(const domain_t& domain) -> range_t { - auto range = domain.mesh.rangeActiveCells(); - if constexpr (M::CoordType != Coord::Cart) { - /** - * @brief taking one extra cell in the x2 direction if AXIS BCs - */ - if constexpr (M::Dim == Dim::_2D) { - if (domain.mesh.flds_bc_in({ 0, +1 }) == FldsBC::AXIS) { - range = CreateRangePolicy( - { domain.mesh.i_min(in::x1), domain.mesh.i_min(in::x2) }, - { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); - } - } else if constexpr (M::Dim == Dim::_3D) { - if (domain.mesh.flds_bc_in({ 0, +1, 0 }) == FldsBC::AXIS) { - range = CreateRangePolicy({ domain.mesh.i_min(in::x1), - domain.mesh.i_min(in::x2), - domain.mesh.i_min(in::x3) }, - { domain.mesh.i_max(in::x1), - domain.mesh.i_max(in::x2) + 1, - domain.mesh.i_max(in::x3) }); - } - } - } - return range; - } - - template - void call_match_fields(ndfield_t& fields, - const boundaries_t& boundaries, - const T& match_fields, - const M& metric, - real_t xg_edge, - real_t ds, - BCTags tags, - tuple_t& range_min, - tuple_t& range_max) { - Kokkos::parallel_for( - "MatchFields", - CreateRangePolicy(range_min, range_max), - kernel::bc::MatchBoundaries_kernel(fields, - match_fields, - metric, - xg_edge, - ds, - tags, - boundaries)); - } - }; - -} // namespace ntt - -#endif // ENGINES_SRPIC_SRPIC_H diff --git a/src/engines/srpic/currents.h b/src/engines/srpic/currents.h new file mode 100644 index 000000000..3afabea8a --- /dev/null +++ b/src/engines/srpic/currents.h @@ -0,0 +1,124 @@ +/** + * @file engines/srpic/currents.h + * @brief Current deposition and filtering routines for the SRPIC engine + * @implements + * - ntt::srpic::CallDepositKernel<> -> void + * - ntt::srpic::CurrentsDeposit<> -> void + * - ntt::srpic::CurrentsFilter<> -> void + * @namespaces: + * - ntt::srpic:: + */ + +#ifndef ENGINES_SRPIC_CURRENTS_H +#define ENGINES_SRPIC_CURRENTS_H + +#include "enums.h" +#include "global.h" + +#include "arch/kokkos_aliases.h" +#include "traits/metric.h" +#include "utils/log.h" +#include "utils/param_container.h" + +#include "engines/srpic/utils.h" +#include "framework/domain/domain.h" +#include "framework/domain/metadomain.h" +#include "kernels/currents_deposit.hpp" +#include "kernels/digital_filter.hpp" + +namespace ntt { + namespace srpic { + + template + void CallDepositKernel(const Particles& species, + const M& local_metric, + const scatter_ndfield_t& scatter_cur, + real_t dt) { + Kokkos::parallel_for("CurrentsDeposit", + species.rangeActiveParticles(), + kernel::DepositCurrents_kernel( + scatter_cur, + species.i1, + species.i2, + species.i3, + species.i1_prev, + species.i2_prev, + species.i3_prev, + species.dx1, + species.dx2, + species.dx3, + species.dx1_prev, + species.dx2_prev, + species.dx3_prev, + species.ux1, + species.ux2, + species.ux3, + species.phi, + species.weight, + species.tag, + local_metric, + (real_t)(species.charge()), + dt)); + } + + template + void CurrentsDeposit(Domain& domain, + const prm::Parameters& engine_params) { + const auto dt = engine_params.get("dt"); + Kokkos::deep_copy(domain.fields.cur, ZERO); + auto scatter_cur = Kokkos::Experimental::create_scatter_view( + domain.fields.cur); + for (auto& species : domain.species) { + if ((species.pusher() == ParticlePusher::NONE) or + (species.npart() == 0) or cmp::AlmostZero_host(species.charge())) { + continue; + } + logger::Checkpoint( + fmt::format("Launching currents deposit kernel for %d [%s] : %lu %f", + species.index(), + species.label().c_str(), + species.npart(), + (double)species.charge()), + HERE); + + CallDepositKernel(species, domain.mesh.metric, scatter_cur, dt); + } + Kokkos::Experimental::contribute(domain.fields.cur, scatter_cur); + } + + template + void CurrentsFilter(Metadomain& metadomain, + Domain& domain, + const SimulationParams& params) { + logger::Checkpoint("Launching currents filtering kernels", HERE); + auto range = srpic::RangeWithAxisBCs(domain); + const auto nfilter = params.template get( + "algorithms.current_filters"); + tuple_t size; + if constexpr (M::Dim == Dim::_1D || M::Dim == Dim::_2D || M::Dim == Dim::_3D) { + size[0] = domain.mesh.n_active(in::x1); + } + if constexpr (M::Dim == Dim::_2D || M::Dim == Dim::_3D) { + size[1] = domain.mesh.n_active(in::x2); + } + if constexpr (M::Dim == Dim::_3D) { + size[2] = domain.mesh.n_active(in::x3); + } + // !TODO: this needs to be done more efficiently + for (auto i { 0u }; i < nfilter; ++i) { + Kokkos::deep_copy(domain.fields.buff, domain.fields.cur); + Kokkos::parallel_for("CurrentsFilter", + range, + kernel::DigitalFilter_kernel( + domain.fields.cur, + domain.fields.buff, + size, + domain.mesh.flds_bc())); + metadomain.CommunicateFields(domain, Comm::J); + } + } + + } // namespace srpic +} // namespace ntt + +#endif // ENGINES_SRPIC_CURRENTS_H diff --git a/src/engines/srpic/fields_bcs.h b/src/engines/srpic/fields_bcs.h new file mode 100644 index 000000000..97c16f866 --- /dev/null +++ b/src/engines/srpic/fields_bcs.h @@ -0,0 +1,672 @@ +/** + * @file engines/srpic/fields_bcs.h + * @brief Field boundary condition routines for the SRPIC engine + * @implements + * - ntt::srpic::CallMatchFields<> -> void + * - ntt::srpic::MatchFieldsIn<> -> void + * - ntt::srpic::AxisFieldsIn<> -> void + * - ntt::srpic::FixedFieldsIn<> -> void + * - ntt::srpic::PerfectConductorFieldsIn<> -> void + * - ntt::srpic::AtmosphereFieldsIn<> -> void + * - ntt::srpic::CustomFieldsIn<> -> void + * - ntt::srpic::FieldBoundaries<> -> void + * @namespaces: + * - ntt::srpic:: + */ + +#ifndef ENGINES_SRPIC_FIELDS_BCS_H +#define ENGINES_SRPIC_FIELDS_BCS_H + +#include "enums.h" +#include "global.h" + +#include "arch/directions.h" +#include "traits/metric.h" +#include "traits/pgen.h" +#include "utils/numeric.h" + +#include "engines/srpic/utils.h" +#include "framework/domain/domain.h" +#include "framework/parameters/parameters.h" +#include "kernels/fields_bcs.hpp" + +#include "engines/engine.hpp" + +namespace ntt { + namespace srpic { + + template + void CallMatchFields(ndfield_t& fields, + const boundaries_t& boundaries, + const T& match_fields, + const M& metric, + real_t xg_edge, + real_t ds, + BCTags tags, + tuple_t& range_min, + tuple_t& range_max) { + Kokkos::parallel_for( + "MatchFields", + CreateRangePolicy(range_min, range_max), + kernel::bc::MatchBoundaries_kernel(fields, + match_fields, + metric, + xg_edge, + ds, + tags, + boundaries)); + } + + template PG> + void MatchFieldsIn(const dir::direction_t& direction, + Domain& domain, + const Grid& global_grid, + const PG& pgen, + const prm::Parameters& engine_params, + const SimulationParams& params, + BCTags tags) { + const auto time = engine_params.get("time"); + /** + * matching boundaries + */ + const auto ds_array = params.template get>( + "grid.boundaries.match.ds"); + const auto dim = direction.get_dim(); + real_t xg_min, xg_max, xg_edge; + auto sign = direction.get_sign(); + real_t ds; + if (sign > 0) { // + direction + ds = ds_array[(short)dim].second; + xg_max = global_grid.extent(dim).second; + xg_min = xg_max - ds; + xg_edge = xg_max; + } else { // - direction + ds = ds_array[(short)dim].first; + xg_min = global_grid.extent(dim).first; + xg_max = xg_min + ds; + xg_edge = xg_min; + } + boundaries_t box; + boundaries_t incl_ghosts; + for (dim_t d { 0 }; d < M::Dim; ++d) { + if (d == static_cast(dim)) { + box.emplace_back(xg_min, xg_max); + if (sign > 0) { + incl_ghosts.emplace_back(false, true); + } else { + incl_ghosts.emplace_back(true, false); + } + } else { + box.push_back(Range::All); + incl_ghosts.emplace_back(true, true); + } + } + if (not domain.mesh.Intersects(box)) { + return; + } + const auto intersect_range = domain.mesh.ExtentToRange(box, incl_ghosts); + tuple_t range_min { 0 }; + tuple_t range_max { 0 }; + + for (auto d { 0u }; d < M::Dim; ++d) { + range_min[d] = intersect_range[d].first; + range_max[d] = intersect_range[d].second; + } + + if (dim == in::x1) { + if constexpr (::traits::pgen::HasMatchFields) { + auto match_fields = pgen.MatchFields(time); + CallMatchFields(domain.fields.em, + domain.mesh.flds_bc(), + match_fields, + domain.mesh.metric, + xg_edge, + ds, + tags, + range_min, + range_max); + } else if constexpr (::traits::pgen::HasMatchFieldsInX1) { + auto match_fields = pgen.MatchFieldsInX1(time); + CallMatchFields(domain.fields.em, + domain.mesh.flds_bc(), + match_fields, + domain.mesh.metric, + xg_edge, + ds, + tags, + range_min, + range_max); + } + } else if (dim == in::x2) { + if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { + if constexpr (::traits::pgen::HasMatchFields) { + auto match_fields = pgen.MatchFields(time); + CallMatchFields( + domain.fields.em, + domain.mesh.flds_bc(), + match_fields, + domain.mesh.metric, + xg_edge, + ds, + tags, + range_min, + range_max); + } else if constexpr (::traits::pgen::HasMatchFieldsInX2) { + auto match_fields = pgen.MatchFieldsInX2(time); + CallMatchFields( + domain.fields.em, + domain.mesh.flds_bc(), + match_fields, + domain.mesh.metric, + xg_edge, + ds, + tags, + range_min, + range_max); + } + } else { + raise::Error("Invalid dimension", HERE); + } + } else if (dim == in::x3) { + if constexpr (M::Dim == Dim::_3D) { + if constexpr (::traits::pgen::HasMatchFields) { + auto match_fields = pgen.MatchFields(time); + CallMatchFields( + domain.fields.em, + domain.mesh.flds_bc(), + match_fields, + domain.mesh.metric, + xg_edge, + ds, + tags, + range_min, + range_max); + } else if constexpr (::traits::pgen::HasMatchFieldsInX3) { + auto match_fields = pgen.MatchFieldsInX3(time); + CallMatchFields( + domain.fields.em, + domain.mesh.flds_bc(), + match_fields, + domain.mesh.metric, + xg_edge, + ds, + tags, + range_min, + range_max); + } + } + } else { + raise::Error("Invalid dimension", HERE); + } + } + + template + void AxisFieldsIn(const dir::direction_t& direction, + Domain& domain, + BCTags tags) { + /** + * axis boundaries + */ + if constexpr (M::CoordType != Coord::Cartesian) { + raise::ErrorIf(direction.get_dim() != in::x2, + "Invalid axis direction, should be x2", + HERE); + const auto i2_min = domain.mesh.i_min(in::x2); + const auto i2_max = domain.mesh.i_max(in::x2); + if (direction.get_sign() < 0) { + Kokkos::parallel_for( + "AxisBCFields", + domain.mesh.n_all(in::x1), + kernel::bc::AxisBoundaries_kernel(domain.fields.em, + i2_min, + tags)); + } else { + Kokkos::parallel_for( + "AxisBCFields", + domain.mesh.n_all(in::x1), + kernel::bc::AxisBoundaries_kernel(domain.fields.em, + i2_max, + tags)); + } + } else { + (void)direction; + (void)domain; + (void)tags; + raise::Error("Invalid coordinate type for axis BCs", HERE); + } + } + + template PG> + void FixedFieldsIn(const dir::direction_t& direction, + Domain& domain, + const PG& pgen, + const prm::Parameters& engine_params, + BCTags tags) { + if constexpr (::traits::pgen::HasFixFieldsConst) { + const auto time = engine_params.get("time"); + /** + * fixed field boundaries + */ + const auto sign = direction.get_sign(); + const auto dim = direction.get_dim(); + raise::ErrorIf(dim != in::x1 and M::CoordType != Coord::Cartesian, + "Fixed BCs only implemented for x1 in " + "non-cartesian coordinates", + HERE); + std::vector xi_min, xi_max; + const std::vector all_dirs { in::x1, in::x2, in::x3 }; + for (dim_t d { 0u }; d < M::Dim; ++d) { + const auto dd = all_dirs[d]; + if (dim == dd) { + if (sign > 0) { // + direction + xi_min.push_back(domain.mesh.n_all(dd) - N_GHOSTS); + xi_max.push_back(domain.mesh.n_all(dd)); + } else { // - direction + xi_min.push_back(0); + xi_max.push_back(N_GHOSTS); + } + } else { + xi_min.push_back(0); + xi_max.push_back(domain.mesh.n_all(dd)); + } + } + raise::ErrorIf(xi_min.size() != xi_max.size() or + xi_min.size() != static_cast(M::Dim), + "Invalid range size", + HERE); + std::vector comps; + if (tags & BC::E) { + comps.push_back(em::ex1); + comps.push_back(em::ex2); + comps.push_back(em::ex3); + } + if (tags & BC::B) { + comps.push_back(em::bx1); + comps.push_back(em::bx2); + comps.push_back(em::bx3); + } + raise::ErrorIf(M::CoordType != Coord::Cartesian and dim != in::x1, + "FixedFields cannot be used for non-cartesian metric", + HERE); + for (const auto& comp : comps) { + auto value = ZERO; + bool shouldset = false; + // if fix field function present, read from it + const auto newset = pgen.FixFieldsConst(time, + (bc_in)(sign * ((short)dim + 1)), + (em)comp); + value = newset.first; + shouldset = newset.second; + if (shouldset) { + // convert tetrad basis field (T) to contravariant (U) + real_t value_U = ZERO; + if (comp == em::ex1 or comp == em::bx1) { + value_U = domain.mesh.metric.template transform<1, Idx::T, Idx::U>( + { ZERO }, + value); + } else if (comp == em::ex2 or comp == em::bx2) { + value_U = domain.mesh.metric.template transform<2, Idx::T, Idx::U>( + { ZERO }, + value); + } else if (comp == em::ex3 or comp == em::bx3) { + value_U = domain.mesh.metric.template transform<3, Idx::T, Idx::U>( + { ZERO }, + value); + } else { + raise::Error("Invalid EM component", HERE); + } + if constexpr (M::Dim == Dim::_1D) { + Kokkos::deep_copy( + Kokkos::subview(domain.fields.em, + std::make_pair(xi_min[0], xi_max[0]), + comp), + value_U); + } else if constexpr (M::Dim == Dim::_2D) { + Kokkos::deep_copy( + Kokkos::subview(domain.fields.em, + std::make_pair(xi_min[0], xi_max[0]), + std::make_pair(xi_min[1], xi_max[1]), + comp), + value_U); + } else if constexpr (M::Dim == Dim::_3D) { + Kokkos::deep_copy( + Kokkos::subview(domain.fields.em, + std::make_pair(xi_min[0], xi_max[0]), + std::make_pair(xi_min[1], xi_max[1]), + std::make_pair(xi_min[2], xi_max[2]), + comp), + value_U); + } else { + raise::Error("Invalid dimension", HERE); + } + } + } + } else { + (void)direction; + (void)domain; + (void)tags; + } + } + + template + void PerfectConductorFieldsIn(const dir::direction_t& direction, + Domain& domain, + BCTags tags) { + /** + * perfect conductor field boundaries + */ + if constexpr (M::CoordType != Coord::Cartesian) { + (void)direction; + (void)domain; + (void)tags; + raise::Error( + "Perfect conductor BCs only applicable to cartesian coordinates", + HERE); + } else { + const auto sign = direction.get_sign(); + const auto dim = direction.get_dim(); + + std::vector xi_min, xi_max; + + const std::vector all_dirs { in::x1, in::x2, in::x3 }; + + for (auto d { 0u }; d < M::Dim; ++d) { + const auto dd = all_dirs[d]; + if (dim == dd) { + xi_min.push_back(0); + xi_max.push_back((sign < 0) ? (N_GHOSTS + 1) : N_GHOSTS); + } else { + xi_min.push_back(0); + xi_max.push_back(domain.mesh.n_all(dd)); + } + } + raise::ErrorIf(xi_min.size() != xi_max.size() or + xi_min.size() != static_cast(M::Dim), + "Invalid range size", + HERE); + + range_t range; + if constexpr (M::Dim == Dim::_1D) { + range = CreateRangePolicy({ xi_min[0] }, { xi_max[0] }); + } else if constexpr (M::Dim == Dim::_2D) { + range = CreateRangePolicy({ xi_min[0], xi_min[1] }, + { xi_max[0], xi_max[1] }); + } else if constexpr (M::Dim == Dim::_3D) { + range = CreateRangePolicy({ xi_min[0], xi_min[1], xi_min[2] }, + { xi_max[0], xi_max[1], xi_max[2] }); + } else { + raise::Error("Invalid dimension", HERE); + } + ncells_t i_edge; + if (sign > 0) { + i_edge = domain.mesh.i_max(dim); + } else { + i_edge = domain.mesh.i_min(dim); + } + + if (dim == in::x1) { + if (sign > 0) { + Kokkos::parallel_for( + "ConductorFields", + range, + kernel::bc::ConductorBoundaries_kernel( + domain.fields.em, + i_edge, + tags)); + } else { + Kokkos::parallel_for( + "ConductorFields", + range, + kernel::bc::ConductorBoundaries_kernel( + domain.fields.em, + i_edge, + tags)); + } + } else if (dim == in::x2) { + if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { + if (sign > 0) { + Kokkos::parallel_for( + "ConductorFields", + range, + kernel::bc::ConductorBoundaries_kernel( + domain.fields.em, + i_edge, + tags)); + } else { + Kokkos::parallel_for( + "ConductorFields", + range, + kernel::bc::ConductorBoundaries_kernel( + domain.fields.em, + i_edge, + tags)); + } + } else { + raise::Error("Invalid dimension", HERE); + } + } else { + if constexpr (M::Dim == Dim::_3D) { + if (sign > 0) { + Kokkos::parallel_for( + "ConductorFields", + range, + kernel::bc::ConductorBoundaries_kernel( + domain.fields.em, + i_edge, + tags)); + } else { + Kokkos::parallel_for( + "ConductorFields", + range, + kernel::bc::ConductorBoundaries_kernel( + domain.fields.em, + i_edge, + tags)); + } + } else { + raise::Error("Invalid dimension", HERE); + } + } + } + } + + template PG> + void AtmosphereFieldsIn(const dir::direction_t& direction, + Domain& domain, + const M& global_metric, + const Grid& global_grid, + const PG& pgen, + const SimulationParams& params, + const prm::Parameters& engine_params, + BCTags tags) { + /** + * atmosphere field boundaries + */ + if constexpr (::traits::pgen::HasAtmFields) { + const auto time = engine_params.get("time"); + const auto [sign, dim, xg_min, xg_max] = GetAtmosphereExtent(direction, + global_metric, + global_grid, + params); + // get_atm_extent(direction); + const auto dd = static_cast(dim); + boundaries_t box; + boundaries_t incl_ghosts; + for (auto d { 0u }; d < M::Dim; ++d) { + if (d == dd) { + box.emplace_back(xg_min, xg_max); + if (sign > 0) { + incl_ghosts.emplace_back(false, true); + } else { + incl_ghosts.emplace_back(true, false); + } + } else { + box.push_back(Range::All); + incl_ghosts.emplace_back(true, true); + } + } + if (not domain.mesh.Intersects(box)) { + return; + } + const auto intersect_range = domain.mesh.ExtentToRange(box, incl_ghosts); + tuple_t range_min { 0 }; + tuple_t range_max { 0 }; + + for (auto d { 0u }; d < M::Dim; ++d) { + range_min[d] = intersect_range[d].first; + range_max[d] = intersect_range[d].second; + } + auto atm_fields = pgen.AtmFields(time); + ncells_t il_edge; + if (sign > 0) { + il_edge = range_min[dd] - N_GHOSTS; + } else { + il_edge = range_max[dd] - 1 - N_GHOSTS; + } + const auto range = CreateRangePolicy(range_min, range_max); + if (dim == in::x1) { + if (sign > 0) { + Kokkos::parallel_for( + "AtmosphereBCFields", + range, + kernel::bc::EnforcedBoundaries_kernel( + domain.fields.em, + atm_fields, + domain.mesh.metric, + il_edge, + tags)); + } else { + Kokkos::parallel_for( + "AtmosphereBCFields", + range, + kernel::bc::EnforcedBoundaries_kernel( + domain.fields.em, + atm_fields, + domain.mesh.metric, + il_edge, + tags)); + } + } else if (dim == in::x2) { + if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { + if (sign > 0) { + Kokkos::parallel_for( + "AtmosphereBCFields", + range, + kernel::bc::EnforcedBoundaries_kernel( + domain.fields.em, + atm_fields, + domain.mesh.metric, + il_edge, + tags)); + } else { + Kokkos::parallel_for( + "AtmosphereBCFields", + range, + kernel::bc::EnforcedBoundaries_kernel( + domain.fields.em, + atm_fields, + domain.mesh.metric, + il_edge, + tags)); + } + } else { + raise::Error("Invalid dimension", HERE); + } + } else if (dim == in::x3) { + if constexpr (M::Dim == Dim::_3D) { + if (sign > 0) { + Kokkos::parallel_for( + "AtmosphereBCFields", + range, + kernel::bc::EnforcedBoundaries_kernel( + domain.fields.em, + atm_fields, + domain.mesh.metric, + il_edge, + tags)); + } else { + Kokkos::parallel_for( + "AtmosphereBCFields", + range, + kernel::bc::EnforcedBoundaries_kernel( + domain.fields.em, + atm_fields, + domain.mesh.metric, + il_edge, + tags)); + } + } else { + raise::Error("Invalid dimension", HERE); + } + } else { + raise::Error("Invalid dimension", HERE); + } + } else { + (void)direction; + (void)domain; + (void)tags; + } + } + + template + void CustomFieldsIn(const dir::direction_t& direction, + Domain& domain, + BCTags tags) { + (void)direction; + (void)domain; + (void)tags; + raise::Error("Custom boundaries not implemented", HERE); + } + + template PG> + void FieldBoundaries(Domain& domain, + const M& global_metric, + const Grid& global_grid, + const PG& pgen, + const prm::Parameters& engine_params, + const SimulationParams& params, + BCTags tags) { + for (auto& direction : dir::Directions::orth) { + if (global_grid.flds_bc_in(direction) == FldsBC::MATCH) { + MatchFieldsIn(direction, + domain, + global_grid, + pgen, + engine_params, + params, + tags); + } else if (global_grid.flds_bc_in(direction) == FldsBC::AXIS) { + if (domain.mesh.flds_bc_in(direction) == FldsBC::AXIS) { + AxisFieldsIn(direction, domain, tags); + } + } else if (global_grid.flds_bc_in(direction) == FldsBC::ATMOSPHERE) { + AtmosphereFieldsIn(direction, + domain, + global_metric, + global_grid, + pgen, + params, + engine_params, + tags); + } else if (global_grid.flds_bc_in(direction) == FldsBC::FIXED) { + if (domain.mesh.flds_bc_in(direction) == FldsBC::FIXED) { + FixedFieldsIn(direction, domain, pgen, engine_params, tags); + } + } else if (global_grid.flds_bc_in(direction) == FldsBC::CONDUCTOR) { + if (domain.mesh.flds_bc_in(direction) == FldsBC::CONDUCTOR) { + PerfectConductorFieldsIn(direction, domain, tags); + } + } else if (global_grid.flds_bc_in(direction) == FldsBC::CUSTOM) { + if (domain.mesh.flds_bc_in(direction) == FldsBC::CUSTOM) { + CustomFieldsIn(direction, domain, tags); + } + } else if (global_grid.flds_bc_in(direction) == FldsBC::HORIZON) { + raise::Error("HORIZON BCs only applicable for GR", HERE); + } + } // loop over directions + } + + } // namespace srpic +} // namespace ntt + +#endif // ENGINES_SRPIC_FIELDS_BCS_H diff --git a/src/engines/srpic/fieldsolvers.h b/src/engines/srpic/fieldsolvers.h new file mode 100644 index 000000000..3a2fb4479 --- /dev/null +++ b/src/engines/srpic/fieldsolvers.h @@ -0,0 +1,205 @@ +/** + * @file engines/srpic/fieldsolvers.h + * @brief Field solver routines (Faraday, Ampere) for the SRPIC engine + * @implements + * - ntt::srpic::Faraday<> -> void + * - ntt::srpic::Ampere<> -> void + * - ntt::srpic::CurrentsAmpere<> -> void + * @namespaces: + * - ntt::srpic:: + */ + +#ifndef ENGINES_SRPIC_FIELDSOLVERS_H +#define ENGINES_SRPIC_FIELDSOLVERS_H + +#include "enums.h" +#include "global.h" + +#include "traits/pgen.h" +#include "utils/log.h" +#include "utils/numeric.h" +#include "utils/param_container.h" + +#include "engines/srpic/utils.h" +#include "framework/domain/domain.h" +#include "framework/parameters/parameters.h" +#include "kernels/ampere_mink.hpp" +#include "kernels/ampere_sr.hpp" +#include "kernels/faraday_mink.hpp" +#include "kernels/faraday_sr.hpp" + +#include "engines/engine.hpp" + +namespace ntt { + namespace srpic { + + template + void Faraday(Domain& domain, + const prm::Parameters& engine_params, + const SimulationParams& params, + real_t fraction = ONE) { + logger::Checkpoint("Launching Faraday kernel", HERE); + const auto dt = engine_params.get("dt"); + + const auto dT = fraction * + params.template get( + "algorithms.timestep.correction") * + dt; + if constexpr (M::CoordType == Coord::Cartesian) { + // minkowski case + const auto dx = math::sqrt(domain.mesh.metric.template h_<1, 1>({})); + const auto deltax = params.template get( + "algorithms.fieldsolver.delta_x"); + const auto deltay = params.template get( + "algorithms.fieldsolver.delta_y"); + const auto betaxy = params.template get( + "algorithms.fieldsolver.beta_xy"); + const auto betayx = params.template get( + "algorithms.fieldsolver.beta_yx"); + const auto deltaz = params.template get( + "algorithms.fieldsolver.delta_z"); + const auto betaxz = params.template get( + "algorithms.fieldsolver.beta_xz"); + const auto betazx = params.template get( + "algorithms.fieldsolver.beta_zx"); + const auto betayz = params.template get( + "algorithms.fieldsolver.beta_yz"); + const auto betazy = params.template get( + "algorithms.fieldsolver.beta_zy"); + real_t coeff1, coeff2; + if constexpr (M::Dim == Dim::_2D) { + coeff1 = dT / SQR(dx); + coeff2 = dT; + } else { + coeff1 = dT / dx; + coeff2 = ZERO; + } + Kokkos::parallel_for("Faraday", + domain.mesh.rangeActiveCells(), + kernel::mink::Faraday_kernel(domain.fields.em, + coeff1, + coeff2, + deltax, + deltay, + betaxy, + betayx, + deltaz, + betaxz, + betazx, + betayz, + betazy)); + } else { + Kokkos::parallel_for("Faraday", + domain.mesh.rangeActiveCells(), + kernel::sr::Faraday_kernel(domain.fields.em, + domain.mesh.metric, + dT, + domain.mesh.flds_bc())); + } + } + + template + void Ampere(Domain& domain, + const prm::Parameters& engine_params, + const SimulationParams& params, + real_t fraction = ONE) { + logger::Checkpoint("Launching Ampere kernel", HERE); + const auto dt = engine_params.get("dt"); + + const auto dT = fraction * + params.template get( + "algorithms.timestep.correction") * + dt; + auto range = RangeWithAxisBCs(domain); + if constexpr (M::CoordType == Coord::Cartesian) { + // minkowski case + const auto dx = math::sqrt(domain.mesh.metric.template h_<1, 1>({})); + real_t coeff1, coeff2; + if constexpr (M::Dim == Dim::_2D) { + coeff1 = dT / SQR(dx); + coeff2 = dT; + } else { + coeff1 = dT / dx; + coeff2 = ZERO; + } + + Kokkos::parallel_for( + "Ampere", + range, + kernel::mink::Ampere_kernel(domain.fields.em, coeff1, coeff2)); + } else { + const auto ni2 = domain.mesh.n_active(in::x2); + Kokkos::parallel_for("Ampere", + range, + kernel::sr::Ampere_kernel(domain.fields.em, + domain.mesh.metric, + dT, + ni2, + domain.mesh.flds_bc())); + } + } + + template PG> + void CurrentsAmpere(Domain& domain, + const prm::Parameters& engine_params, + const SimulationParams& params, + const PG& pgen) { + logger::Checkpoint("Launching Ampere kernel for adding currents", HERE); + const auto dt = engine_params.get("dt"); + + const auto q0 = params.template get("scales.q0"); + const auto n0 = params.template get("scales.n0"); + const auto B0 = params.template get("scales.B0"); + if constexpr (M::CoordType == Coord::Cartesian) { + // minkowski case + const auto V0 = params.template get("scales.V0"); + const auto ppc0 = params.template get("particles.ppc0"); + const auto coeff = -dt * q0 / (B0 * V0); + if constexpr (::traits::pgen::HasExtCurrent) { + const std::vector xmin { domain.mesh.extent(in::x1).first, + domain.mesh.extent(in::x2).first, + domain.mesh.extent(in::x3).first }; + const auto ext_current = pgen.ext_current; + const auto dx = domain.mesh.metric.template sqrt_h_<1, 1>({}); + Kokkos::parallel_for( + "Ampere", + domain.mesh.rangeActiveCells(), + kernel::mink::CurrentsAmpere_kernel( + domain.fields.em, + domain.fields.cur, + coeff, + ppc0, + ext_current, + xmin, + dx)); + } else { + Kokkos::parallel_for( + "Ampere", + domain.mesh.rangeActiveCells(), + kernel::mink::CurrentsAmpere_kernel(domain.fields.em, + domain.fields.cur, + coeff, + ppc0)); + } + } else { + // non-minkowski + const auto coeff = -dt * q0 * n0 / B0; + auto range = RangeWithAxisBCs(domain); + const auto ni2 = domain.mesh.n_active(in::x2); + Kokkos::parallel_for( + "Ampere", + range, + kernel::sr::CurrentsAmpere_kernel(domain.fields.em, + domain.fields.cur, + domain.mesh.metric, + coeff, + ONE / n0, + ni2, + domain.mesh.flds_bc())); + } + } + + } // namespace srpic +} // namespace ntt + +#endif // ENGINES_SRPIC_FIELDSOLVERS_H diff --git a/src/engines/srpic/particle_pusher.h b/src/engines/srpic/particle_pusher.h new file mode 100644 index 000000000..d708cfb6b --- /dev/null +++ b/src/engines/srpic/particle_pusher.h @@ -0,0 +1,190 @@ +/** + * @file engines/srpic/particle_pusher.h + * @brief Particle pusher routines for the SRPIC engine + * @implements + * - ntt::srpic::ParticlePush<> -> void + * @namespaces: + * - ntt::srpic:: + */ + +#ifndef ENGINES_SRPIC_PARTICLE_PUSHER_H +#define ENGINES_SRPIC_PARTICLE_PUSHER_H + +#include "enums.h" +#include "global.h" + +#include "traits/metric.h" +#include "traits/policies.h" +#include "utils/log.h" +#include "utils/numeric.h" +#include "utils/param_container.h" + +#include "engines/srpic/utils.h" +#include "framework/domain/domain.h" +#include "framework/domain/grid.h" +#include "framework/parameters/parameters.h" +#include "kernels/pushers/context.h" +#include "kernels/pushers/sr.hpp" +#include "kernels/pushers/sr_policies.h" + +#include "engines/engine.hpp" + +namespace ntt { + namespace srpic { + + template PG> + void ParticlePush(Domain& domain, + const Grid& global_grid, + const M& global_metric, + const prm::Parameters& engine_params, + const SimulationParams& params, + const PG& pgen) { + const auto dt = engine_params.get("dt"); + const auto time = engine_params.get("time"); + + real_t gx1 { ZERO }, gx2 { ZERO }, gx3 { ZERO }, ds { ZERO }; + real_t x_surf { ZERO }; + bool has_atmosphere = false; + for (auto& direction : dir::Directions::orth) { + if (global_grid.prtl_bc_in(direction) == PrtlBC::ATMOSPHERE) { + raise::ErrorIf(has_atmosphere, + "Only one direction is allowed to have atm boundaries", + HERE); + has_atmosphere = true; + const auto g = params.template get( + "grid.boundaries.atmosphere.g"); + ds = params.template get("grid.boundaries.atmosphere.ds"); + const auto [sign, dim, xg_min, xg_max] = + GetAtmosphereExtent(direction, global_metric, global_grid, params); + if (dim == in::x1) { + gx1 = sign > 0 ? g : -g; + gx2 = ZERO; + gx3 = ZERO; + } else if (dim == in::x2) { + gx1 = ZERO; + gx2 = sign > 0 ? g : -g; + gx3 = ZERO; + } else if (dim == in::x3) { + gx1 = ZERO; + gx2 = ZERO; + gx3 = sign > 0 ? g : -g; + } else { + raise::Error("Invalid dimension", HERE); + } + if (sign > 0) { + x_surf = xg_min; + } else { + x_surf = xg_max; + } + } + } + for (auto& species : domain.species) { + if ((species.pusher() == ParticlePusher::NONE) or (species.npart() == 0)) { + continue; + } + species.set_unsorted(); + logger::Checkpoint( + fmt::format("Launching particle pusher kernel for %d [%s] : %lu", + species.index(), + species.label().c_str(), + species.npart()), + HERE); + + kernel::sr::PusherContext pusher_ctx { + species.index(), + species.pusher(), + species.radiative_drag_flags(), + species.mass(), + species.charge(), + time, + dt, + params.template get("scales.omegaB0"), + static_cast(domain.mesh.n_active(in::x1)), + static_cast(domain.mesh.n_active(in::x2)), + static_cast(domain.mesh.n_active(in::x3)) + }; + + if (species.pusher() & ParticlePusher::GCA) { + pusher_ctx.gca = kernel::sr::PusherGCAContext( + params.template get("algorithms.gca.larmor_max"), + params.template get("algorithms.gca.e_ovr_b_max")); + } + + if (has_atmosphere) { + pusher_ctx.atmosphere = kernel::sr::PusherAtmosphereContext(gx1, + gx2, + gx3, + x_surf, + ds); + } + + if (species.radiative_drag_flags() & RadiativeDrag::SYNCHROTRON) { + pusher_ctx.synchrotron_drag = kernel::sr::PusherSynchrotronDragContext( + dt, + pusher_ctx.omegaB0, + params.template get("radiation.drag.synchrotron.gamma_rad"), + species.mass()); + } + + if (species.radiative_drag_flags() & RadiativeDrag::COMPTON) { + pusher_ctx.compton_drag = kernel::sr::PusherComptonDragContext( + dt, + pusher_ctx.omegaB0, + params.template get("radiation.drag.compton.gamma_rad"), + species.mass()); + } + + auto pusher_boundaries = kernel::sr::PusherBoundaries { + domain.mesh.prtl_bc() + }; + + kernel::sr::MakePusherPolicy( + pgen, + domain, + params, + pusher_ctx, + species.emission_policy_flag(), + has_atmosphere, + [&](const auto& policies) { + using policy_t = std::decay_t; + Kokkos::parallel_for( + "ParticlePusher", + species.rangeActiveParticles(), + kernel::sr::Pusher_kernel { pusher_ctx, + pusher_boundaries, + species, + domain.fields.em, + domain.mesh.metric, + policies }); + // if emission takes place, update the npart and counter of emitted species + if constexpr ( + not ::traits::emission::IsNoPolicy) { + const auto& emission_policy = policies.emission_policy; + const auto emitted_species = emission_policy.emitted_species_indices(); + const auto n_inj = emission_policy.numbers_injected(); + raise::ErrorIf(emitted_species.size() != n_inj.size(), + "Emission policy emitted_species_indices and " + "numbers_injected must have the same size", + HERE); + for (auto i = 0u; i < emitted_species.size(); ++i) { + const auto sp_idx = emitted_species[i]; + raise::ErrorIf(sp_idx > domain.species.size(), + "Invalid emitted species index from custom " + "emission policy", + HERE); + domain.species[sp_idx - 1].set_npart( + domain.species[sp_idx - 1].npart() + n_inj[i]); + domain.species[sp_idx - 1].set_counter( + domain.species[sp_idx - 1].counter() + n_inj[i]); + } + } + }); + } + } + + } // namespace srpic +} // namespace ntt + +#endif // ENGINES_SRPIC_PARTICLE_PUSHER_H diff --git a/src/engines/srpic/particles_bcs.h b/src/engines/srpic/particles_bcs.h new file mode 100644 index 000000000..d7b3b9a47 --- /dev/null +++ b/src/engines/srpic/particles_bcs.h @@ -0,0 +1,174 @@ +/** + * @file engines/srpic/particles_bcs.h + * @brief Particle boundary condition routines for the SRPIC engine + * @implements + * - ntt::srpic::AtmosphereParticlesIn<> -> void + * - ntt::srpic::ParticleInjector<> -> void + * @namespaces: + * - ntt::srpic:: + */ + +#ifndef ENGINES_SRPIC_PARTICLES_BCS_H +#define ENGINES_SRPIC_PARTICLES_BCS_H + +#include "enums.h" +#include "global.h" + +#include "arch/directions.h" +#include "traits/metric.h" +#include "utils/numeric.h" + +#include "archetypes/energy_dist.h" +#include "archetypes/particle_injector.h" +#include "archetypes/spatial_dist.h" +#include "engines/srpic/utils.h" +#include "framework/domain/domain.h" +#include "framework/domain/metadomain.h" +#include "framework/parameters/parameters.h" +#include "kernels/particle_moments.hpp" + +namespace ntt { + namespace srpic { + + template + void AtmosphereParticlesIn(const dir::direction_t& direction, + Metadomain& metadomain, + Domain& domain, + const SimulationParams& params, + InjTags tags) { + const auto [sign, dim, xg_min, xg_max] = srpic::GetAtmosphereExtent( + direction, + metadomain.mesh().metric, + metadomain.mesh(), + params); + + const auto x_surf = sign > 0 ? xg_min : xg_max; + const auto ds = params.template get( + "grid.boundaries.atmosphere.ds"); + const auto temp = params.template get( + "grid.boundaries.atmosphere.temperature"); + const auto height = params.template get( + "grid.boundaries.atmosphere.height"); + const auto species = params.template get>( + "grid.boundaries.atmosphere.species"); + const auto nmax = params.template get( + "grid.boundaries.atmosphere.density"); + + Kokkos::deep_copy(domain.fields.bckp, ZERO); + auto scatter_bckp = Kokkos::Experimental::create_scatter_view( + domain.fields.bckp); + const auto use_weights = M::CoordType != Coord::Cartesian; + const auto ni2 = domain.mesh.n_active(in::x2); + const auto inv_n0 = ONE / params.template get("scales.n0"); + + // compute the density of the two species + if (tags & Inj::AssumeEmpty) { + if constexpr (M::Dim == Dim::_1D) { + Kokkos::deep_copy( + Kokkos::subview(domain.fields.bckp, Kokkos::ALL, std::make_pair(0, 1)), + ZERO); + } else if constexpr (M::Dim == Dim::_2D) { + Kokkos::deep_copy(Kokkos::subview(domain.fields.bckp, + Kokkos::ALL, + Kokkos::ALL, + std::make_pair(0, 1)), + ZERO); + } else if constexpr (M::Dim == Dim::_3D) { + Kokkos::deep_copy(Kokkos::subview(domain.fields.bckp, + Kokkos::ALL, + Kokkos::ALL, + Kokkos::ALL, + std::make_pair(0, 1)), + ZERO); + } + } else { + for (const auto& sp : + std::vector { species.first, species.second }) { + auto& prtl_spec = domain.species[sp - 1]; + if (prtl_spec.npart() == 0) { + continue; + } + // clang-format off + Kokkos::parallel_for( + "ComputeMoments", + prtl_spec.rangeActiveParticles(), + kernel::ParticleMoments_kernel( + {}, scatter_bckp, 0, + prtl_spec.i1, prtl_spec.i2, prtl_spec.i3, + prtl_spec.dx1, prtl_spec.dx2, prtl_spec.dx3, + prtl_spec.ux1, prtl_spec.ux2, prtl_spec.ux3, + prtl_spec.phi, prtl_spec.weight, prtl_spec.tag, + prtl_spec.mass(), prtl_spec.charge(), + use_weights, + domain.mesh.metric, domain.mesh.flds_bc(), + ni2, inv_n0, 0)); + // clang-format on + prtl_spec.set_unsorted(); + } + Kokkos::Experimental::contribute(domain.fields.bckp, scatter_bckp); + metadomain.SynchronizeFields(domain, Comm::Bckp, { 0, 1 }); + } + + const auto maxwellian = arch::energy_dist::Maxwellian { + domain.random_pool(), + temp + }; + + auto do_inject = [&]() { + auto target_density = + arch::AtmosphereDensityProfile { nmax, + height, + x_surf, + ds }; + const auto spatial_dist = + arch::spatial_dist::Replenish { + domain.mesh.metric, + domain.fields.bckp, + 0, + target_density, + nmax + }; + arch::InjectNonUniform( + params, + domain, + { species.first, species.second }, + { maxwellian, maxwellian }, + spatial_dist, + nmax, + use_weights); + }; + + if (dim == in::x1) { + sign > 0 ? do_inject.template operator()() + : do_inject.template operator()(); + } else if (dim == in::x2) { + sign > 0 ? do_inject.template operator()() + : do_inject.template operator()(); + } else if (dim == in::x3) { + sign > 0 ? do_inject.template operator()() + : do_inject.template operator()(); + } else { + raise::Error("Invalid dimension", HERE); + } + } + + template + void ParticleInjector(Metadomain& metadomain, + Domain& domain, + const SimulationParams& params, + InjTags tags = Inj::None) { + for (auto& direction : dir::Directions::orth) { + if (metadomain.mesh().prtl_bc_in(direction) == PrtlBC::ATMOSPHERE) { + AtmosphereParticlesIn(direction, metadomain, domain, params, tags); + } + } + } + + } // namespace srpic +} // namespace ntt + +#endif diff --git a/src/engines/srpic/srpic.hpp b/src/engines/srpic/srpic.hpp new file mode 100644 index 000000000..94a8949c1 --- /dev/null +++ b/src/engines/srpic/srpic.hpp @@ -0,0 +1,193 @@ +/** + * @file engines/srpic/srpic.hpp + * @brief Simulation engine class which specializes on SRPIC + * @implements + * - ntt::SRPICEngine<> : ntt::Engine<> + * @cpp: + * - srpic.cpp + * @namespaces: + * - ntt:: + */ + +#ifndef ENGINES_SRPIC_SRPIC_HPP +#define ENGINES_SRPIC_SRPIC_HPP + +#include "enums.h" +#include "global.h" + +#include "traits/metric.h" +#include "utils/numeric.h" +#include "utils/timer.h" + +#include "engines/srpic/currents.h" +#include "engines/srpic/fields_bcs.h" +#include "engines/srpic/fieldsolvers.h" +#include "engines/srpic/particle_pusher.h" +#include "engines/srpic/particles_bcs.h" +#include "framework/domain/domain.h" +#include "framework/parameters/parameters.h" + +#include "engines/engine.hpp" +#include "pgen.hpp" + +#include +#include +#include + +namespace ntt { + + template + class SRPICEngine : public Engine { + + using base_t = Engine; + using pgen_t = user::PGen; + using domain_t = Domain; + // contents + using base_t::m_metadomain; + using base_t::m_params; + using base_t::m_pgen; + // methods + using base_t::init; + // variables + using base_t::dt; + using base_t::max_steps; + using base_t::runtime; + using base_t::step; + using base_t::time; + + public: + static constexpr auto S { SimEngine::SRPIC }; + + SRPICEngine(const SimulationParams& params) : base_t { params } {} + + ~SRPICEngine() override = default; + + void step_forward(timer::Timers& timers, domain_t& dom) override { + const auto fieldsolver_enabled = m_params.template get( + "algorithms.fieldsolver.enable"); + const auto deposit_enabled = m_params.template get( + "algorithms.deposit.enable"); + + if (step == 0) { + // communicate fields and apply BCs on the first timestep + m_metadomain.CommunicateFields(dom, Comm::B | Comm::E); + srpic::FieldBoundaries(dom, + m_metadomain.mesh().metric, + m_metadomain.mesh(), + m_pgen, + this->engineParams(), + m_params, + BC::B | BC::E); + srpic::ParticleInjector(m_metadomain, dom, m_params); + } + + if (fieldsolver_enabled) { + timers.start("FieldSolver"); + srpic::Faraday(dom, this->engineParams(), m_params, HALF); + timers.stop("FieldSolver"); + + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::B); + timers.stop("Communications"); + + timers.start("FieldBoundaries"); + srpic::FieldBoundaries(dom, + m_metadomain.mesh().metric, + m_metadomain.mesh(), + m_pgen, + this->engineParams(), + m_params, + BC::B); + timers.stop("FieldBoundaries"); + Kokkos::fence(); + } + + { + timers.start("ParticlePusher"); + srpic::ParticlePush(dom, + m_metadomain.mesh(), + m_metadomain.mesh().metric, + this->engineParams(), + m_params, + m_pgen); + timers.stop("ParticlePusher"); + + if (deposit_enabled) { + timers.start("CurrentDeposit"); + srpic::CurrentsDeposit(dom, this->engineParams()); + timers.stop("CurrentDeposit"); + + timers.start("Communications"); + m_metadomain.SynchronizeFields(dom, Comm::J); + m_metadomain.CommunicateFields(dom, Comm::J); + timers.stop("Communications"); + + timers.start("CurrentFiltering"); + srpic::CurrentsFilter(m_metadomain, dom, m_params); + timers.stop("CurrentFiltering"); + } + + timers.start("Communications"); + m_metadomain.CommunicateParticles(dom); + timers.stop("Communications"); + } + + if (fieldsolver_enabled) { + timers.start("FieldSolver"); + srpic::Faraday(dom, this->engineParams(), m_params, HALF); + timers.stop("FieldSolver"); + + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::B); + timers.stop("Communications"); + + timers.start("FieldBoundaries"); + srpic::FieldBoundaries(dom, + m_metadomain.mesh().metric, + m_metadomain.mesh(), + m_pgen, + this->engineParams(), + m_params, + BC::B); + timers.stop("FieldBoundaries"); + + timers.start("FieldSolver"); + srpic::Ampere(dom, this->engineParams(), m_params, ONE); + timers.stop("FieldSolver"); + + if (deposit_enabled) { + timers.start("FieldSolver"); + srpic::CurrentsAmpere(dom, this->engineParams(), m_params, m_pgen); + timers.stop("FieldSolver"); + } + + timers.start("Communications"); + m_metadomain.CommunicateFields(dom, Comm::E | Comm::J); + timers.stop("Communications"); + + timers.start("FieldBoundaries"); + srpic::FieldBoundaries(dom, + m_metadomain.mesh().metric, + m_metadomain.mesh(), + m_pgen, + this->engineParams(), + m_params, + BC::E); + timers.stop("FieldBoundaries"); + } + + { + timers.start("Injector"); + srpic::ParticleInjector(m_metadomain, dom, m_params); + timers.stop("Injector"); + } + + timers.start("ParticleSort"); + m_metadomain.SortParticles(time, step, m_params, dom); + timers.stop("ParticleSort"); + } + }; + +} // namespace ntt + +#endif // ENGINES_SRPIC_SRPIC_HPP diff --git a/src/engines/srpic/utils.h b/src/engines/srpic/utils.h new file mode 100644 index 000000000..0f8926a43 --- /dev/null +++ b/src/engines/srpic/utils.h @@ -0,0 +1,134 @@ +/** + * @file engines/srpic/utils.h + * @brief Utility functions used by the SRPIC engine + * @implements + * - ntt::srpic::GetAtmosphereExtent<> -> std::tuple + * @namespaces: + * - ntt::srpic:: + */ + +#ifndef ENGINES_SRPIC_UTILS_H +#define ENGINES_SRPIC_UTILS_H + +#include "enums.h" +#include "global.h" + +#include "arch/directions.h" +#include "traits/metric.h" +#include "utils/numeric.h" + +#include "framework/domain/domain.h" +#include "framework/domain/grid.h" +#include "framework/parameters/parameters.h" + +#include + +namespace ntt { + namespace srpic { + + /** + * @brief Get the buffer region of the atmosphere and the direction + * @param direction direction in which the atmosphere is applied + * @return tuple: [sign of the direction, the direction (as in::), the min and max extent + * @note xg_min and xg_max are the extents where the fields are set, not the atmosphere itself + * @note i.e. + * + * fields set particles injected + * ghost zone | | + * v v v + * |....|...........|*******************..... -> x1 + * ^ ^ + * xg_min xg_max + * | | | + * |<-- buffer -->|<-- atmosphere -->| + * + * in this case the function returns { -1, in::x1, xg_min, xg_max } + */ + template + auto GetAtmosphereExtent(const dir::direction_t& direction, + const M& global_metric, + const Grid& global_grid, + const SimulationParams& params) + -> std::tuple { + const auto sign = direction.get_sign(); + const auto dim = direction.get_dim(); + const auto min_buff = params.template get( + "algorithms.current_filters") + + 2; + const auto buffer_ncells = min_buff > 5 ? min_buff : 5; + if (M::CoordType != Coord::Cartesian and (dim != in::x1 or sign > 0)) { + raise::Error("For non-cartesian coordinates atmosphere BCs is " + "possible only in -x1 (@ rmin)", + HERE); + } + real_t xg_min { ZERO }, xg_max { ZERO }; + ncells_t ig_min, ig_max; + if (sign > 0) { // + direction + ig_min = global_grid.n_active(dim) - buffer_ncells; + ig_max = global_grid.n_active(dim); + } else { // - direction + ig_min = 0; + ig_max = buffer_ncells; + } + + if (dim == in::x1) { + xg_min = global_metric.template convert<1, Crd::Cd, Crd::Ph>( + static_cast(ig_min)); + xg_max = global_metric.template convert<1, Crd::Cd, Crd::Ph>( + static_cast(ig_max)); + } else if (dim == in::x2) { + if constexpr (M::Dim == Dim::_2D or M::Dim == Dim::_3D) { + xg_min = global_metric.template convert<2, Crd::Cd, Crd::Ph>( + static_cast(ig_min)); + xg_max = global_metric.template convert<2, Crd::Cd, Crd::Ph>( + static_cast(ig_max)); + } else { + raise::Error("Invalid dimension", HERE); + } + } else if (dim == in::x3) { + if constexpr (M::Dim == Dim::_3D) { + xg_min = global_metric.template convert<3, Crd::Cd, Crd::Ph>( + static_cast(ig_min)); + xg_max = global_metric.template convert<3, Crd::Cd, Crd::Ph>( + static_cast(ig_max)); + } else { + raise::Error("Invalid dimension", HERE); + } + } else { + raise::Error("Invalid dimension", HERE); + } + return { sign, dim, xg_min, xg_max }; + } + + template + auto RangeWithAxisBCs(const Domain& domain) + -> range_t { + auto range = domain.mesh.rangeActiveCells(); + if constexpr (M::CoordType != Coord::Cartesian) { + /** + * @brief taking one extra cell in the x2 direction if AXIS BCs + */ + if constexpr (M::Dim == Dim::_2D) { + if (domain.mesh.flds_bc_in({ 0, +1 }) == FldsBC::AXIS) { + range = CreateRangePolicy( + { domain.mesh.i_min(in::x1), domain.mesh.i_min(in::x2) }, + { domain.mesh.i_max(in::x1), domain.mesh.i_max(in::x2) + 1 }); + } + } else if constexpr (M::Dim == Dim::_3D) { + if (domain.mesh.flds_bc_in({ 0, +1, 0 }) == FldsBC::AXIS) { + range = CreateRangePolicy({ domain.mesh.i_min(in::x1), + domain.mesh.i_min(in::x2), + domain.mesh.i_min(in::x3) }, + { domain.mesh.i_max(in::x1), + domain.mesh.i_max(in::x2) + 1, + domain.mesh.i_max(in::x3) }); + } + } + } + return range; + } + + } // namespace srpic +} // namespace ntt + +#endif diff --git a/src/entity.cpp b/src/entity.cpp index 8d88e2654..c05c6c18a 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -1,21 +1,49 @@ #include "enums.h" +#include "global.h" #include "arch/traits.h" +#include "traits/metric.h" +#include "traits/pgen.h" #include "utils/error.h" -#include "engines/engine_traits.h" #include "framework/simulation.h" #include "framework/specialization_registry.h" +#include "engines/grpic/grpic.hpp" +#include "engines/srpic/srpic.hpp" #include "pgen.hpp" - + +#include +#include #include +namespace ntt { + template + struct EngineSelector; + + template <> + struct EngineSelector { + template + using type = SRPICEngine; + }; + + template <> + struct EngineSelector { + template + using type = GRPICEngine; + }; +} // namespace ntt + +template +static constexpr bool is_compatible(::traits::pgen::compatible_with) { + return ((N == Is) || ...); +} + template class M, Dimension D> static constexpr bool should_compile { - traits::check_compatibility::value(user::PGen>::engines) && - traits::check_compatibility::MetricType>::value(user::PGen>::metrics) && - traits::check_compatibility::value(user::PGen>::dimensions) + is_compatible(user::PGen>::engines) and + is_compatible::MetricType>(user::PGen>::metrics) and + is_compatible(user::PGen>::dimensions) }; template class M, Dimension D> @@ -25,47 +53,50 @@ void dispatch_engine(ntt::Simulation& sim) { } else if constexpr (S == SimEngine::GRPIC) { sim.run::template type, M, D>(); } else { - static_assert( - traits::always_false>::value, - "Unsupported engine"); + static_assert(::traits::always_false>::value, + "Unsupported engine"); } } auto main(int argc, char* argv[]) -> int { - ntt::Simulation sim { argc, argv }; - - auto matched = false; - auto launched = false; - - ntt::for_each_specialization([&](auto spec) { - using Spec = decltype(spec); - const auto requested_e = sim.requested_engine(); - const auto requested_m = sim.requested_metric(); - const auto requested_d = sim.requested_dimension(); - - if (requested_e == Spec::engine && requested_m == Spec::metric && - requested_d == Spec::dimension) { - matched = true; - if constexpr ( - should_compile) { - dispatch_engine( - sim); - launched = true; - } else { - raise::Fatal( - "Requested configuration is not available for this problem generator", - HERE); + try { + ntt::Simulation sim { argc, argv }; + + auto matched = false; + auto launched = false; + ntt::for_each_specialization([&](auto spec) { + using Spec = decltype(spec); + const auto requested_e = sim.requested_engine(); + const auto requested_m = sim.requested_metric(); + const auto requested_d = sim.requested_dimension(); + + if (requested_e == Spec::engine && requested_m == Spec::metric && + requested_d == Spec::dimension) { + matched = true; + if constexpr ( + should_compile) { + dispatch_engine( + sim); + launched = true; + } else { + raise::Fatal("Requested configuration is not available for this " + "problem generator", + HERE); + } } + }); + + if (not matched) { + raise::Fatal("Invalid engine, metric, or dimension combination", HERE); } - }); - if (not matched) { - raise::Fatal("Invalid engine, metric, or dimension combination", HERE); - } + if (not launched) { + raise::Fatal("Requested combination is not enabled in this build", HERE); + } - if (not launched) { - raise::Fatal("Requested combination is not enabled in this build", HERE); + return 0; + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << '\n'; + return 1; } - - return 0; } diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index 014967870..5343f3d8a 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -4,16 +4,26 @@ # # @sources: # -# * parameters.cpp +# * parameters/parameters.cpp +# * parameters/particles.cpp +# * parameters/grid.cpp +# * parameters/output.cpp +# * parameters/algorithms.cpp +# * parameters/extra.cpp # * simulation.cpp # * domain/grid.cpp # * domain/metadomain.cpp -# * domain/communications.cpp -# * domain/checkpoint.cpp +# * domain/metadomain_sort.cpp +# * domain/metadomain_comm.cpp +# * domain/metadomain_chckpt.cpp +# * domain/metadomain_stats.cpp +# * domain/metadomain_io.cpp +# * domain/metadomain_reshape.cpp # * containers/particles.cpp +# * containers/particles_comm.cpp +# * containers/particles_io.cpp +# * containers/particles_sort.cpp # * containers/fields.cpp -# * domain/stats.cpp -# * domain/output.cpp # # @includes: # @@ -36,17 +46,25 @@ set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(SOURCES - ${SRC_DIR}/parameters.cpp ${SRC_DIR}/simulation.cpp + ${SRC_DIR}/parameters/parameters.cpp + ${SRC_DIR}/parameters/particles.cpp + ${SRC_DIR}/parameters/grid.cpp + ${SRC_DIR}/parameters/output.cpp + ${SRC_DIR}/parameters/algorithms.cpp + ${SRC_DIR}/parameters/extra.cpp ${SRC_DIR}/domain/grid.cpp ${SRC_DIR}/domain/metadomain.cpp - ${SRC_DIR}/domain/communications.cpp - ${SRC_DIR}/domain/stats.cpp + ${SRC_DIR}/domain/metadomain_comm.cpp + ${SRC_DIR}/domain/metadomain_sort.cpp + ${SRC_DIR}/domain/metadomain_stats.cpp + ${SRC_DIR}/domain/metadomain_reshape.cpp ${SRC_DIR}/containers/particles.cpp + ${SRC_DIR}/containers/particles_sort.cpp ${SRC_DIR}/containers/fields.cpp) if(${output}) - list(APPEND SOURCES ${SRC_DIR}/domain/output.cpp) - list(APPEND SOURCES ${SRC_DIR}/domain/checkpoint.cpp) + list(APPEND SOURCES ${SRC_DIR}/domain/metadomain_io.cpp) + list(APPEND SOURCES ${SRC_DIR}/domain/metadomain_chckpt.cpp) list(APPEND SOURCES ${SRC_DIR}/containers/fields_io.cpp) list(APPEND SOURCES ${SRC_DIR}/containers/particles_io.cpp) endif() @@ -58,7 +76,9 @@ add_library(ntt_framework ${SOURCES}) set(libs ntt_global ntt_metrics ntt_kernels ntt_output) add_dependencies(ntt_framework ${libs}) target_link_libraries(ntt_framework PUBLIC ${libs}) -target_link_libraries(ntt_framework PRIVATE stdc++fs) +if(NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang") + target_link_libraries(ntt_framework PRIVATE stdc++fs) +endif() target_include_directories( ntt_framework diff --git a/src/framework/containers/fields.cpp b/src/framework/containers/fields.cpp index 7202ff282..6d379ee34 100644 --- a/src/framework/containers/fields.cpp +++ b/src/framework/containers/fields.cpp @@ -46,10 +46,10 @@ namespace ntt { } } - template struct Fields; - template struct Fields; - template struct Fields; - template struct Fields; - template struct Fields; + template struct Fields; + template struct Fields; + template struct Fields; + template struct Fields; + template struct Fields; } // namespace ntt diff --git a/src/framework/containers/fields_io.cpp b/src/framework/containers/fields_io.cpp index 8c7029a7e..683519493 100644 --- a/src/framework/containers/fields_io.cpp +++ b/src/framework/containers/fields_io.cpp @@ -9,10 +9,7 @@ #include -#if defined(MPI_ENABLED) - #include -#endif - +#include #include namespace ntt { @@ -25,9 +22,9 @@ namespace ntt { const std::vector& local_offset) const { logger::Checkpoint("Declaring fields checkpoint", HERE); - auto gs6 = std::vector(global_shape.begin(), global_shape.end()); - auto lo6 = std::vector(local_offset.begin(), local_offset.end()); - auto ls6 = std::vector(local_shape.begin(), local_shape.end()); + auto gs6 = std::vector(global_shape.begin(), global_shape.end()); + auto lo6 = std::vector(local_offset.begin(), local_offset.end()); + auto ls6 = std::vector(local_shape.begin(), local_shape.end()); gs6.push_back(6); lo6.push_back(0); ls6.push_back(6); @@ -35,9 +32,9 @@ namespace ntt { io.DefineVariable("em", gs6, lo6, ls6); if (S == ntt::SimEngine::GRPIC) { io.DefineVariable("em0", gs6, lo6, ls6); - auto gs3 = std::vector(global_shape.begin(), global_shape.end()); - auto lo3 = std::vector(local_offset.begin(), local_offset.end()); - auto ls3 = std::vector(local_shape.begin(), local_shape.end()); + auto gs3 = std::vector(global_shape.begin(), global_shape.end()); + auto lo3 = std::vector(local_offset.begin(), local_offset.end()); + auto ls3 = std::vector(local_shape.begin(), local_shape.end()); gs3.push_back(3); lo3.push_back(0); ls3.push_back(3); diff --git a/src/framework/containers/particles.cpp b/src/framework/containers/particles.cpp index e9e516221..f1e67acdd 100644 --- a/src/framework/containers/particles.cpp +++ b/src/framework/containers/particles.cpp @@ -12,32 +12,37 @@ #include #include -#include namespace ntt { + template - Particles::Particles(spidx_t index, - const std::string& label, - float m, - float ch, - npart_t maxnpart, - const PrtlPusher& pusher, - bool use_tracking, - bool use_gca, - const Cooling& cooling, - unsigned short npld_r, - unsigned short npld_i) - : ParticleSpecies(index, - label, - m, - ch, - maxnpart, - pusher, - use_tracking, - use_gca, - cooling, - npld_r, - npld_i) { + Particles::Particles(spidx_t index, + const std::string& label, + float m, + float ch, + npart_t maxnpart, + timestep_t clearing_interval, + timestep_t spatial_sorting_interval, + ParticlePusherFlags particle_pusher_flags, + bool use_tracking, + RadiativeDragFlags radiative_drag_flags, + EmissionTypeFlag emission_policy_flag, + unsigned short npld_r, + unsigned short npld_i) + : ParticleArrays { index } + , ParticleSpecies { index, + label, + m, + ch, + maxnpart, + clearing_interval, + spatial_sorting_interval, + particle_pusher_flags, + use_tracking, + radiative_drag_flags, + emission_policy_flag, + npld_r, + npld_i } { if constexpr (D == Dim::_1D or D == Dim::_2D or D == Dim::_3D) { i1 = array_t { label + "_i1", maxnpart }; @@ -75,182 +80,17 @@ namespace ntt { pld_i = array_t { label + "_pld_i", maxnpart, npld_i }; } - if ((D == Dim::_2D) && (C != Coord::Cart)) { + if ((D == Dim::_2D) && (C != Coord::Cartesian)) { phi = array_t { label + "_phi", maxnpart }; } } - template - auto Particles::NpartsPerTagAndOffsets() const - -> std::pair, array_t> { - auto this_tag = tag; - const auto num_tags = ntags(); - array_t npptag { "nparts_per_tag", ntags() }; - - // count # of particles per each tag - auto npptag_scat = Kokkos::Experimental::create_scatter_view(npptag); - Kokkos::parallel_for( - "NpartPerTag", - rangeActiveParticles(), - Lambda(index_t p) { - auto npptag_acc = npptag_scat.access(); - if (this_tag(p) < 0 || this_tag(p) >= static_cast(num_tags)) { - raise::KernelError(HERE, "Invalid tag value"); - } - npptag_acc(this_tag(p)) += 1; - }); - Kokkos::Experimental::contribute(npptag, npptag_scat); - - // copy the count to a vector on the host - auto npptag_h = Kokkos::create_mirror_view(npptag); - Kokkos::deep_copy(npptag_h, npptag); - std::vector npptag_vec(num_tags); - for (auto t { 0u }; t < num_tags; ++t) { - npptag_vec[t] = npptag_h(t); - } - - // count the offsets on the host and copy to device - array_t tag_offsets("tag_offsets", num_tags - 3); - auto tag_offsets_h = Kokkos::create_mirror_view(tag_offsets); - - tag_offsets_h(0) = npptag_vec[2]; // offset for tag = 3 - for (auto t { 1u }; t < num_tags - 3; ++t) { - tag_offsets_h(t) = npptag_vec[t + 2] + tag_offsets_h(t - 1); - } - Kokkos::deep_copy(tag_offsets, tag_offsets_h); - - return { npptag_vec, tag_offsets }; - } - - template - void RemoveDeadInArray(array_t& arr, const array_t& indices_alive) { - npart_t n_alive = indices_alive.extent(0); - auto buffer = Kokkos::View("buffer", n_alive); - Kokkos::parallel_for( - "PopulateBufferAlive", - n_alive, - Lambda(index_t p) { buffer(p) = arr(indices_alive(p)); }); - - Kokkos::deep_copy( - Kokkos::subview(arr, std::make_pair(static_cast(0), n_alive)), - buffer); - } - - template - void RemoveDeadInArray(array_t& arr, const array_t& indices_alive) { - npart_t n_alive = indices_alive.extent(0); - auto buffer = array_t { "buffer", n_alive, arr.extent(1) }; - Kokkos::parallel_for( - "PopulateBufferAlive", - CreateRangePolicy({ 0, 0 }, { n_alive, arr.extent(1) }), - Lambda(index_t p, index_t l) { buffer(p, l) = arr(indices_alive(p), l); }); - - Kokkos::deep_copy( - Kokkos::subview(arr, - std::make_pair(static_cast(0), n_alive), - Kokkos::ALL), - buffer); - } - - template - void Particles::RemoveDead() { - npart_t n_alive = 0, n_dead = 0; - auto& this_tag = tag; - - Kokkos::parallel_reduce( - "CountDeadAlive", - rangeActiveParticles(), - Lambda(index_t p, npart_t & nalive, npart_t & ndead) { - nalive += (this_tag(p) == ParticleTag::alive); - ndead += (this_tag(p) == ParticleTag::dead); - if (this_tag(p) != ParticleTag::alive and this_tag(p) != ParticleTag::dead) { - raise::KernelError(HERE, "wrong particle tag"); - } - }, - n_alive, - n_dead); - - array_t indices_alive { "indices_alive", n_alive }; - array_t alive_counter { "counter_alive", 1 }; - - Kokkos::parallel_for( - "AliveIndices", - rangeActiveParticles(), - Lambda(index_t p) { - if (this_tag(p) == ParticleTag::alive) { - const auto idx = Kokkos::atomic_fetch_add(&alive_counter(0), 1); - indices_alive(idx) = p; - } - }); - - { - auto alive_counter_h = Kokkos::create_mirror_view(alive_counter); - Kokkos::deep_copy(alive_counter_h, alive_counter); - raise::ErrorIf(alive_counter_h(0) != n_alive, - "error in finding alive particle indices", - HERE); - } - - if constexpr (D == Dim::_1D or D == Dim::_2D or D == Dim::_3D) { - RemoveDeadInArray(i1, indices_alive); - RemoveDeadInArray(i1_prev, indices_alive); - RemoveDeadInArray(dx1, indices_alive); - RemoveDeadInArray(dx1_prev, indices_alive); - } - - if constexpr (D == Dim::_2D or D == Dim::_3D) { - RemoveDeadInArray(i2, indices_alive); - RemoveDeadInArray(i2_prev, indices_alive); - RemoveDeadInArray(dx2, indices_alive); - RemoveDeadInArray(dx2_prev, indices_alive); - } - - if constexpr (D == Dim::_3D) { - RemoveDeadInArray(i3, indices_alive); - RemoveDeadInArray(i3_prev, indices_alive); - RemoveDeadInArray(dx3, indices_alive); - RemoveDeadInArray(dx3_prev, indices_alive); - } - - RemoveDeadInArray(ux1, indices_alive); - RemoveDeadInArray(ux2, indices_alive); - RemoveDeadInArray(ux3, indices_alive); - RemoveDeadInArray(weight, indices_alive); - - if constexpr (D == Dim::_2D && C != Coord::Cart) { - RemoveDeadInArray(phi, indices_alive); - } - - if (npld_r() > 0) { - RemoveDeadInArray(pld_r, indices_alive); - } - - if (npld_i() > 0) { - RemoveDeadInArray(pld_i, indices_alive); - } - - Kokkos::Experimental::fill( - "TagAliveParticles", - Kokkos::DefaultExecutionSpace(), - Kokkos::subview(this_tag, std::make_pair(static_cast(0), n_alive)), - ParticleTag::alive); - - Kokkos::Experimental::fill( - "TagDeadParticles", - Kokkos::DefaultExecutionSpace(), - Kokkos::subview(this_tag, std::make_pair(n_alive, n_alive + n_dead)), - ParticleTag::dead); - - set_npart(n_alive); - m_is_sorted = true; - } - - template struct Particles; - template struct Particles; - template struct Particles; - template struct Particles; - template struct Particles; - template struct Particles; - template struct Particles; + template struct Particles; + template struct Particles; + template struct Particles; + template struct Particles; + template struct Particles; + template struct Particles; + template struct Particles; } // namespace ntt diff --git a/src/framework/containers/particles.h b/src/framework/containers/particles.h index 144ca611c..41d96db9a 100644 --- a/src/framework/containers/particles.h +++ b/src/framework/containers/particles.h @@ -2,9 +2,15 @@ * @file framework/containers/particles.h * @brief Definition of the particle container class * @implements - * - ntt::Particles<> : ntt::ParticleSpecies + * - ntt::ParticleArrays + * - ntt::Particles<> : ntt::ParticleSpecies, ntt::ParticleArrays * @cpp: * - particles.cpp + * - particles_io.cpp + * - particles_comm.cpp + * - particles_sort.cpp + * @namespaces: + * - ntt:: * @macros: * - MPI_ENABLED */ @@ -15,12 +21,17 @@ #include "enums.h" #include "global.h" -#include "arch/directions.h" #include "arch/kokkos_aliases.h" +#include "traits/metric.h" #include "utils/error.h" #include "utils/formatting.h" #include "framework/containers/species.h" +#include "framework/domain/grid.h" + +#if defined(MPI_ENABLED) + #include "arch/directions.h" +#endif #include @@ -33,26 +44,11 @@ namespace ntt { - /** - * @brief Container class to carry particle information for a specific species - * @tparam D The dimension of the simulation - * @tparam S The simulation engine being used - */ - template - struct Particles : public ParticleSpecies { - private: - // Number of currently active (used) particles - npart_t m_npart { 0 }; - npart_t m_counter { 0 }; - bool m_is_sorted { false }; + struct ParticleArrays { + const spidx_t sp; -#if !defined(MPI_ENABLED) - const std::size_t m_ntags { 2 }; -#else // MPI_ENABLED - const std::size_t m_ntags { (std::size_t)(2 + math::pow(3, (int)D) - 1) }; -#endif + ParticleArrays(spidx_t sp = 0u) : sp { sp } {} - public: // Cell indices of the current particle array_t i1, i2, i3; // Displacement of a particle within the cell @@ -72,7 +68,29 @@ namespace ntt { array_t pld_i; // phi coordinate (for axisymmetry) array_t phi; + }; + /** + * @brief Container class to carry particle information for a specific species + * @tparam D The dimension of the simulation + * @tparam S The simulation engine being used + */ + template + struct Particles : public ParticleSpecies, + public ParticleArrays { + private: + // Number of currently active (used) particles + npart_t m_npart { 0 }; + npart_t m_counter { 0 }; + bool m_is_sorted { false }; + +#if !defined(MPI_ENABLED) + const uint8_t m_ntags { 2u }; +#else // MPI_ENABLED + const uint8_t m_ntags { (uint8_t)(2 + math::pow(3, (int)D) - 1) }; +#endif + + public: // for empty allocation Particles() {} @@ -83,24 +101,28 @@ namespace ntt { * @param m The mass of the species * @param ch The charge of the species * @param maxnpart The maximum number of allocated particles for the species - * @param pusher The pusher assigned for the species + * @param clearing_interval The interval for clearing the particles + * @param spatial_sorting_interval The interval for spatial sorting of the particles + * @param particle_pusher_flags The pusher(s) assigned for the species * @param use_tracking Use particle tracking for the species - * @param use_gca Use hybrid GCA pusher for the species - * @param cooling The cooling mechanism assigned for the species + * @param radiative_drag_flags The radiative drag mechanism(s) assigned for the species + * @param emission_policy_flag The emission policy assigned for the species * @param npld_r The number of real-valued payloads for the species * @param npld_i The number of integer-valued payloads for the species */ - Particles(spidx_t index, - const std::string& label, - float m, - float ch, - npart_t maxnpart, - const PrtlPusher& pusher, - bool use_gca, - bool use_tracking, - const Cooling& cooling, - unsigned short npld_r = 0, - unsigned short npld_i = 0); + Particles(spidx_t index, + const std::string& label, + float m, + float ch, + npart_t maxnpart, + timestep_t clearing_interval, + timestep_t spatial_sorting_interval, + ParticlePusherFlags particle_pusher_flags, + bool use_tracking, + RadiativeDragFlags radiative_drag_flags, + EmissionTypeFlag emission_policy_flag, + unsigned short npld_r, + unsigned short npld_i); /** * @brief Constructor for the particle container @@ -113,10 +135,12 @@ namespace ntt { spec.mass(), spec.charge(), spec.maxnpart(), + spec.clearing_interval(), + spec.spatial_sorting_interval(), spec.pusher(), spec.use_tracking(), - spec.use_gca(), - spec.cooling(), + spec.radiative_drag_flags(), + spec.emission_policy_flag(), spec.npld_r(), spec.npld_i()) {} @@ -129,16 +153,16 @@ namespace ntt { * @brief Loop over all active particles * @returns A 1D Kokkos range policy of size of `npart` */ - inline auto rangeActiveParticles() const -> range_t { - return CreateParticleRangePolicy(0u, npart()); + auto rangeActiveParticles() const -> range_t { + return CreateParticleRangePolicy({ 0u }, { npart() }); } /** * @brief Loop over all particles * @returns A 1D Kokkos range policy of size of `npart` */ - inline auto rangeAllParticles() const -> range_t { - return CreateParticleRangePolicy(0u, maxnpart()); + auto rangeAllParticles() const -> range_t { + return CreateParticleRangePolicy({ 0u }, { maxnpart() }); } /* getters -------------------------------------------------------------- */ @@ -170,7 +194,7 @@ namespace ntt { * @brief Get the number of distinct tags possible */ [[nodiscard]] - auto ntags() const -> std::size_t { + auto ntags() const -> uint8_t { return m_ntags; } @@ -249,6 +273,12 @@ namespace ntt { */ void RemoveDead(); + /** + * @brief Sort particles spatially by their cell indices + * @param grid The grid object to get the cell information for sorting + */ + void SortSpatially(const Grid&); + /** * @brief Copy particle data from device to host. */ @@ -275,7 +305,7 @@ namespace ntt { #if defined(OUTPUT_ENABLED) void OutputDeclare(adios2::IO&) const; - template + template void OutputWrite(adios2::IO&, adios2::Engine&, npart_t, diff --git a/src/framework/containers/particles_comm.cpp b/src/framework/containers/particles_comm.cpp index 4d6d67118..8fe9d1ec5 100644 --- a/src/framework/containers/particles_comm.cpp +++ b/src/framework/containers/particles_comm.cpp @@ -10,7 +10,6 @@ #include "utils/log.h" #include "framework/containers/particles.h" - #include "kernels/comm.hpp" #include @@ -225,7 +224,7 @@ namespace ntt { // number of arrays of each type to send/recv const unsigned short NREALS = 4 + static_cast( - D == Dim::_2D and C != Coord::Cart); + D == Dim::_2D and C != Coord::Cartesian); const unsigned short NINTS = 2 * static_cast(D); const unsigned short NPRTLDX = 2 * static_cast(D); const unsigned short NPLDS_R = npld_r(); @@ -382,13 +381,13 @@ namespace ntt { const dir::map_t&, \ const dir::map_t&); - PARTICLES_COMM(Dim::_1D, Coord::Cart) - PARTICLES_COMM(Dim::_2D, Coord::Cart) - PARTICLES_COMM(Dim::_3D, Coord::Cart) - PARTICLES_COMM(Dim::_2D, Coord::Sph) - PARTICLES_COMM(Dim::_2D, Coord::Qsph) - PARTICLES_COMM(Dim::_3D, Coord::Sph) - PARTICLES_COMM(Dim::_3D, Coord::Qsph) + PARTICLES_COMM(Dim::_1D, Coord::Cartesian) + PARTICLES_COMM(Dim::_2D, Coord::Cartesian) + PARTICLES_COMM(Dim::_3D, Coord::Cartesian) + PARTICLES_COMM(Dim::_2D, Coord::Spherical) + PARTICLES_COMM(Dim::_2D, Coord::Qspherical) + PARTICLES_COMM(Dim::_3D, Coord::Spherical) + PARTICLES_COMM(Dim::_3D, Coord::Qspherical) #undef PARTICLES_COMM } // namespace ntt diff --git a/src/framework/containers/particles_io.cpp b/src/framework/containers/particles_io.cpp index 8efff59ac..759e3985d 100644 --- a/src/framework/containers/particles_io.cpp +++ b/src/framework/containers/particles_io.cpp @@ -1,39 +1,40 @@ #include "enums.h" #include "global.h" +#include "arch/kokkos_aliases.h" +#include "traits/metric.h" #include "utils/error.h" #include "utils/formatting.h" #include "utils/log.h" -#include "metrics/kerr_schild.h" -#include "metrics/kerr_schild_0.h" -#include "metrics/minkowski.h" -#include "metrics/qkerr_schild.h" -#include "metrics/qspherical.h" -#include "metrics/spherical.h" - #include "framework/containers/particles.h" #include "framework/specialization_registry.h" +#include "kernels/prtls_to_phys.hpp" #include "output/utils/readers.h" #include "output/utils/writers.h" -#include "kernels/prtls_to_phys.hpp" - #include #include #if defined(MPI_ENABLED) + #include "arch/mpi_aliases.h" + #include + + #include #endif +#include + namespace ntt { /* * * * * * * * * * Output * * * * * * * * */ template void Particles::OutputDeclare(adios2::IO& io) const { - const auto n_addition_coords = ((D == Dim::_2D) and (C != Coord::Cart)) ? 1 - : 0; + const auto n_addition_coords = ((D == Dim::_2D) and (C != Coord::Cartesian)) + ? 1 + : 0; for (auto d { 0u }; d < D + n_addition_coords; ++d) { io.DefineVariable(fmt::format("pX%d_%d", d + 1, index()), { adios2::UnknownDim }, @@ -86,7 +87,7 @@ namespace ntt { } template - template + template void Particles::OutputWrite(adios2::IO& io, adios2::Engine& writer, npart_t prtl_stride, @@ -107,7 +108,7 @@ namespace ntt { Kokkos::parallel_reduce( "CountOutputParticles", rangeActiveParticles(), - Lambda(index_t p, npart_t & l_nout) { + Lambda(prtlidx_t p, npart_t & l_nout) { if ((tag_d(p) == ParticleTag::alive) and (pld_i_d(p, pldi::spcCtr) % prtl_stride == 0)) { l_nout += 1; @@ -115,11 +116,11 @@ namespace ntt { }, nout); out_indices = array_t { "out_indices", nout }; - array_t out_counter { "out_counter" }; + const array_t out_counter { "out_counter" }; Kokkos::parallel_for( "RecordOutputIndices", rangeActiveParticles(), - Lambda(index_t p) { + Lambda(prtlidx_t p) { if ((tag_d(p) == ParticleTag::alive) and (pld_i_d(p, pldi::spcCtr) % prtl_stride == 0)) { const auto p_out = Kokkos::atomic_fetch_add(&out_counter(), 1); @@ -128,10 +129,15 @@ namespace ntt { }); } - npart_t nout_offset = 0; - npart_t nout_total = nout; -#if defined(MPI_ENABLED) - auto nout_total_vec = std::vector(domains_total); +#if !defined(MPI_ENABLED) + const npart_t nout_offset = 0; + const npart_t nout_total = nout; + (void)domains_total; + (void)domains_offset; +#else + npart_t nout_offset = 0; + npart_t nout_total = nout; + auto nout_total_vec = std::vector(domains_total); MPI_Allgather(&nout, 1, mpi::get_type(), @@ -140,7 +146,7 @@ namespace ntt { mpi::get_type(), MPI_COMM_WORLD); nout_total = 0; - for (auto r = 0; r < domains_total; ++r) { + for (auto r = 0u; r < domains_total; ++r) { if (r < domains_offset) { nout_offset += nout_total_vec[r]; } @@ -159,7 +165,7 @@ namespace ntt { if constexpr (D == Dim::_2D or D == Dim::_3D) { buff_x2 = array_t { "x2", nout }; } - if constexpr (D == Dim::_3D or ((D == Dim::_2D) and (C != Coord::Cart))) { + if constexpr (D == Dim::_3D or ((D == Dim::_2D) and (C != Coord::Cartesian))) { buff_x3 = array_t { "x3", nout }; } array_t buff_pldr; @@ -255,7 +261,7 @@ namespace ntt { nout_total, nout_offset); } - if constexpr (D == Dim::_3D or ((D == Dim::_2D) and (C != Coord::Cart))) { + if constexpr (D == Dim::_3D or ((D == Dim::_2D) and (C != Coord::Cartesian))) { out::Write1DArray(io, writer, fmt::format("pX3_%d", index()), @@ -379,7 +385,7 @@ namespace ntt { { adios2::UnknownDim }); } - if constexpr (D == Dim::_2D and C != ntt::Coord::Cart) { + if constexpr (D == Dim::_2D and C != ntt::Coord::Cartesian) { io.DefineVariable(fmt::format("s%d_phi", index()), { adios2::UnknownDim }, { adios2::UnknownDim }, @@ -403,9 +409,9 @@ namespace ntt { { adios2::UnknownDim }); if (npld_r() > 0) { io.DefineVariable(fmt::format("s%d_pld_r", index()), - { adios2::UnknownDim, npld_r() }, - { adios2::UnknownDim, 0 }, - { adios2::UnknownDim, npld_r() }); + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); } if (npld_i() > 0) { io.DefineVariable(fmt::format("s%d_pld_i", index()), @@ -426,9 +432,8 @@ namespace ntt { raise::ErrorIf(npart() > 0, "Particles already initialized before reading checkpoint", HERE); - npart_t npart_offset = 0u; - npart_t npart_read; + npart_t npart_read; out::ReadVariable(io, reader, fmt::format("s%d_npart", index()), @@ -436,7 +441,11 @@ namespace ntt { domains_offset); set_npart(npart_read); -#if defined(MPI_ENABLED) +#if !defined(MPI_ENABLED) + const npart_t npart_offset = 0u; + (void)domains_total; +#else + npart_t npart_offset = 0u; { const auto npart_send = npart(); std::vector glob_nparts(domains_total); @@ -539,7 +548,7 @@ namespace ntt { npart_offset); } - if constexpr (D == Dim::_2D and C != Coord::Cart) { + if constexpr (D == Dim::_2D and C != Coord::Cartesian) { out::Read1DArray(io, reader, fmt::format("s%d_phi", index()), @@ -623,7 +632,7 @@ namespace ntt { mpi::get_type(), MPI_COMM_WORLD); npart_total = 0u; - for (auto r = 0; r < domains_total; ++r) { + for (auto r = 0u; r < domains_total; ++r) { if (r < domains_offset) { npart_offset += glob_nparts[r]; } @@ -641,7 +650,7 @@ namespace ntt { out::WriteVariable(io, writer, fmt::format("s%d_counter", index()), - npart(), + counter(), domains_total, domains_offset); @@ -738,7 +747,7 @@ namespace ntt { npart_offset); } - if constexpr (D == Dim::_2D and C != Coord::Cart) { + if constexpr (D == Dim::_2D and C != Coord::Cartesian) { out::Write1DArray(io, writer, fmt::format("s%d_phi", index()), @@ -806,14 +815,17 @@ namespace ntt { } } + // NOLINTBEGIN(bugprone-macro-parentheses) #define PARTICLES_OUTPUT_DECLARE(D, C) \ template void Particles::OutputDeclare(adios2::IO&) const; - PARTICLES_OUTPUT_DECLARE(Dim::_1D, Coord::Cart) - PARTICLES_OUTPUT_DECLARE(Dim::_2D, Coord::Cart) - PARTICLES_OUTPUT_DECLARE(Dim::_3D, Coord::Cart) - PARTICLES_OUTPUT_DECLARE(Dim::_2D, Coord::Sph) - PARTICLES_OUTPUT_DECLARE(Dim::_2D, Coord::Qsph) + PARTICLES_OUTPUT_DECLARE(Dim::_1D, Coord::Cartesian) + PARTICLES_OUTPUT_DECLARE(Dim::_2D, Coord::Cartesian) + PARTICLES_OUTPUT_DECLARE(Dim::_3D, Coord::Cartesian) + PARTICLES_OUTPUT_DECLARE(Dim::_2D, Coord::Spherical) + PARTICLES_OUTPUT_DECLARE(Dim::_2D, Coord::Qspherical) + PARTICLES_OUTPUT_DECLARE(Dim::_3D, Coord::Spherical) + PARTICLES_OUTPUT_DECLARE(Dim::_3D, Coord::Qspherical) #undef PARTICLES_OUTPUT_DECLARE #define PARTICLES_OUTPUT_WRITE(S, M, D) \ @@ -839,13 +851,14 @@ namespace ntt { std::size_t, \ std::size_t) const; - PARTICLES_CHECKPOINTS(Dim::_1D, Coord::Cart) - PARTICLES_CHECKPOINTS(Dim::_2D, Coord::Cart) - PARTICLES_CHECKPOINTS(Dim::_3D, Coord::Cart) - PARTICLES_CHECKPOINTS(Dim::_2D, Coord::Sph) - PARTICLES_CHECKPOINTS(Dim::_2D, Coord::Qsph) - PARTICLES_CHECKPOINTS(Dim::_3D, Coord::Sph) - PARTICLES_CHECKPOINTS(Dim::_3D, Coord::Qsph) + PARTICLES_CHECKPOINTS(Dim::_1D, Coord::Cartesian) + PARTICLES_CHECKPOINTS(Dim::_2D, Coord::Cartesian) + PARTICLES_CHECKPOINTS(Dim::_3D, Coord::Cartesian) + PARTICLES_CHECKPOINTS(Dim::_2D, Coord::Spherical) + PARTICLES_CHECKPOINTS(Dim::_2D, Coord::Qspherical) + PARTICLES_CHECKPOINTS(Dim::_3D, Coord::Spherical) + PARTICLES_CHECKPOINTS(Dim::_3D, Coord::Qspherical) #undef PARTICLES_CHECKPOINTS + // NOLINTEND(bugprone-macro-parentheses) } // namespace ntt diff --git a/src/framework/containers/particles_sort.cpp b/src/framework/containers/particles_sort.cpp new file mode 100644 index 000000000..8741c966a --- /dev/null +++ b/src/framework/containers/particles_sort.cpp @@ -0,0 +1,264 @@ +#include "enums.h" +#include "global.h" + +#include "arch/kokkos_aliases.h" +#include "utils/error.h" +#include "utils/sorting.h" + +#include "framework/containers/particles.h" +#include "framework/domain/grid.h" + +#include +#include +#include + +#include +#include +#include + +namespace ntt { + + template + auto Particles::NpartsPerTagAndOffsets() const + -> std::pair, array_t> { + auto this_tag = tag; + const auto num_tags = ntags(); + array_t npptag { "nparts_per_tag", ntags() }; + + // count # of particles per each tag + auto npptag_scat = Kokkos::Experimental::create_scatter_view(npptag); + Kokkos::parallel_for( + "NpartPerTag", + rangeActiveParticles(), + Lambda(prtlidx_t p) { + auto npptag_acc = npptag_scat.access(); + if (this_tag(p) < 0 || this_tag(p) >= static_cast(num_tags)) { + raise::KernelError(HERE, "Invalid tag value"); + } + npptag_acc(this_tag(p)) += 1; + }); + Kokkos::Experimental::contribute(npptag, npptag_scat); + + // copy the count to a vector on the host + auto npptag_h = Kokkos::create_mirror_view(npptag); + Kokkos::deep_copy(npptag_h, npptag); + std::vector npptag_vec(num_tags); + for (auto t { 0u }; t < num_tags; ++t) { + npptag_vec[t] = npptag_h(t); + } + + // count the offsets on the host and copy to device + const array_t tag_offsets("tag_offsets", num_tags - 3); + auto tag_offsets_h = Kokkos::create_mirror_view(tag_offsets); + + tag_offsets_h(0) = npptag_vec[2]; // offset for tag = 3 + for (auto t { 1u }; t < num_tags - 3; ++t) { + tag_offsets_h(t) = npptag_vec[t + 2] + tag_offsets_h(t - 1); + } + Kokkos::deep_copy(tag_offsets, tag_offsets_h); + + return { npptag_vec, tag_offsets }; + } + + template + void RemoveDeadInArray(array_t& arr, const array_t& indices_alive) { + const npart_t n_alive = indices_alive.extent(0); + auto buffer = Kokkos::View("buffer", n_alive); + Kokkos::parallel_for( + "PopulateBufferAlive", + n_alive, + Lambda(prtlidx_t p) { buffer(p) = arr(indices_alive(p)); }); + + Kokkos::deep_copy( + Kokkos::subview(arr, std::make_pair(static_cast(0), n_alive)), + buffer); + } + + template + void RemoveDeadInArray(array_t& arr, const array_t& indices_alive) { + const npart_t n_alive = indices_alive.extent(0); + auto buffer = array_t { "buffer", n_alive, arr.extent(1) }; + Kokkos::parallel_for( + "PopulateBufferAlive", + CreateParticleRangePolicy( + { 0, 0 }, + { n_alive, static_cast(arr.extent(1)) }), + Lambda(prtlidx_t p, prtlidx_t l) { + buffer(p, l) = arr(indices_alive(p), l); + }); + + Kokkos::deep_copy( + Kokkos::subview(arr, + std::make_pair(static_cast(0), n_alive), + Kokkos::ALL), + buffer); + } + + template + void Particles::RemoveDead() { + npart_t n_alive = 0, n_dead = 0; + auto& this_tag = tag; + + Kokkos::parallel_reduce( + "CountDeadAlive", + rangeActiveParticles(), + Lambda(prtlidx_t p, npart_t & nalive, npart_t & ndead) { + nalive += (this_tag(p) == ParticleTag::alive); + ndead += (this_tag(p) == ParticleTag::dead); + if (this_tag(p) != ParticleTag::alive and this_tag(p) != ParticleTag::dead) { + raise::KernelError(HERE, "wrong particle tag"); + } + }, + n_alive, + n_dead); + + const array_t indices_alive { "indices_alive", n_alive }; + const array_t alive_counter { "counter_alive", 1 }; + + Kokkos::parallel_for( + "AliveIndices", + rangeActiveParticles(), + Lambda(prtlidx_t p) { + if (this_tag(p) == ParticleTag::alive) { + const auto idx = Kokkos::atomic_fetch_add(&alive_counter(0), 1); + indices_alive(idx) = p; + } + }); + + { + auto alive_counter_h = Kokkos::create_mirror_view(alive_counter); + Kokkos::deep_copy(alive_counter_h, alive_counter); + raise::ErrorIf(alive_counter_h(0) != n_alive, + "error in finding alive particle indices", + HERE); + } + + if constexpr (D == Dim::_1D or D == Dim::_2D or D == Dim::_3D) { + RemoveDeadInArray(i1, indices_alive); + RemoveDeadInArray(i1_prev, indices_alive); + RemoveDeadInArray(dx1, indices_alive); + RemoveDeadInArray(dx1_prev, indices_alive); + } + + if constexpr (D == Dim::_2D or D == Dim::_3D) { + RemoveDeadInArray(i2, indices_alive); + RemoveDeadInArray(i2_prev, indices_alive); + RemoveDeadInArray(dx2, indices_alive); + RemoveDeadInArray(dx2_prev, indices_alive); + } + + if constexpr (D == Dim::_3D) { + RemoveDeadInArray(i3, indices_alive); + RemoveDeadInArray(i3_prev, indices_alive); + RemoveDeadInArray(dx3, indices_alive); + RemoveDeadInArray(dx3_prev, indices_alive); + } + + RemoveDeadInArray(ux1, indices_alive); + RemoveDeadInArray(ux2, indices_alive); + RemoveDeadInArray(ux3, indices_alive); + RemoveDeadInArray(weight, indices_alive); + + if constexpr (D == Dim::_2D && C != Coord::Cartesian) { + RemoveDeadInArray(phi, indices_alive); + } + + if (npld_r() > 0) { + RemoveDeadInArray(pld_r, indices_alive); + } + + if (npld_i() > 0) { + RemoveDeadInArray(pld_i, indices_alive); + } + + Kokkos::Experimental::fill( + "TagAliveParticles", + Kokkos::DefaultExecutionSpace(), + Kokkos::subview(this_tag, std::make_pair(static_cast(0), n_alive)), + ParticleTag::alive); + + Kokkos::Experimental::fill( + "TagDeadParticles", + Kokkos::DefaultExecutionSpace(), + Kokkos::subview(this_tag, std::make_pair(n_alive, n_alive + n_dead)), + ParticleTag::dead); + + set_npart(n_alive); + m_is_sorted = true; + } + + template + void Particles::SortSpatially(const Grid& grid) { + const auto nx2 = grid.n_active(in::x2); + const auto nx3 = grid.n_active(in::x3); + const auto total_cells = grid.num_active(); + + array_t cell_indices { "cell_indices", npart() }; + + Kokkos::parallel_for("FillCellIndices", + rangeActiveParticles(), + sort::PositionToTileIndex { i1, + i2, + i3, + tag, + cell_indices, + grid.n_active() }); + const auto slice = prtl_slice_t(0, npart()); + + using sorter_op_t = Kokkos::BinOp1D; + using sorter_t = Kokkos::BinSort; + auto bin_op = sorter_op_t { static_cast(total_cells + 1u), + 0u, + total_cells + 1u }; + auto sorter = sorter_t { cell_indices, bin_op, false }; + sorter.create_permute_vector(); + if constexpr (D == Dim::_1D or D == Dim::_2D or D == Dim::_3D) { + sorter.sort(Kokkos::subview(i1, slice)); + sorter.sort(Kokkos::subview(i1_prev, slice)); + sorter.sort(Kokkos::subview(dx1, slice)); + sorter.sort(Kokkos::subview(dx1_prev, slice)); + } + if constexpr (D == Dim::_2D or D == Dim::_3D) { + sorter.sort(Kokkos::subview(i2, slice)); + sorter.sort(Kokkos::subview(i2_prev, slice)); + sorter.sort(Kokkos::subview(dx2, slice)); + sorter.sort(Kokkos::subview(dx2_prev, slice)); + } + if constexpr (D == Dim::_3D) { + sorter.sort(Kokkos::subview(i3, slice)); + sorter.sort(Kokkos::subview(i3_prev, slice)); + sorter.sort(Kokkos::subview(dx3, slice)); + sorter.sort(Kokkos::subview(dx3_prev, slice)); + } + sorter.sort(Kokkos::subview(ux1, slice)); + sorter.sort(Kokkos::subview(ux2, slice)); + sorter.sort(Kokkos::subview(ux3, slice)); + sorter.sort(Kokkos::subview(weight, slice)); + sorter.sort(Kokkos::subview(tag, slice)); + if constexpr (D == Dim::_2D and C != Coord::Cartesian) { + sorter.sort(Kokkos::subview(phi, slice)); + } + for (auto pldr { 0u }; pldr < npld_r(); ++pldr) { + sorter.sort(Kokkos::subview(pld_r, slice, pldr)); + } + for (auto pldi { 0u }; pldi < npld_i(); ++pldi) { + sorter.sort(Kokkos::subview(pld_i, slice, pldi)); + } + } + +#define PARTICLES_SORT(D, C) \ + template auto Particles::NpartsPerTagAndOffsets() const \ + -> std::pair, array_t>; \ + template void Particles::RemoveDead(); \ + template void Particles::SortSpatially(const Grid&); + + PARTICLES_SORT(Dim::_1D, Coord::Cartesian) + PARTICLES_SORT(Dim::_2D, Coord::Cartesian) + PARTICLES_SORT(Dim::_3D, Coord::Cartesian) + PARTICLES_SORT(Dim::_2D, Coord::Spherical) + PARTICLES_SORT(Dim::_2D, Coord::Qspherical) + PARTICLES_SORT(Dim::_3D, Coord::Spherical) + PARTICLES_SORT(Dim::_3D, Coord::Qspherical) +#undef PARTICLES_SORT + +} // namespace ntt diff --git a/src/framework/containers/species.h b/src/framework/containers/species.h index baf024874..411630da2 100644 --- a/src/framework/containers/species.h +++ b/src/framework/containers/species.h @@ -13,6 +13,9 @@ #include "enums.h" +#include "utils/formatting.h" +#include "utils/reporter.h" + #include namespace ntt { @@ -29,18 +32,22 @@ namespace ntt { const float m_charge; // Max number of allocated particles for the species npart_t m_maxnpart; + // Clearing interval for the species (0 means no clearing) + const timestep_t m_clearing_interval; + // Spatial sorting interval for the species (0 means no sorting) + const timestep_t m_spatial_sorting_interval; // Pusher assigned for the species - const PrtlPusher m_pusher; + const ParticlePusherFlags m_particle_pusher_flags; // Use particle tracking for the species const bool m_use_tracking; - // Use byrid gca pusher for the species - const bool m_use_gca; + // Radiative drag mechanism(s) assigned for the species + const RadiativeDragFlags m_radiative_drag_flags; - // Cooling drag mechanism assigned for the species - const Cooling m_cooling; + // Emission policy assigned for the species + const EmissionTypeFlag m_emission_policy_flag; // Number of payloads for the species const unsigned short m_npld_r; @@ -49,14 +56,15 @@ namespace ntt { public: ParticleSpecies() : m_index { 0u } - , m_label { "" } , m_mass { 0.0 } , m_charge { 0.0 } , m_maxnpart { 0 } - , m_pusher { PrtlPusher::INVALID } + , m_clearing_interval { 0u } + , m_spatial_sorting_interval { 0u } + , m_particle_pusher_flags { ParticlePusher::NONE } , m_use_tracking { false } - , m_use_gca { false } - , m_cooling { Cooling::INVALID } + , m_radiative_drag_flags { RadiativeDrag::NONE } + , m_emission_policy_flag { EmissionType::NONE } , m_npld_r { 0 } , m_npld_i { 0 } {} @@ -68,33 +76,37 @@ namespace ntt { * @param m The mass of the species. * @param ch The charge of the species. * @param maxnpart The maximum number of allocated particles for the species. - * @param pusher The pusher assigned for the species. + * @param particle_pusher_flags The pusher(s) assigned for the species. * @param use_tracking Use particle tracking for the species. - * @param use_gca Use hybrid GCA pusher for the species. - * @param cooling The cooling mechanism assigned for the species. + * @param radiative_drag_flags The radiative drag mechanism(s) assigned for the species. + * @param emission_policy_flag The emission policy assigned for the species. * @param npld_r The number of real-valued payloads for the species * @param npld_i The number of integer-valued payloads for the species */ - ParticleSpecies(spidx_t index, - const std::string& label, - float m, - float ch, - npart_t maxnpart, - const PrtlPusher& pusher, - bool use_tracking, - bool use_gca, - const Cooling& cooling, - unsigned short npld_r = 0, - unsigned short npld_i = 0) + ParticleSpecies(spidx_t index, + const std::string& label, + float m, + float ch, + npart_t maxnpart, + timestep_t clearing_interval, + timestep_t spatial_sorting_interval, + ParticlePusherFlags particle_pusher_flags, + bool use_tracking, + RadiativeDragFlags radiative_drag_flags, + EmissionTypeFlag emission_policy_flag, + unsigned short npld_r, + unsigned short npld_i) : m_index { index } - , m_label { std::move(label) } + , m_label { label } , m_mass { m } , m_charge { ch } , m_maxnpart { maxnpart } - , m_pusher { pusher } + , m_clearing_interval { clearing_interval } + , m_spatial_sorting_interval { spatial_sorting_interval } + , m_particle_pusher_flags { particle_pusher_flags } , m_use_tracking { use_tracking } - , m_use_gca { use_gca } - , m_cooling { cooling } + , m_radiative_drag_flags { radiative_drag_flags } + , m_emission_policy_flag { emission_policy_flag } , m_npld_r { npld_r } , m_npld_i { npld_i } { if (use_tracking) { @@ -144,8 +156,18 @@ namespace ntt { } [[nodiscard]] - auto pusher() const -> PrtlPusher { - return m_pusher; + auto clearing_interval() const -> timestep_t { + return m_clearing_interval; + } + + [[nodiscard]] + auto spatial_sorting_interval() const -> timestep_t { + return m_spatial_sorting_interval; + } + + [[nodiscard]] + auto pusher() const -> ParticlePusherFlags { + return m_particle_pusher_flags; } [[nodiscard]] @@ -154,13 +176,13 @@ namespace ntt { } [[nodiscard]] - auto use_gca() const -> bool { - return m_use_gca; + auto radiative_drag_flags() const -> RadiativeDragFlags { + return m_radiative_drag_flags; } [[nodiscard]] - auto cooling() const -> Cooling { - return m_cooling; + auto emission_policy_flag() const -> EmissionTypeFlag { + return m_emission_policy_flag; } [[nodiscard]] @@ -172,6 +194,51 @@ namespace ntt { auto npld_i() const -> unsigned short { return m_npld_i; } + + /* reporter -------------------------------------------------------------- */ + auto Report() const -> std::string { + std::string report; + reporter::AddSubcategory(report, + 4, + fmt::format("Species #%d", index()).c_str()); + reporter::AddParam(report, 6, "Label", "%s", label().c_str()); + reporter::AddParam(report, 6, "Mass", "%.1f", mass()); + reporter::AddParam(report, 6, "Charge", "%.1f", charge()); + reporter::AddParam(report, 6, "Max #", "%d [per domain]", maxnpart()); + reporter::AddParam(report, + 6, + "Clearing interval", + "%s", + clearing_interval() == 0u + ? "OFF" + : fmt::format("%d", clearing_interval()).c_str()); + reporter::AddParam(report, + 6, + "Spatial sorting interval", + "%s", + spatial_sorting_interval() == 0u + ? "OFF" + : fmt::format("%d", spatial_sorting_interval()).c_str()); + reporter::AddParam(report, + 6, + "Pusher", + "%s", + ParticlePusher::to_string(pusher()).c_str()); + reporter::AddParam(report, + 6, + "Radiative drag", + "%s", + RadiativeDrag::to_string(radiative_drag_flags()).c_str()); + reporter::AddParam(report, + 6, + "Emission policy", + "%s", + EmissionType::to_string(emission_policy_flag()).c_str()); + reporter::AddParam(report, 6, "Tracking", "%s", use_tracking() ? "ON" : "OFF"); + reporter::AddParam(report, 6, "# of real-value payloads", "%d", npld_r()); + reporter::AddParam(report, 6, "# of integer-value payloads", "%d", npld_i()); + return report; + } }; } // namespace ntt diff --git a/src/framework/domain/checkpoint.cpp b/src/framework/domain/checkpoint.cpp deleted file mode 100644 index cc4eac43e..000000000 --- a/src/framework/domain/checkpoint.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include "output/checkpoint.h" - -#include "enums.h" -#include "global.h" - -#include "utils/error.h" -#include "utils/formatting.h" -#include "utils/log.h" - -#include "framework/domain/metadomain.h" -#include "framework/parameters.h" -#include "framework/specialization_registry.h" - -namespace ntt { - - template - void Metadomain::InitCheckpointWriter(adios2::ADIOS* ptr_adios, - const SimulationParams& params) { - raise::ErrorIf(ptr_adios == nullptr, "adios == nullptr", HERE); - raise::ErrorIf( - l_subdomain_indices().size() != 1, - "Checkpoint writing for now is only supported for one subdomain per rank", - HERE); - auto local_domain = subdomain_ptr(l_subdomain_indices()[0]); - raise::ErrorIf(local_domain->is_placeholder(), - "local_domain is a placeholder", - HERE); - - std::vector glob_shape_with_ghosts, off_ncells_with_ghosts; - for (auto d { 0u }; d < M::Dim; ++d) { - off_ncells_with_ghosts.push_back( - local_domain->offset_ncells()[d] + - 2 * N_GHOSTS * local_domain->offset_ndomains()[d]); - glob_shape_with_ghosts.push_back( - mesh().n_active()[d] + 2 * N_GHOSTS * ndomains_per_dim()[d]); - } - auto loc_shape_with_ghosts = local_domain->mesh.n_all(); - - std::vector npld_r, npld_i; - for (auto s { 0u }; s < local_domain->species.size(); ++s) { - npld_r.push_back(local_domain->species[s].npld_r()); - npld_i.push_back(local_domain->species[s].npld_i()); - } - - const path_t checkpoint_root = params.template get( - "checkpoint.write_path"); - - g_checkpoint_writer.init( - ptr_adios, - checkpoint_root, - params.template get("checkpoint.interval"), - params.template get("checkpoint.interval_time"), - params.template get("checkpoint.keep"), - params.template get("checkpoint.walltime")); - if (g_checkpoint_writer.enabled()) { - local_domain->fields.CheckpointDeclare(g_checkpoint_writer.io(), - loc_shape_with_ghosts, - glob_shape_with_ghosts, - off_ncells_with_ghosts); - for (const auto& species : local_domain->species) { - species.CheckpointDeclare(g_checkpoint_writer.io()); - } - } - } - - template - auto Metadomain::WriteCheckpoint(const SimulationParams& params, - timestep_t current_step, - timestep_t finished_step, - simtime_t current_time, - simtime_t finished_time) -> bool { - raise::ErrorIf( - l_subdomain_indices().size() != 1, - "Checkpointing for now is only supported for one subdomain per rank", - HERE); - if (not g_checkpoint_writer.shouldSave(finished_step, finished_time) or - finished_step <= 1) { - return false; - } - auto local_domain = subdomain_ptr(l_subdomain_indices()[0]); - raise::ErrorIf(local_domain->is_placeholder(), - "local_domain is a placeholder", - HERE); - logger::Checkpoint("Writing checkpoint", HERE); - g_checkpoint_writer.beginSaving(current_step, current_time); - { - if (g_checkpoint_writer.written().empty()) { - raise::Fatal("No checkpoint file to save metadata", HERE); - } - params.saveTOML(g_checkpoint_writer.written().back().second, current_time); - - local_domain->fields.CheckpointWrite(g_checkpoint_writer.io(), - g_checkpoint_writer.writer()); -#if !defined(MPI_ENABLED) - const std::size_t dom_tot = 1, dom_offset = 0; -#else - const std::size_t dom_tot = g_mpi_size, dom_offset = g_mpi_rank; -#endif // MPI_ENABLED - - for (const auto& species : local_domain->species) { - species.CheckpointWrite(g_checkpoint_writer.io(), - g_checkpoint_writer.writer(), - dom_tot, - dom_offset); - } - } - g_checkpoint_writer.endSaving(); - logger::Checkpoint("Checkpoint written", HERE); - return true; - } - - template - void Metadomain::ContinueFromCheckpoint(adios2::ADIOS* ptr_adios, - const SimulationParams& params) { - raise::ErrorIf(ptr_adios == nullptr, "adios == nullptr", HERE); - const path_t checkpoint_root = params.template get( - "checkpoint.read_path"); - const auto fname = checkpoint_root / - fmt::format("step-%08lu.bp", - params.template get( - "checkpoint.start_step")); - - logger::Checkpoint(fmt::format("Reading checkpoint from %s", fname.c_str()), - HERE); - - adios2::IO io = ptr_adios->DeclareIO("Entity::CheckpointRead"); - io.SetEngine("BPFile"); -#if !defined(MPI_ENABLED) - adios2::Engine reader = io.Open(fname, adios2::Mode::Read); -#else - adios2::Engine reader = io.Open(fname, adios2::Mode::Read, MPI_COMM_SELF); -#endif - - reader.BeginStep(); - for (const auto local_domain_idx : l_subdomain_indices()) { - auto local_domain = subdomain_ptr(local_domain_idx); - - adios2::Box range; - for (auto d { 0u }; d < M::Dim; ++d) { - range.first.push_back(local_domain->offset_ncells()[d] + - 2 * N_GHOSTS * local_domain->offset_ndomains()[d]); - range.second.push_back(local_domain->mesh.n_all()[d]); - } - local_domain->fields.CheckpointRead(io, reader, range); - - for (auto& species : local_domain->species) { - species.CheckpointRead(io, reader, ndomains(), local_domain_idx); - } - - } // local subdomain loop - - reader.EndStep(); - reader.Close(); - - logger::Checkpoint( - fmt::format("Checkpoint reading done from %s", fname.c_str()), - HERE); - } - -#define METADOMAIN_CHECKPOINTS(S, M, D) \ - template void Metadomain>::InitCheckpointWriter( \ - adios2::ADIOS*, \ - const SimulationParams&); \ - template auto Metadomain>::WriteCheckpoint(const SimulationParams&, \ - timestep_t, \ - timestep_t, \ - simtime_t, \ - simtime_t) -> bool; \ - template void Metadomain>::ContinueFromCheckpoint( \ - adios2::ADIOS*, \ - const SimulationParams&); - NTT_FOREACH_SPECIALIZATION(METADOMAIN_CHECKPOINTS) -#undef METADOMAIN_CHECKPOINTS - -} // namespace ntt diff --git a/src/framework/domain/comm_mpi.hpp b/src/framework/domain/comm_mpi.hpp index 266c844c5..e3598f8b0 100644 --- a/src/framework/domain/comm_mpi.hpp +++ b/src/framework/domain/comm_mpi.hpp @@ -124,17 +124,17 @@ namespace comm { } // namespace flds template - inline void CommunicateField(unsigned int idx, - ndfield_t& fld, - ndfield_t& fld_buff, - unsigned int send_idx, - unsigned int recv_idx, - int send_rank, - int recv_rank, - const std::vector& send_slice, - const std::vector& recv_slice, - const range_tuple_t& comps, - bool additive) { + inline void CommunicateField(unsigned int idx, + ndfield_t& fld, + ndfield_t& fld_buff, + unsigned int send_idx, + unsigned int recv_idx, + int send_rank, + int recv_rank, + const std::vector& send_slice, + const std::vector& recv_slice, + const cell_range_t& comps, + bool additive) { raise::ErrorIf(send_rank < 0 && recv_rank < 0, "CommunicateField called with negative ranks", HERE); @@ -176,7 +176,7 @@ namespace comm { Kokkos::MDRangePolicy, Kokkos::DefaultExecutionSpace>( { recv_slice[0].first, comps.first }, { recv_slice[0].second, comps.second }), - Lambda(index_t i1, index_t ci) { + Lambda(cellidx_t i1, cellidx_t ci) { fld_buff(i1, ci) += fld(i1 - offset_x1, ci); }); } else if constexpr (D == Dim::_2D) { @@ -189,7 +189,7 @@ namespace comm { Kokkos::MDRangePolicy, Kokkos::DefaultExecutionSpace>( { recv_slice[0].first, recv_slice[1].first, comps.first }, { recv_slice[0].second, recv_slice[1].second, comps.second }), - Lambda(index_t i1, index_t i2, index_t ci) { + Lambda(cellidx_t i1, cellidx_t i2, cellidx_t ci) { fld_buff(i1, i2, ci) += fld(i1 - offset_x1, i2 - offset_x2, ci); }); } else if constexpr (D == Dim::_3D) { @@ -210,7 +210,7 @@ namespace comm { recv_slice[1].second, recv_slice[2].second, comps.second }), - Lambda(index_t i1, index_t i2, index_t i3, index_t ci) { + Lambda(cellidx_t i1, cellidx_t i2, cellidx_t i3, cellidx_t ci) { fld_buff(i1, i2, i3, ci) += fld(i1 - offset_x1, i2 - offset_x2, i3 - offset_x3, @@ -305,7 +305,7 @@ namespace comm { Kokkos::MDRangePolicy, Kokkos::DefaultExecutionSpace>( { recv_slice[0].first, comps.first }, { recv_slice[0].second, comps.second }), - Lambda(index_t i1, index_t ci) { + Lambda(cellidx_t i1, cellidx_t ci) { fld_buff(i1, ci) += recv_fld(i1 - offset_x1, ci - offset_c); }); } else if constexpr (D == Dim::_2D) { @@ -317,7 +317,7 @@ namespace comm { Kokkos::MDRangePolicy, Kokkos::DefaultExecutionSpace>( { recv_slice[0].first, recv_slice[1].first, comps.first }, { recv_slice[0].second, recv_slice[1].second, comps.second }), - Lambda(index_t i1, index_t i2, index_t ci) { + Lambda(cellidx_t i1, cellidx_t i2, cellidx_t ci) { fld_buff(i1, i2, ci) += recv_fld(i1 - offset_x1, i2 - offset_x2, ci - offset_c); @@ -338,7 +338,7 @@ namespace comm { recv_slice[1].second, recv_slice[2].second, comps.second }), - Lambda(index_t i1, index_t i2, index_t i3, index_t ci) { + Lambda(cellidx_t i1, cellidx_t i2, cellidx_t i3, cellidx_t ci) { fld_buff(i1, i2, i3, ci) += recv_fld(i1 - offset_x1, i2 - offset_x2, i3 - offset_x3, diff --git a/src/framework/domain/comm_nompi.hpp b/src/framework/domain/comm_nompi.hpp index d0cc36cbd..16e20d261 100644 --- a/src/framework/domain/comm_nompi.hpp +++ b/src/framework/domain/comm_nompi.hpp @@ -26,17 +26,17 @@ namespace comm { * @note: `fld` and `fld_buff` may be the same */ template - inline void CommunicateField(unsigned int idx, - ndfield_t& fld, - ndfield_t& fld_buff, - unsigned int send_idx, - unsigned int recv_idx, - int send_rank, - int recv_rank, - const std::vector& send_slice, - const std::vector& recv_slice, - const range_tuple_t& comps, - bool additive) { + inline void CommunicateField(unsigned int idx, + ndfield_t& fld, + ndfield_t& fld_buff, + unsigned int send_idx, + unsigned int recv_idx, + int send_rank, + int recv_rank, + const std::vector& send_slice, + const std::vector& recv_slice, + const cell_range_t& comps, + bool additive) { raise::ErrorIf(send_rank < 0 && recv_rank < 0, "CommunicateField called with negative ranks", HERE); @@ -71,7 +71,7 @@ namespace comm { Kokkos::MDRangePolicy, Kokkos::DefaultExecutionSpace>( { recv_slice[0].first, comps.first }, { recv_slice[0].second, comps.second }), - Lambda(index_t i1, index_t ci) { + Lambda(cellidx_t i1, cellidx_t ci) { fld_buff(i1, ci) += fld(i1 - offset_x1, ci); }); } else if constexpr (D == Dim::_2D) { @@ -84,7 +84,7 @@ namespace comm { Kokkos::MDRangePolicy, Kokkos::DefaultExecutionSpace>( { recv_slice[0].first, recv_slice[1].first, comps.first }, { recv_slice[0].second, recv_slice[1].second, comps.second }), - Lambda(index_t i1, index_t i2, index_t ci) { + Lambda(cellidx_t i1, cellidx_t i2, cellidx_t ci) { fld_buff(i1, i2, ci) += fld(i1 - offset_x1, i2 - offset_x2, ci); }); } else if constexpr (D == Dim::_3D) { @@ -105,7 +105,7 @@ namespace comm { recv_slice[1].second, recv_slice[2].second, comps.second }), - Lambda(index_t i1, index_t i2, index_t i3, index_t ci) { + Lambda(cellidx_t i1, cellidx_t i2, cellidx_t i3, cellidx_t ci) { fld_buff(i1, i2, i3, ci) += fld(i1 - offset_x1, i2 - offset_x2, i3 - offset_x3, diff --git a/src/framework/domain/domain.h b/src/framework/domain/domain.h index ca59b74e6..747ab4576 100644 --- a/src/framework/domain/domain.h +++ b/src/framework/domain/domain.h @@ -6,6 +6,10 @@ * as well as pointers to neighboring domains. * @implements * - ntt::Domain<> + * @cpp: + * - domain.cpp + * @namespaces: + * - ntt:: * @macros: * - MPI_ENABLED * @note @@ -43,8 +47,10 @@ #include "global.h" #include "arch/directions.h" +#include "traits/metric.h" #include "utils/formatting.h" #include "utils/numeric.h" +#include "utils/reporter.h" #include "framework/containers/fields.h" #include "framework/containers/particles.h" @@ -58,9 +64,9 @@ #include namespace ntt { - template + + template struct Domain { - static_assert(M::is_metric, "template arg for Mesh class has to be a metric"); static constexpr Dimension D { M::Dim }; Mesh mesh; @@ -147,17 +153,79 @@ namespace ntt { [[nodiscard]] auto random_pool() -> random_number_pool_t& { - raise::ErrorIf(not m_random_number_pool.has_value(), - "random_pool() called on a placeholder domain", - HERE); + if (not m_random_number_pool.has_value()) { + raise::Error("random_pool() called on a placeholder domain", HERE); + } return m_random_number_pool.value(); } /* setters -------------------------------------------------------------- */ - auto set_neighbor_idx(const dir::direction_t& dir, unsigned int idx) -> void { + auto set_neighbor_idx(const dir::direction_t& dir, unsigned int idx) + -> void { m_neighbor_idx[dir] = idx; } + /* printer overload ----------------------------------------------------- */ + auto Report() const -> std::string { + std::string report; + reporter::AddSubcategory(report, + 4, + fmt::format("Domain #%d", index()).c_str()); +#if defined(MPI_ENABLED) + reporter::AddParam(report, 6, "Rank", "%d", mpi_rank()); +#endif + reporter::AddParam(report, + 6, + "Resolution", + "%s", + fmt::formatVector(mesh.n_active()).c_str()); + reporter::AddParam(report, + 6, + "Extent", + "%s", + fmt::formatVector(mesh.extent()).c_str()); + reporter::AddSubcategory(report, 6, "Boundary conditions"); + + reporter::AddLabel( + report, + 8 + 2 + 2 * M::Dim, + fmt::format("%-10s %-10s %-10s", "[flds]", "[prtl]", "[neighbor]").c_str()); + for (auto& direction : dir::Directions::all) { + const auto flds_bc = mesh.flds_bc_in(direction); + const auto prtl_bc = mesh.prtl_bc_in(direction); + bool has_sync = false; + auto neighbor_idx = neighbor_idx_in(direction); + if (flds_bc == FldsBC::SYNC || prtl_bc == PrtlBC::SYNC) { + has_sync = true; + } + reporter::AddUnlabeledParam( + report, + 8, + direction.to_string().c_str(), + "%-10s %-10s %-10s", + flds_bc.to_string(), + prtl_bc.to_string(), + has_sync ? std::to_string(neighbor_idx).c_str() : "."); + } + reporter::AddSubcategory(report, 6, "Memory footprint"); + auto flds_footprint = fields.memory_footprint(); + auto [flds_size, flds_unit] = reporter::Bytes2HumanReadable(flds_footprint); + reporter::AddParam(report, 8, "Fields", "%.2f %s", flds_size, flds_unit.c_str()); + if (species.size() > 0) { + reporter::AddSubcategory(report, 8, "Particles"); + } + for (auto& species : species) { + const auto str = fmt::format("Species #%d (%s)", + species.index(), + species.label().c_str()); + auto [size, + unit] = reporter::Bytes2HumanReadable(species.memory_footprint()); + reporter::AddParam(report, 10, str.c_str(), "%.2f %s", size, unit.c_str()); + } + report.pop_back(); + return report; + } + private: // index of the domain in the metadomain unsigned int m_index; @@ -168,15 +236,15 @@ namespace ntt { // neighboring domain indices dir::map_t m_neighbor_idx; // MPI rank of the domain (used only when MPI enabled) - int m_mpi_rank; + int m_mpi_rank { -1 }; // random number pool for the domain std::optional m_random_number_pool; }; - template - inline auto operator<<(std::ostream& os, - const Domain& domain) -> std::ostream& { + template + inline auto operator<<(std::ostream& os, const Domain& domain) + -> std::ostream& { os << "Domain #" << domain.index(); #if defined(MPI_ENABLED) os << " [MPI rank: " << domain.mpi_rank() << "]"; diff --git a/src/framework/domain/grid.cpp b/src/framework/domain/grid.cpp index fa409263b..0b09f98a6 100644 --- a/src/framework/domain/grid.cpp +++ b/src/framework/domain/grid.cpp @@ -1,5 +1,6 @@ #include "framework/domain/grid.h" +#include "enums.h" #include "global.h" #include "arch/kokkos_aliases.h" @@ -9,42 +10,34 @@ namespace ntt { template <> auto Grid::rangeAllCells() const -> range_t { - box_region_t region { CellLayer::allLayer }; - return rangeCells(region); + return rangeCells({ CellLayer::allLayer }); } template <> auto Grid::rangeAllCells() const -> range_t { - box_region_t region { CellLayer::allLayer, CellLayer::allLayer }; - return rangeCells(region); + return rangeCells({ CellLayer::allLayer, CellLayer::allLayer }); } template <> auto Grid::rangeAllCells() const -> range_t { - box_region_t region { CellLayer::allLayer, - CellLayer::allLayer, - CellLayer::allLayer }; - return rangeCells(region); + return rangeCells( + { CellLayer::allLayer, CellLayer::allLayer, CellLayer::allLayer }); } template <> auto Grid::rangeActiveCells() const -> range_t { - box_region_t region { CellLayer::activeLayer }; - return rangeCells(region); + return rangeCells({ CellLayer::activeLayer }); } template <> auto Grid::rangeActiveCells() const -> range_t { - box_region_t region { CellLayer::activeLayer, CellLayer::activeLayer }; - return rangeCells(region); + return rangeCells({ CellLayer::activeLayer, CellLayer::activeLayer }); } template <> auto Grid::rangeActiveCells() const -> range_t { - box_region_t region { CellLayer::activeLayer, - CellLayer::activeLayer, - CellLayer::activeLayer }; - return rangeCells(region); + return rangeCells( + { CellLayer::activeLayer, CellLayer::activeLayer, CellLayer::activeLayer }); } template @@ -85,8 +78,8 @@ namespace ntt { } template - auto Grid::rangeCellsOnHost( - const box_region_t& region) const -> range_h_t { + auto Grid::rangeCellsOnHost(const box_region_t& region) const + -> range_h_t { tuple_t imin, imax; for (auto i { 0u }; i < D; i++) { switch (region[i]) { @@ -123,47 +116,39 @@ namespace ntt { template <> auto Grid::rangeAllCellsOnHost() const -> range_h_t { - box_region_t region { CellLayer::allLayer }; - return rangeCellsOnHost(region); + return rangeCellsOnHost({ CellLayer::allLayer }); } template <> auto Grid::rangeAllCellsOnHost() const -> range_h_t { - box_region_t region { CellLayer::allLayer, CellLayer::allLayer }; - return rangeCellsOnHost(region); + return rangeCellsOnHost({ CellLayer::allLayer, CellLayer::allLayer }); } template <> auto Grid::rangeAllCellsOnHost() const -> range_h_t { - box_region_t region { CellLayer::allLayer, - CellLayer::allLayer, - CellLayer::allLayer }; - return rangeCellsOnHost(region); + return rangeCellsOnHost( + { CellLayer::allLayer, CellLayer::allLayer, CellLayer::allLayer }); } template <> auto Grid::rangeActiveCellsOnHost() const -> range_h_t { - box_region_t region { CellLayer::activeLayer }; - return rangeCellsOnHost(region); + return rangeCellsOnHost({ CellLayer::activeLayer }); } template <> auto Grid::rangeActiveCellsOnHost() const -> range_h_t { - box_region_t region { CellLayer::activeLayer, CellLayer::activeLayer }; - return rangeCellsOnHost(region); + return rangeCellsOnHost({ CellLayer::activeLayer, CellLayer::activeLayer }); } template <> auto Grid::rangeActiveCellsOnHost() const -> range_h_t { - box_region_t region { CellLayer::activeLayer, - CellLayer::activeLayer, - CellLayer::activeLayer }; - return rangeCellsOnHost(region); + return rangeCellsOnHost( + { CellLayer::activeLayer, CellLayer::activeLayer, CellLayer::activeLayer }); } template - auto Grid::rangeCells( - const tuple_t, D>& ranges) const -> range_t { + auto Grid::rangeCells(const tuple_t, D>& ranges) const + -> range_t { tuple_t imin, imax; for (auto i { 0u }; i < D; i++) { raise::ErrorIf((ranges[i][0] < -(int)N_GHOSTS) || @@ -177,6 +162,54 @@ namespace ntt { return CreateRangePolicy(imin, imax); } + template <> + auto Grid::flds_bc() const -> boundaries_t { + return { + { flds_bc_in({ -1 }), flds_bc_in({ 1 }) } + }; + } + + template <> + auto Grid::flds_bc() const -> boundaries_t { + return { + { flds_bc_in({ -1, 0 }), flds_bc_in({ 1, 0 }) }, + { flds_bc_in({ 0, -1 }), flds_bc_in({ 0, 1 }) } + }; + } + + template <> + auto Grid::flds_bc() const -> boundaries_t { + return { + { flds_bc_in({ -1, 0, 0 }), flds_bc_in({ 1, 0, 0 }) }, + { flds_bc_in({ 0, -1, 0 }), flds_bc_in({ 0, 1, 0 }) }, + { flds_bc_in({ 0, 0, -1 }), flds_bc_in({ 0, 0, 1 }) } + }; + } + + template <> + auto Grid::prtl_bc() const -> boundaries_t { + return { + { prtl_bc_in({ -1 }), prtl_bc_in({ 1 }) } + }; + } + + template <> + auto Grid::prtl_bc() const -> boundaries_t { + return { + { prtl_bc_in({ -1, 0 }), prtl_bc_in({ 1, 0 }) }, + { prtl_bc_in({ 0, -1 }), prtl_bc_in({ 0, 1 }) } + }; + } + + template <> + auto Grid::prtl_bc() const -> boundaries_t { + return { + { prtl_bc_in({ -1, 0, 0 }), prtl_bc_in({ 1, 0, 0 }) }, + { prtl_bc_in({ 0, -1, 0 }), prtl_bc_in({ 0, 1, 0 }) }, + { prtl_bc_in({ 0, 0, -1 }), prtl_bc_in({ 0, 0, 1 }) } + }; + } + template struct Grid; template struct Grid; template struct Grid; diff --git a/src/framework/domain/grid.h b/src/framework/domain/grid.h index 87b21b1f5..2e725eabf 100644 --- a/src/framework/domain/grid.h +++ b/src/framework/domain/grid.h @@ -62,10 +62,13 @@ #ifndef FRAMEWORK_DOMAIN_GRID_H #define FRAMEWORK_DOMAIN_GRID_H +#include "enums.h" #include "global.h" +#include "arch/directions.h" #include "arch/kokkos_aliases.h" #include "utils/error.h" +#include "utils/numeric.h" #include @@ -73,10 +76,29 @@ namespace ntt { template struct Grid { - Grid(const std::vector& res) : m_resolution { res } { + Grid(const std::vector& res, const boundaries_t& ext) + : m_resolution { res } + , m_extent { ext } { raise::ErrorIf(m_resolution.size() != D, "invalid dimension", HERE); } + Grid(const std::vector& res, + const boundaries_t& ext, + const boundaries_t& flds_bc, + const boundaries_t& prtl_bc) + : Grid { res, ext } { + for (auto d { 0 }; d < D; ++d) { + dir::direction_t dir_plus; + dir_plus[d] = +1; + dir::direction_t dir_minus; + dir_minus[d] = -1; + set_flds_bc(dir_plus, flds_bc[d].second); + set_flds_bc(dir_minus, flds_bc[d].first); + set_prtl_bc(dir_plus, prtl_bc[d].second); + set_prtl_bc(dir_minus, prtl_bc[d].first); + } + } + ~Grid() = default; /* getters -------------------------------------------------------------- */ @@ -84,7 +106,7 @@ namespace ntt { auto i_min(in i) const -> ncells_t { switch (i) { case in::x1: - return (m_resolution.size() > 0) ? N_GHOSTS : 0; + return (not m_resolution.empty()) ? N_GHOSTS : 0; case in::x2: return (m_resolution.size() > 1) ? N_GHOSTS : 0; case in::x3: @@ -99,7 +121,7 @@ namespace ntt { auto i_max(in i) const -> ncells_t { switch (i) { case in::x1: - return (m_resolution.size() > 0) ? (m_resolution[0] + N_GHOSTS) : 1; + return (not m_resolution.empty()) ? (m_resolution[0] + N_GHOSTS) : 1; case in::x2: return (m_resolution.size() > 1) ? (m_resolution[1] + N_GHOSTS) : 1; case in::x3: @@ -114,7 +136,7 @@ namespace ntt { auto n_active(in i) const -> ncells_t { switch (i) { case in::x1: - return (m_resolution.size() > 0) ? m_resolution[0] : 1; + return (not m_resolution.empty()) ? m_resolution[0] : 1; case in::x2: return (m_resolution.size() > 1) ? m_resolution[1] : 1; case in::x3: @@ -143,7 +165,7 @@ namespace ntt { auto n_all(in i) const -> ncells_t { switch (i) { case in::x1: - return (m_resolution.size() > 0) ? (m_resolution[0] + 2 * N_GHOSTS) : 1; + return (not m_resolution.empty()) ? (m_resolution[0] + 2 * N_GHOSTS) : 1; case in::x2: return (m_resolution.size() > 1) ? (m_resolution[1] + 2 * N_GHOSTS) : 1; case in::x3: @@ -156,9 +178,9 @@ namespace ntt { [[nodiscard]] auto n_all() const -> std::vector { - std::vector nall; + std::vector nall(D); for (auto i = 0u; i < D; ++i) { - nall.push_back(m_resolution[i] + 2 * N_GHOSTS); + nall[i] = m_resolution[i] + 2 * N_GHOSTS; } return nall; } @@ -221,8 +243,67 @@ namespace ntt { */ auto rangeCellsOnHost(const box_region_t&) const -> range_h_t; + /* getters -------------------------------------------------------------- */ + [[nodiscard]] + auto extent(in i) const -> std::pair { + switch (i) { + case in::x1: + return (not m_extent.empty()) + ? m_extent[0] + : std::pair { ZERO, ZERO }; + case in::x2: + return (m_extent.size() > 1) ? m_extent[1] + : std::pair { ZERO, ZERO }; + case in::x3: + return (m_extent.size() > 2) ? m_extent[2] + : std::pair { ZERO, ZERO }; + default: + raise::Error("invalid dimension", HERE); + throw; + } + } + + [[nodiscard]] + auto extent() const -> boundaries_t { + return m_extent; + } + + [[nodiscard]] + auto flds_bc() const -> boundaries_t; + + [[nodiscard]] + auto prtl_bc() const -> boundaries_t; + + [[nodiscard]] + auto flds_bc_in(const dir::direction_t& direction) const -> FldsBC { + raise::ErrorIf(m_flds_bc.find(direction) == m_flds_bc.end(), + "direction not found", + HERE); + return m_flds_bc.at(direction); + } + + [[nodiscard]] + auto prtl_bc_in(const dir::direction_t& direction) const -> PrtlBC { + raise::ErrorIf(m_prtl_bc.find(direction) == m_prtl_bc.end(), + "direction not found", + HERE); + return m_prtl_bc.at(direction); + } + + /* setters -------------------------------------------------------------- */ + void set_flds_bc(const dir::direction_t& direction, const FldsBC& bc) { + m_flds_bc.insert_or_assign(direction, bc); + } + + void set_prtl_bc(const dir::direction_t& direction, const PrtlBC& bc) { + m_prtl_bc.insert_or_assign(direction, bc); + } + protected: std::vector m_resolution; + boundaries_t m_extent; + dir::map_t m_flds_bc; + dir::map_t m_prtl_bc; }; } // namespace ntt diff --git a/src/framework/domain/mesh.h b/src/framework/domain/mesh.h index 89f6a1b9f..aa2eea726 100644 --- a/src/framework/domain/mesh.h +++ b/src/framework/domain/mesh.h @@ -16,7 +16,7 @@ #include "enums.h" #include "global.h" -#include "arch/directions.h" +#include "traits/metric.h" #include "utils/comparators.h" #include "utils/error.h" #include "utils/numeric.h" @@ -30,43 +30,41 @@ namespace ntt { - template + template struct Mesh : public Grid { - static_assert(M::is_metric, "template arg for Mesh class has to be a metric"); - static constexpr bool is_mesh { true }; static constexpr Dimension D { M::Dim }; + using base_t = Grid; + using base_t::extent; + using base_t::m_extent; + using base_t::m_flds_bc; + using base_t::m_prtl_bc; M metric; Mesh(const std::vector& res, const boundaries_t& ext, const std::map& metric_params) - : Grid { res } + : Grid { res, ext } , metric { res, ext, metric_params } - , m_extent { ext } {} + , m_metric_params_raw { metric_params } {} Mesh(const std::vector& res, const boundaries_t& ext, const std::map& metric_params, const boundaries_t& flds_bc, const boundaries_t& prtl_bc) - : Grid { res } + : Grid { res, ext, flds_bc, prtl_bc } , metric { res, ext, metric_params } - , m_extent { ext } { - for (auto d { 0 }; d < D; ++d) { - dir::direction_t dir_plus; - dir_plus[d] = +1; - dir::direction_t dir_minus; - dir_minus[d] = -1; - set_flds_bc(dir_plus, flds_bc[d].second); - set_flds_bc(dir_minus, flds_bc[d].first); - set_prtl_bc(dir_plus, prtl_bc[d].second); - set_prtl_bc(dir_minus, prtl_bc[d].first); - } - } + , m_metric_params_raw { metric_params } {} ~Mesh() = default; + void set_extent(const boundaries_t& new_extent) { + m_extent = new_extent; + metric.~M(); + new (&metric) M { this->m_resolution, new_extent, m_metric_params_raw }; + } + /** * @brief Get the intersection of the mesh with a box * @param box physical extent @@ -74,7 +72,8 @@ namespace ntt { * @note pass Range::All to select the entire dimension */ [[nodiscard]] - auto Intersection(boundaries_t box) const -> boundaries_t { + auto Intersection(const boundaries_t& box) const + -> boundaries_t { raise::ErrorIf(box.size() != M::Dim, "Invalid box dimension", HERE); boundaries_t intersection; auto d = 0; @@ -94,7 +93,7 @@ namespace ntt { } else { x_max = std::max(extent()[d].first, std::min(extent()[d].second, b.second)); - intersection.push_back({ x_min, x_max }); + intersection.emplace_back(x_min, x_max); } } ++d; @@ -109,7 +108,7 @@ namespace ntt { * @note pass Range::All to select the entire dimension */ [[nodiscard]] - auto Intersects(boundaries_t box) const -> bool { + auto Intersects(const boundaries_t& box) const -> bool { raise::ErrorIf(box.size() != M::Dim, "Invalid box dimension", HERE); const auto intersection = Intersection(box); for (const auto& i : intersection) { @@ -131,9 +130,9 @@ namespace ntt { * @note indices are already shifted by N_GHOSTS (i.e. they start at N_GHOSTS not 0) */ [[nodiscard]] - auto ExtentToRange( - boundaries_t box, - boundaries_t incl_ghosts) const -> boundaries_t { + auto ExtentToRange(const boundaries_t& box, + const boundaries_t& incl_ghosts) const + -> boundaries_t { raise::ErrorIf(box.size() != M::Dim, "Invalid box dimension", HERE); raise::ErrorIf(incl_ghosts.size() != M::Dim, "Invalid incl_ghosts dimension", @@ -141,7 +140,7 @@ namespace ntt { boundaries_t range; if (not Intersects(box)) { for (auto i { 0u }; i < box.size(); ++i) { - range.push_back({ 0, 0 }); + range.emplace_back(0, 0); } return range; } @@ -185,115 +184,26 @@ namespace ntt { raise::Error("invalid dimension", HERE); throw; } - range.push_back({ static_cast(xi_min_Cd) + - (incl_ghosts[d].first ? 0 : N_GHOSTS), - static_cast(xi_max_Cd) + - (incl_ghosts[d].second ? 2 * N_GHOSTS : N_GHOSTS) }); + if (!incl_ghosts[d].first) { + xi_min_Cd = std::max(xi_min_Cd, static_cast(ZERO)); + } + if (!incl_ghosts[d].second) { + xi_max_Cd = std::min(xi_max_Cd, + static_cast(this->n_active()[d])); + } + range.emplace_back(static_cast(xi_min_Cd) + + (incl_ghosts[d].first ? 0 : N_GHOSTS), + static_cast(xi_max_Cd) + + (incl_ghosts[d].second ? 2 * N_GHOSTS : N_GHOSTS)); } ++d; } - return range; - } - - /* getters -------------------------------------------------------------- */ - [[nodiscard]] - auto extent(in i) const -> std::pair { - switch (i) { - case in::x1: - return (m_extent.size() > 0) ? m_extent[0] - : std::pair { ZERO, ZERO }; - case in::x2: - return (m_extent.size() > 1) ? m_extent[1] - : std::pair { ZERO, ZERO }; - case in::x3: - return (m_extent.size() > 2) ? m_extent[2] - : std::pair { ZERO, ZERO }; - default: - raise::Error("invalid dimension", HERE); - throw; - } - } - - [[nodiscard]] - auto extent() const -> boundaries_t { - return m_extent; - } - - [[nodiscard]] - auto flds_bc() const -> boundaries_t { - if constexpr (D == Dim::_1D) { - return { - { flds_bc_in({ -1 }), flds_bc_in({ 1 }) } - }; - } else if constexpr (D == Dim::_2D) { - return { - { flds_bc_in({ -1, 0 }), flds_bc_in({ 1, 0 }) }, - { flds_bc_in({ 0, -1 }), flds_bc_in({ 0, 1 }) } - }; - } else if constexpr (D == Dim::_3D) { - return { - { flds_bc_in({ -1, 0, 0 }), flds_bc_in({ 1, 0, 0 }) }, - { flds_bc_in({ 0, -1, 0 }), flds_bc_in({ 0, 1, 0 }) }, - { flds_bc_in({ 0, 0, -1 }), flds_bc_in({ 0, 0, 1 }) } - }; - } else { - raise::Error("invalid dimension", HERE); - throw; - } - } - [[nodiscard]] - auto prtl_bc() const -> boundaries_t { - if constexpr (D == Dim::_1D) { - return { - { prtl_bc_in({ -1 }), prtl_bc_in({ 1 }) } - }; - } else if constexpr (D == Dim::_2D) { - return { - { prtl_bc_in({ -1, 0 }), prtl_bc_in({ 1, 0 }) }, - { prtl_bc_in({ 0, -1 }), prtl_bc_in({ 0, 1 }) } - }; - } else if constexpr (D == Dim::_3D) { - return { - { prtl_bc_in({ -1, 0, 0 }), prtl_bc_in({ 1, 0, 0 }) }, - { prtl_bc_in({ 0, -1, 0 }), prtl_bc_in({ 0, 1, 0 }) }, - { prtl_bc_in({ 0, 0, -1 }), prtl_bc_in({ 0, 0, 1 }) } - }; - } else { - raise::Error("invalid dimension", HERE); - throw; - } - } - - [[nodiscard]] - auto flds_bc_in(const dir::direction_t& direction) const -> FldsBC { - raise::ErrorIf(m_flds_bc.find(direction) == m_flds_bc.end(), - "direction not found", - HERE); - return m_flds_bc.at(direction); - } - - [[nodiscard]] - auto prtl_bc_in(const dir::direction_t& direction) const -> PrtlBC { - raise::ErrorIf(m_prtl_bc.find(direction) == m_prtl_bc.end(), - "direction not found", - HERE); - return m_prtl_bc.at(direction); - } - - /* setters -------------------------------------------------------------- */ - inline void set_flds_bc(const dir::direction_t& direction, const FldsBC& bc) { - m_flds_bc.insert_or_assign(direction, bc); - } - - inline void set_prtl_bc(const dir::direction_t& direction, const PrtlBC& bc) { - m_prtl_bc.insert_or_assign(direction, bc); + return range; } private: - boundaries_t m_extent; - dir::map_t m_flds_bc; - dir::map_t m_prtl_bc; + std::map m_metric_params_raw; }; } // namespace ntt diff --git a/src/framework/domain/metadomain.cpp b/src/framework/domain/metadomain.cpp index cbea40843..b0eacb5dc 100644 --- a/src/framework/domain/metadomain.cpp +++ b/src/framework/domain/metadomain.cpp @@ -7,17 +7,21 @@ #include "arch/mpi_aliases.h" #endif +#include "arch/directions.h" +#include "traits/metric.h" #include "utils/comparators.h" #include "utils/error.h" +#include "utils/numeric.h" #include "utils/tools.h" -#include "framework/domain/domain.h" +#include "framework/containers/species.h" #include "framework/specialization_registry.h" #if defined(MPI_ENABLED) #include #endif +#include #include #include #include @@ -25,7 +29,7 @@ namespace ntt { - template + template Metadomain::Metadomain(unsigned int global_ndomains, const std::vector& global_decomposition, const std::vector& global_ncells, @@ -56,7 +60,7 @@ namespace ntt { metricCompatibilityCheck(); } - template + template void Metadomain::initialValidityCheck() const { // ensure everything has the correct shape raise::ErrorIf(g_decomposition.size() != (std::size_t)D, @@ -93,7 +97,7 @@ namespace ntt { #endif // MPI_ENABLED } - template + template void Metadomain::createEmptyDomains() { /* decompose and compute cell & domain offsets ------------------------ */ auto d_ncells = tools::Decompose(g_ndomains, g_mesh.n_active(), g_decomposition); @@ -188,7 +192,7 @@ namespace ntt { } } - template + template void Metadomain::redefineNeighbors() { for (unsigned int idx { 0 }; idx < g_ndomains; ++idx) { // offset of the subdomain[idx] @@ -228,7 +232,7 @@ namespace ntt { } } - template + template void Metadomain::redefineBoundaries() { for (unsigned int idx { 0 }; idx < g_ndomains; ++idx) { // offset of the subdomain[idx] @@ -277,7 +281,7 @@ namespace ntt { current_domain.mesh.set_prtl_bc(direction, prtl_bc); } // setting boundaries in non-orthogonal (corner) directions - for (auto direction : dir::Directions::all) { + for (const auto& direction : dir::Directions::all) { auto assoc_orth = direction.get_assoc_orth(); if (assoc_orth.size() == 1) { // skip the orthogonal directions @@ -286,7 +290,7 @@ namespace ntt { // if one of the boundaries is not periodic, then use it // otherwise, use periodic FldsBC flds_bc { FldsBC::INVALID }; - for (auto dir : assoc_orth) { + for (const auto& dir : assoc_orth) { const auto fldsbc_in_dir = current_domain.mesh.flds_bc_in(dir); if (fldsbc_in_dir != FldsBC::PERIODIC) { flds_bc = fldsbc_in_dir; @@ -296,7 +300,7 @@ namespace ntt { } } PrtlBC prtl_bc { PrtlBC::INVALID }; - for (auto dir : assoc_orth) { + for (const auto& dir : assoc_orth) { const auto prtlbc_in_dir = current_domain.mesh.prtl_bc_in(dir); if (prtlbc_in_dir != PrtlBC::PERIODIC) { prtl_bc = prtlbc_in_dir; @@ -317,7 +321,7 @@ namespace ntt { } } - template + template void Metadomain::finalValidityCheck() const { for (unsigned int idx { 0 }; idx < g_ndomains; ++idx) { const auto& current_domain = g_subdomains[idx]; @@ -362,7 +366,7 @@ namespace ntt { } } - template + template void Metadomain::metricCompatibilityCheck() const { const auto epsilon = std::numeric_limits::epsilon() * static_cast(100.0); @@ -396,7 +400,7 @@ namespace ntt { #endif } - template + template void Metadomain::setFldsBC(const bc_in& dir, const FldsBC& new_bcs) { if (dir == bc_in::Mx1) { if constexpr (M::Dim == Dim::_1D) { @@ -464,7 +468,7 @@ namespace ntt { redefineBoundaries(); } - template + template void Metadomain::setPrtlBC(const bc_in& dir, const PrtlBC& new_bcs) { if (dir == bc_in::Mx1) { if constexpr (M::Dim == Dim::_1D) { @@ -532,10 +536,10 @@ namespace ntt { redefineBoundaries(); } + // NOLINTBEGIN(bugprone-macro-parentheses) #define METADOMAIN_STRUCT(S, M, D) template struct Metadomain>; - NTT_FOREACH_SPECIALIZATION(METADOMAIN_STRUCT) - #undef METADOMAIN_STRUCT + // NOLINTEND(bugprone-macro-parentheses) } // namespace ntt diff --git a/src/framework/domain/metadomain.h b/src/framework/domain/metadomain.h index a456879b4..2ff5fe40f 100644 --- a/src/framework/domain/metadomain.h +++ b/src/framework/domain/metadomain.h @@ -1,10 +1,16 @@ /** * @file framework/domain/metadomain.h - * @brief ... + * @brief Global metadomain class managing domain decomposition, inter-domain + * communication, I/O, and checkpointing * @implements * - ntt::Metadomain<> * @cpp: * - metadomain.cpp + * - metadomain_comm.cpp + * - metadomain_chckpt.cpp + * - metadomain_io.cpp + * - metadomain_stats.cpp + * - metadomain_reshape.cpp * @namespaces: * - ntt:: * @macros: @@ -19,11 +25,12 @@ #include "global.h" #include "arch/kokkos_aliases.h" +#include "traits/metric.h" #include "framework/containers/species.h" #include "framework/domain/domain.h" #include "framework/domain/mesh.h" -#include "framework/parameters.h" +#include "framework/parameters/parameters.h" #include "output/stats.h" #if defined(MPI_ENABLED) @@ -35,21 +42,20 @@ #include "output/writer.h" #include - #include + #include #endif // OUTPUT_ENABLED #include #include +#include #include #include #include namespace ntt { - template + template struct Metadomain { - static_assert(M::is_metric, - "template arg for Metadomain class has to be a metric"); static constexpr Dimension D { M::Dim }; void initialValidityCheck() const; @@ -86,10 +92,18 @@ namespace ntt { } } - void CommunicateFields(Domain&, CommTags); - void SynchronizeFields(Domain&, CommTags, const range_tuple_t& = { 0, 0 }); - void CommunicateParticles(Domain&); - void RemoveDeadParticles(Domain&); + void CommunicateFields(Domain&, CommTags) const; + void SynchronizeFields(Domain&, + CommTags, + const cell_range_t& = { 0, 0 }) const; +#if defined(MPI_ENABLED) && defined(OUTPUT_ENABLED) + void CommunicateVectorPotential(unsigned short); +#endif + void CommunicateParticles(Domain&) const; + void SortParticles(simtime_t, + timestep_t, + const SimulationParams&, + Domain&) const; /** * @param global_ndomains total number of domains @@ -115,6 +129,11 @@ namespace ntt { ~Metadomain() = default; + /* domain update-related ------------------------------------------------ */ + void ShiftByCells(int, in = in::x1) + requires(CartesianMetricClass); + + /* output-related ------------------------------------------------------- */ #if defined(OUTPUT_ENABLED) void InitWriter(adios2::ADIOS*, const SimulationParams&); auto Write(const SimulationParams&, @@ -122,12 +141,12 @@ namespace ntt { timestep_t, simtime_t, simtime_t, - std::function&, - index_t, - timestep_t, - simtime_t, - const Domain&)> = nullptr) -> bool; + const std::function&, + uint32_t, + timestep_t, + simtime_t, + const Domain&)>& = nullptr) -> bool; void InitCheckpointWriter(adios2::ADIOS*, const SimulationParams&); auto WriteCheckpoint(const SimulationParams&, timestep_t, @@ -136,6 +155,8 @@ namespace ntt { simtime_t) -> bool; void ContinueFromCheckpoint(adios2::ADIOS*, const SimulationParams&); + void redecomposeFromCheckpoint(const std::vector>&, + const std::vector>&); #endif void InitStatsWriter(const SimulationParams&, bool); @@ -145,8 +166,9 @@ namespace ntt { timestep_t, simtime_t, simtime_t, - std::function&)> = - nullptr) -> bool; + const std::function< + real_t(const std::string&, timestep_t, simtime_t, const Domain&)>& = nullptr) + -> bool; /* setters -------------------------------------------------------------- */ void setFldsBC(const bc_in&, const FldsBC&); @@ -175,6 +197,12 @@ namespace ntt { return &g_subdomains[idx]; } + [[nodiscard]] + auto subdomain_ptr(unsigned int idx) const -> const Domain* { + raise::ErrorIf(idx >= g_subdomains.size(), "subdomain_ptr() failed", HERE); + return &g_subdomains[idx]; + } + [[nodiscard]] auto mesh() const -> const Mesh& { return g_mesh; @@ -213,16 +241,16 @@ namespace ntt { } [[nodiscard]] - auto l_npart() const -> std::size_t { + auto l_npart() const -> npart_t { const auto npart = l_npart_perspec(); - return std::accumulate(npart.begin(), npart.end(), 0); + return std::accumulate(npart.begin(), npart.end(), static_cast(0)); } [[nodiscard]] - auto l_ncells() const -> std::size_t { - std::size_t ncells_local = 0; + auto l_ncells() const -> ncells_t { + ncells_t ncells_local = 0; for (const auto& ldidx : l_subdomain_indices()) { - std::size_t ncells = 1; + ncells_t ncells = 1; for (const auto& n : g_subdomains[ldidx].mesh.n_all()) { ncells *= n; } @@ -233,9 +261,9 @@ namespace ntt { [[nodiscard]] auto species_labels() const -> std::vector { - std::vector labels; - for (const auto& sp : g_species_params) { - labels.push_back(sp.label()); + std::vector labels(g_species_params.size()); + for (std::size_t i = 0; i < g_species_params.size(); ++i) { + labels[i] = g_species_params[i].label(); } return labels; } @@ -261,13 +289,10 @@ namespace ntt { #if defined(OUTPUT_ENABLED) out::Writer g_writer; checkpoint::Writer g_checkpoint_writer; - #if defined(MPI_ENABLED) - void CommunicateVectorPotential(unsigned short); - #endif #endif #if defined(MPI_ENABLED) - int g_mpi_rank, g_mpi_size; + int g_mpi_rank { -1 }, g_mpi_size { -1 }; #endif }; diff --git a/src/framework/domain/metadomain_chckpt.cpp b/src/framework/domain/metadomain_chckpt.cpp new file mode 100644 index 000000000..06918a053 --- /dev/null +++ b/src/framework/domain/metadomain_chckpt.cpp @@ -0,0 +1,366 @@ +#include "enums.h" +#include "global.h" + +#include "traits/metric.h" +#include "utils/error.h" +#include "utils/formatting.h" +#include "utils/log.h" + +#include "framework/domain/metadomain.h" +#include "framework/parameters/parameters.h" +#include "framework/specialization_registry.h" +#include "output/checkpoint.h" +#include "output/utils/readers.h" +#include "output/utils/writers.h" + +#if defined(MPI_ENABLED) + #include +#endif + +#include +#include +#include +#include +#include + +namespace ntt { + + template + void Metadomain::InitCheckpointWriter(adios2::ADIOS* ptr_adios, + const SimulationParams& params) { + raise::ErrorIf(ptr_adios == nullptr, "adios == nullptr", HERE); + raise::ErrorIf( + l_subdomain_indices().size() != 1, + "Checkpoint writing for now is only supported for one subdomain per rank", + HERE); + auto local_domain = subdomain_ptr(l_subdomain_indices()[0]); + raise::ErrorIf(local_domain->is_placeholder(), + "local_domain is a placeholder", + HERE); + + std::vector glob_shape_with_ghosts, off_ncells_with_ghosts; + for (auto d { 0u }; d < M::Dim; ++d) { + off_ncells_with_ghosts.push_back( + local_domain->offset_ncells()[d] + + 2 * N_GHOSTS * local_domain->offset_ndomains()[d]); + glob_shape_with_ghosts.push_back( + mesh().n_active()[d] + 2 * N_GHOSTS * ndomains_per_dim()[d]); + } + auto loc_shape_with_ghosts = local_domain->mesh.n_all(); + + std::vector npld_r, npld_i; + for (auto s { 0u }; s < local_domain->species.size(); ++s) { + npld_r.push_back(local_domain->species[s].npld_r()); + npld_i.push_back(local_domain->species[s].npld_i()); + } + + const path_t checkpoint_root = params.template get( + "checkpoint.write_path"); + + g_checkpoint_writer.init( + ptr_adios, + checkpoint_root, + params.template get("checkpoint.interval"), + params.template get("checkpoint.interval_time"), + params.template get("checkpoint.keep"), + params.template get("checkpoint.walltime")); + if (g_checkpoint_writer.enabled()) { + local_domain->fields.CheckpointDeclare(g_checkpoint_writer.io(), + loc_shape_with_ghosts, + glob_shape_with_ghosts, + off_ncells_with_ghosts); + for (const auto& species : local_domain->species) { + species.CheckpointDeclare(g_checkpoint_writer.io()); + } + for (auto d { 0u }; d < M::Dim; ++d) { + g_checkpoint_writer.io().DefineVariable( + fmt::format("subdomain_x%d_min", d + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + g_checkpoint_writer.io().DefineVariable( + fmt::format("subdomain_x%d_max", d + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + g_checkpoint_writer.io().DefineVariable( + fmt::format("subdomain_nx%d", d + 1), + { adios2::UnknownDim }, + { adios2::UnknownDim }, + { adios2::UnknownDim }); + } + } + } + + template + auto Metadomain::WriteCheckpoint(const SimulationParams& params, + timestep_t current_step, + timestep_t finished_step, + simtime_t current_time, + simtime_t finished_time) -> bool { + raise::ErrorIf( + l_subdomain_indices().size() != 1, + "Checkpointing for now is only supported for one subdomain per rank", + HERE); + if (not g_checkpoint_writer.shouldSave(finished_step, finished_time) or + finished_step <= 1) { + return false; + } + auto local_domain = subdomain_ptr(l_subdomain_indices()[0]); + raise::ErrorIf(local_domain->is_placeholder(), + "local_domain is a placeholder", + HERE); + logger::Checkpoint("Writing checkpoint", HERE); + g_checkpoint_writer.beginSaving(current_step, current_time); + { + if (g_checkpoint_writer.written().empty()) { + raise::Fatal("No checkpoint file to save metadata", HERE); + } + params.saveTOML(g_checkpoint_writer.written().back().second, current_time); + + local_domain->fields.CheckpointWrite(g_checkpoint_writer.io(), + g_checkpoint_writer.writer()); +#if !defined(MPI_ENABLED) + const std::size_t dom_tot = 1, dom_offset = 0; +#else + const std::size_t dom_tot = g_mpi_size, dom_offset = g_mpi_rank; +#endif // MPI_ENABLED + + for (const auto& species : local_domain->species) { + species.CheckpointWrite(g_checkpoint_writer.io(), + g_checkpoint_writer.writer(), + dom_tot, + dom_offset); + } + for (auto d { 0u }; d < M::Dim; ++d) { + out::WriteVariable(g_checkpoint_writer.io(), + g_checkpoint_writer.writer(), + fmt::format("subdomain_x%d_min", d + 1), + local_domain->mesh.extent()[d].first, + dom_tot, + dom_offset); + out::WriteVariable(g_checkpoint_writer.io(), + g_checkpoint_writer.writer(), + fmt::format("subdomain_x%d_max", d + 1), + local_domain->mesh.extent()[d].second, + dom_tot, + dom_offset); + out::WriteVariable(g_checkpoint_writer.io(), + g_checkpoint_writer.writer(), + fmt::format("subdomain_nx%d", d + 1), + local_domain->mesh.n_active()[d], + dom_tot, + dom_offset); + } + } + g_checkpoint_writer.endSaving(); + logger::Checkpoint("Checkpoint written", HERE); + return true; + } + + template + void Metadomain::redecomposeFromCheckpoint( + const std::vector>& dom_ncells, + const std::vector>& dom_extents) { + + // For each dimension: collect ncells per domain-grid position, + // validate total is unchanged, then compute prefix-sum offsets. + std::vector> offset_ncells_per_dom( + g_ndomains, + std::vector(M::Dim, 0)); + + for (auto d { 0u }; d < M::Dim; ++d) { + std::vector ncells_at_pos(g_ndomains_per_dim[d], 0); + for (unsigned int idx { 0 }; idx < g_ndomains; ++idx) { + ncells_at_pos[g_domain_offsets[idx][d]] = dom_ncells[idx][d]; + } + + ncells_t total { 0 }; + for (const auto& n : ncells_at_pos) { + total += n; + } + raise::ErrorIf(total != g_mesh.n_active()[d], + fmt::format("total cells in dim %d changed between " + "checkpoint (%lu) and current (%lu); " + "changing total domain size is not supported", + d + 1, + total, + g_mesh.n_active()[d]), + HERE); + + ncells_t running { 0 }; + std::vector offset_at_pos(g_ndomains_per_dim[d], 0); + for (unsigned int nd { 0 }; nd < g_ndomains_per_dim[d]; ++nd) { + offset_at_pos[nd] = running; + running += ncells_at_pos[nd]; + } + for (unsigned int idx { 0 }; idx < g_ndomains; ++idx) { + offset_ncells_per_dom[idx][d] = offset_at_pos[g_domain_offsets[idx][d]]; + } + } + + g_subdomains.clear(); + for (unsigned int idx { 0 }; idx < g_ndomains; ++idx) { + const auto& l_offset_ndomains = g_domain_offsets[idx]; + const auto& l_ncells = dom_ncells[idx]; + const auto& l_offset_ncells = offset_ncells_per_dom[idx]; + const auto& l_extent = dom_extents[idx]; + +#if defined(MPI_ENABLED) + const auto local = ((int)idx == g_mpi_rank); + if (not local) { + g_subdomains.emplace_back(false, + idx, + l_offset_ndomains, + l_offset_ncells, + l_ncells, + l_extent, + g_metric_params, + g_species_params); + } else { + g_subdomains.emplace_back(idx, + l_offset_ndomains, + l_offset_ncells, + l_ncells, + l_extent, + g_metric_params, + g_species_params); + } + g_subdomains.back().set_mpi_rank(idx); +#else + g_subdomains.emplace_back(idx, + l_offset_ndomains, + l_offset_ncells, + l_ncells, + l_extent, + g_metric_params, + g_species_params); +#endif + } + + redefineNeighbors(); + redefineBoundaries(); + } + + template + void Metadomain::ContinueFromCheckpoint(adios2::ADIOS* ptr_adios, + const SimulationParams& params) { + raise::ErrorIf(ptr_adios == nullptr, "adios == nullptr", HERE); + const path_t checkpoint_root = params.template get( + "checkpoint.read_path"); + const auto fname = checkpoint_root / + fmt::format("step-%08lu.bp", + params.template get( + "checkpoint.start_step")); + + logger::Checkpoint(fmt::format("Reading checkpoint from %s", fname.c_str()), + HERE); + + adios2::IO io = ptr_adios->DeclareIO("Entity::CheckpointRead"); + io.SetEngine("BPFile"); +#if !defined(MPI_ENABLED) + adios2::Engine reader = io.Open(fname, adios2::Mode::Read); +#else + adios2::Engine reader = io.Open(fname, adios2::Mode::Read, MPI_COMM_SELF); +#endif + + reader.BeginStep(); + + // Phase 1: read all subdomain metadata to detect size changes + std::vector> saved_ncells(g_ndomains, + std::vector(M::Dim)); + std::vector> saved_extents(g_ndomains); + boundaries_t global_extent; + for (auto d { 0u }; d < M::Dim; ++d) { + global_extent.emplace_back(std::numeric_limits::max(), + std::numeric_limits::lowest()); + } + + bool needs_reconstruction = false; + for (unsigned int dom_idx { 0 }; dom_idx < g_ndomains; ++dom_idx) { + for (auto d { 0u }; d < M::Dim; ++d) { + real_t x_min, x_max; + out::ReadVariable(io, + reader, + fmt::format("subdomain_x%d_min", d + 1), + x_min, + dom_idx); + out::ReadVariable(io, + reader, + fmt::format("subdomain_x%d_max", d + 1), + x_max, + dom_idx); + saved_extents[dom_idx].emplace_back(x_min, x_max); + global_extent[d].first = std::min(global_extent[d].first, x_min); + global_extent[d].second = std::max(global_extent[d].second, x_max); + + ncells_t nx; + out::ReadVariable(io, + reader, + fmt::format("subdomain_nx%d", d + 1), + nx, + dom_idx); + saved_ncells[dom_idx][d] = nx; + + if (nx != subdomain_ptr(dom_idx)->mesh.n_active()[d]) { + needs_reconstruction = true; + } + } + } + + // Phase 2: update domain structure + if (needs_reconstruction) { + redecomposeFromCheckpoint(saved_ncells, saved_extents); + } else { + for (unsigned int dom_idx { 0 }; dom_idx < g_ndomains; ++dom_idx) { + subdomain_ptr(dom_idx)->mesh.set_extent(saved_extents[dom_idx]); + } + } + g_mesh.set_extent(global_extent); + + // Phase 3: read field and particle data using the (now-correct) domain layout + for (const auto local_domain_idx : l_subdomain_indices()) { + auto local_domain = subdomain_ptr(local_domain_idx); + + adios2::Box range; + for (auto d { 0u }; d < M::Dim; ++d) { + range.first.push_back(local_domain->offset_ncells()[d] + + 2 * N_GHOSTS * local_domain->offset_ndomains()[d]); + range.second.push_back(local_domain->mesh.n_all()[d]); + } + local_domain->fields.CheckpointRead(io, reader, range); + + for (auto& species : local_domain->species) { + species.CheckpointRead(io, reader, ndomains(), local_domain_idx); + } + } + + reader.EndStep(); + reader.Close(); + + logger::Checkpoint( + fmt::format("Checkpoint reading done from %s", fname.c_str()), + HERE); + } + + // NOLINTBEGIN(bugprone-macro-parentheses) +#define METADOMAIN_CHECKPOINTS(S, M, D) \ + template void Metadomain>::InitCheckpointWriter( \ + adios2::ADIOS*, \ + const SimulationParams&); \ + template auto Metadomain>::WriteCheckpoint(const SimulationParams&, \ + timestep_t, \ + timestep_t, \ + simtime_t, \ + simtime_t) -> bool; \ + template void Metadomain>::ContinueFromCheckpoint( \ + adios2::ADIOS*, \ + const SimulationParams&); \ + template void Metadomain>::redecomposeFromCheckpoint( \ + const std::vector>&, \ + const std::vector>&); + NTT_FOREACH_SPECIALIZATION(METADOMAIN_CHECKPOINTS) +#undef METADOMAIN_CHECKPOINTS + // NOLINTEND(bugprone-macro-parentheses) + +} // namespace ntt diff --git a/src/framework/domain/communications.cpp b/src/framework/domain/metadomain_comm.cpp similarity index 89% rename from src/framework/domain/communications.cpp rename to src/framework/domain/metadomain_comm.cpp index ae4ec83d1..e492752c4 100644 --- a/src/framework/domain/communications.cpp +++ b/src/framework/domain/metadomain_comm.cpp @@ -2,10 +2,14 @@ #include "global.h" #include "arch/directions.h" +#include "arch/kokkos_aliases.h" +#include "traits/metric.h" #include "utils/error.h" #include "utils/formatting.h" #include "utils/log.h" +#include "utils/numeric.h" +#include "framework/domain/domain.h" #include "framework/domain/metadomain.h" #include "framework/specialization_registry.h" @@ -19,21 +23,22 @@ #include +#include #include #include namespace ntt { using address_t = std::pair; - using comm_params_t = std::pair>; - - template - auto GetSendRecvRanks( - Metadomain* metadomain, - Domain& domain, - dir::direction_t direction) -> std::pair { - Domain* send_to_nghbr_ptr = nullptr; - Domain* recv_from_nghbr_ptr = nullptr; + using comm_params_t = std::pair>; + + template + auto GetSendRecvRanks(const Metadomain* const metadomain, + Domain& domain, + const dir::direction_t& direction) + -> std::pair { + const Domain* send_to_nghbr_ptr = nullptr; + const Domain* recv_from_nghbr_ptr = nullptr; // set pointers to the correct send/recv domains // can coincide with the current domain if periodic if (domain.mesh.flds_bc_in(direction) == FldsBC::PERIODIC) { @@ -110,12 +115,12 @@ namespace ntt { }; } - template - auto GetSendRecvParams( - Metadomain* metadomain, - Domain& domain, - dir::direction_t direction, - bool synchronize) -> std::pair { + template + auto GetSendRecvParams(const Metadomain* const metadomain, + Domain& domain, + dir::direction_t direction, + bool synchronize) + -> std::pair { const auto [send_indrank, recv_indrank] = GetSendRecvRanks(metadomain, domain, direction); const auto [send_ind, send_rank] = send_indrank; @@ -128,8 +133,8 @@ namespace ntt { { { 0, -1 }, {} } }; } - auto send_slice = std::vector {}; - auto recv_slice = std::vector {}; + auto send_slice = std::vector {}; + auto recv_slice = std::vector {}; const in components[] = { in::x1, in::x2, in::x3 }; // find the field components and indices to be sent/received for (auto d { 0u }; d < direction.size(); ++d) { @@ -196,12 +201,9 @@ namespace ntt { }; } - template - void Metadomain::CommunicateFields(Domain& domain, CommTags tags) { - // const auto comm_fields = (tags & Comm::E) or (tags & Comm::B) or - // (tags & Comm::J) or (tags & Comm::D) or - // (tags & Comm::D0) or (tags & Comm::B0) or - // (tags & Comm::H); + template + void Metadomain::CommunicateFields(Domain& domain, + CommTags tags) const { const auto comm_em = ((S == SimEngine::SRPIC) and ((tags & Comm::E) or (tags & Comm::B))) or ((S == SimEngine::GRPIC) and @@ -215,7 +217,7 @@ namespace ntt { "CommunicateFields called with no task", HERE); - std::string comms = ""; + std::string comms; if (tags & Comm::E) { comms += "E "; } @@ -244,31 +246,31 @@ namespace ntt { * on a single rank, however that is not yet implemented */ // establish the last index ranges for fields (i.e., components) - auto comp_range_fld = range_tuple_t {}; - auto comp_range_cur = range_tuple_t {}; + auto comp_range_fld = cell_range_t {}; + auto comp_range_cur = cell_range_t {}; if constexpr (S == SimEngine::GRPIC) { if (((tags & Comm::D) and (tags & Comm::B)) or ((tags & Comm::D0) and (tags & Comm::B0)) or ((tags & Comm::E) and (tags & Comm::H))) { - comp_range_fld = range_tuple_t(em::dx1, em::bx3 + 1); + comp_range_fld = cell_range_t(em::dx1, em::bx3 + 1); } else if ((tags & Comm::D) or (tags & Comm::D0) or (tags & Comm::E)) { - comp_range_fld = range_tuple_t(em::dx1, em::dx3 + 1); + comp_range_fld = cell_range_t(em::dx1, em::dx3 + 1); } else if ((tags & Comm::B) or (tags & Comm::B0) or (tags & Comm::H)) { - comp_range_fld = range_tuple_t(em::bx1, em::bx3 + 1); + comp_range_fld = cell_range_t(em::bx1, em::bx3 + 1); } } else if constexpr (S == SimEngine::SRPIC) { if ((tags & Comm::E) and (tags & Comm::B)) { - comp_range_fld = range_tuple_t(em::ex1, em::bx3 + 1); + comp_range_fld = cell_range_t(em::ex1, em::bx3 + 1); } else if (tags & Comm::E) { - comp_range_fld = range_tuple_t(em::ex1, em::ex3 + 1); + comp_range_fld = cell_range_t(em::ex1, em::ex3 + 1); } else if (tags & Comm::B) { - comp_range_fld = range_tuple_t(em::bx1, em::bx3 + 1); + comp_range_fld = cell_range_t(em::bx1, em::bx3 + 1); } } else { raise::Error("Unknown simulation engine", HERE); } if (comm_j) { - comp_range_cur = range_tuple_t(cur::jx1, cur::jx3 + 1); + comp_range_cur = cell_range_t(cur::jx1, cur::jx3 + 1); } // traverse in all directions and send/recv the fields for (auto& direction : dir::Directions::all) { @@ -378,17 +380,17 @@ namespace ntt { } template - void AddBufferedFields(ndfield_t& field, - ndfield_t& buffer, - const range_t& range_policy, - const range_tuple_t& components) { + void AddBufferedFields(ndfield_t& field, + ndfield_t& buffer, + const range_t& range_policy, + const cell_range_t& components) { const auto cmin = components.first; const auto cmax = components.second; if constexpr (D == Dim::_1D) { Kokkos::parallel_for( "AddBufferedFields", range_policy, - Lambda(index_t i1) { + Lambda(cellidx_t i1) { for (auto c { cmin }; c < cmax; ++c) { field(i1, c) += buffer(i1, c); } @@ -397,7 +399,7 @@ namespace ntt { Kokkos::parallel_for( "AddBufferedFields", range_policy, - Lambda(index_t i1, index_t i2) { + Lambda(cellidx_t i1, cellidx_t i2) { for (auto c { cmin }; c < cmax; ++c) { field(i1, i2, c) += buffer(i1, i2, c); } @@ -406,7 +408,7 @@ namespace ntt { Kokkos::parallel_for( "AddBuffers", range_policy, - Lambda(index_t i1, index_t i2, index_t i3) { + Lambda(cellidx_t i1, cellidx_t i2, cellidx_t i3) { for (auto c { cmin }; c < cmax; ++c) { field(i1, i2, i3, c) += buffer(i1, i2, i3, c); } @@ -416,10 +418,10 @@ namespace ntt { } } - template - void Metadomain::SynchronizeFields(Domain& domain, - CommTags tags, - const range_tuple_t& components) { + template + void Metadomain::SynchronizeFields(Domain& domain, + CommTags tags, + const cell_range_t& components) const { const bool comm_j = (tags & Comm::J); const bool comm_bckp = (tags & Comm::Bckp); const bool comm_buff = (tags & Comm::Buff); @@ -431,7 +433,7 @@ namespace ntt { HERE); const auto synchronize = true; - std::string comms = ""; + std::string comms; if (comm_j) { comms += "J "; } @@ -443,9 +445,9 @@ namespace ntt { } logger::Checkpoint(fmt::format("Synchronizing %s\n", comms.c_str()), HERE); - auto comp_range_cur = range_tuple_t {}; + auto comp_range_cur = cell_range_t {}; if (comm_j) { - comp_range_cur = range_tuple_t(cur::jx1, cur::jx3 + 1); + comp_range_cur = cell_range_t(cur::jx1, cur::jx3 + 1); Kokkos::deep_copy(domain.fields.buff, ZERO); } ndfield_t bckp_recv; @@ -572,8 +574,8 @@ namespace ntt { } } - template - void Metadomain::CommunicateParticles(Domain& domain) { + template + void Metadomain::CommunicateParticles(Domain& domain) const { #if defined(MPI_ENABLED) logger::Checkpoint("Communicating particles\n", HERE); for (auto& species : domain.species) { @@ -663,24 +665,17 @@ namespace ntt { #endif } - template - void Metadomain::RemoveDeadParticles(Domain& domain) { - for (auto& species : domain.species) { - species.RemoveDead(); - } - } - -#define METADOMAIN_COMM(S, M, D) \ - template void Metadomain>::CommunicateFields(Domain>&, \ - CommTags); \ - template void Metadomain>::SynchronizeFields(Domain>&, \ - CommTags, \ - const range_tuple_t&); \ - template void Metadomain>::CommunicateParticles(Domain>&); \ - template void Metadomain>::RemoveDeadParticles(Domain>&); + // NOLINTBEGIN(bugprone-macro-parentheses) +#define METADOMAIN_COMM(S, M, D) \ + template void Metadomain>::CommunicateFields(Domain>&, \ + CommTags) const; \ + template void Metadomain>::SynchronizeFields(Domain>&, \ + CommTags, \ + const cell_range_t&) const; \ + template void Metadomain>::CommunicateParticles(Domain>&) const; NTT_FOREACH_SPECIALIZATION(METADOMAIN_COMM) - #undef METADOMAIN_COMM + // NOLINTEND(bugprone-macro-parentheses) } // namespace ntt diff --git a/src/framework/domain/output.cpp b/src/framework/domain/metadomain_io.cpp similarity index 89% rename from src/framework/domain/output.cpp rename to src/framework/domain/metadomain_io.cpp index 4d625c4d0..3fd177914 100644 --- a/src/framework/domain/output.cpp +++ b/src/framework/domain/metadomain_io.cpp @@ -2,16 +2,17 @@ #include "global.h" #include "arch/kokkos_aliases.h" +#include "traits/metric.h" #include "utils/error.h" #include "utils/log.h" #include "utils/numeric.h" #include "framework/containers/particles.h" #include "framework/domain/domain.h" +#include "framework/domain/mesh.h" #include "framework/domain/metadomain.h" -#include "framework/parameters.h" +#include "framework/parameters/parameters.h" #include "framework/specialization_registry.h" - #include "kernels/divergences.hpp" #include "kernels/fields_to_phys.hpp" #include "kernels/particle_moments.hpp" @@ -21,16 +22,22 @@ #include #if defined(MPI_ENABLED) + #include "arch/mpi_aliases.h" + #include #endif // MPI_ENABLED #include +#include +#include +#include #include +#include #include namespace ntt { - template + template void Metadomain::InitWriter(adios2::ADIOS* ptr_adios, const SimulationParams& params) { raise::ErrorIf( @@ -57,8 +64,7 @@ namespace ntt { g_writer.init(ptr_adios, params.template get("output.format"), - params.template get("simulation.name"), - params.template get("output.separate_files")); + params.template get("simulation.name")); g_writer.defineMeshLayout(glob_shape_with_ghosts, off_ncells_with_ghosts, loc_shape_with_ghosts, @@ -81,10 +87,6 @@ namespace ntt { "output.particles.species"); g_writer.defineFieldOutputs(S, all_fields_to_write); - Dimension dim = M::PrtlDim; - if constexpr (M::CoordType != Coord::Cart) { - dim = Dim::_3D; - } g_writer.clearSpeciesIndex(); for (const auto& s : species_to_write) { g_writer.addSpeciesIndex(s); @@ -109,7 +111,7 @@ namespace ntt { g_writer.writeAttrs(params); } - template + template void ComputeMoments(const SimulationParams& params, const Mesh& mesh, const std::vector>& prtl_species, @@ -118,7 +120,7 @@ namespace ntt { ndfield_t& buffer, idx_t buff_idx) { std::vector specs = species; - if (specs.size() == 0) { + if (specs.empty()) { // if no species specified, take all massive species for (auto& sp : prtl_species) { if (sp.mass() > 0) { @@ -161,10 +163,10 @@ namespace ntt { } template - void DeepCopyFields(ndfield_t& fld_from, - ndfield_t& fld_to, - const range_tuple_t& from, - const range_tuple_t& to) { + void DeepCopyFields(ndfield_t& fld_from, + ndfield_t& fld_to, + const cell_range_t& from, + const cell_range_t& to) { for (auto d { 0u }; d < D; ++d) { raise::ErrorIf(fld_from.extent(d) != fld_to.extent(d), "Fields have different sizes " + @@ -185,17 +187,17 @@ namespace ntt { } } - template + template void ComputeVectorPotential(ndfield_t& buffer, ndfield_t& EM, unsigned short buff_idx, - const Mesh mesh) { + const Mesh& mesh) { if constexpr (M::Dim == Dim::_2D) { const auto metric = mesh.metric; Kokkos::parallel_for( "ComputeVectorPotential", mesh.rangeActiveCells(), - Lambda(index_t i1, index_t i2) { + Lambda(cellidx_t i1, cellidx_t i2) { const real_t i1_ { COORD(i1) }; const ncells_t k_min = 0; const ncells_t k_max = (i2 - (N_GHOSTS)); @@ -223,10 +225,10 @@ namespace ntt { // "ComputeVectorPotential", // policy, // Lambda(const TeamPolicy::member_type& team_member) { - // index_t i1 = team_member.league_rank(); + // cellidx_t i1 = team_member.league_rank(); // Kokkos::parallel_scan( // Kokkos::TeamThreadRange(team_member, nx2), - // [=](index_t i2, real_t& update, const bool final_pass) { + // [=](cellidx_t i2, real_t& update, const bool final_pass) { // const auto i1_ { static_cast(i1) }; // const auto i2_ { static_cast(i2) }; // const real_t sqrt_detH_ijM { metric.sqrt_det_h({ i1_, i2_ - HALF }) }; @@ -249,20 +251,20 @@ namespace ntt { } #if defined(MPI_ENABLED) && defined(OUTPUT_ENABLED) - template + template void ExtractVectorPotential(ndfield_t& buffer, array_t& aphi_r, unsigned short buff_idx, - const Mesh mesh) { + const Mesh& mesh) { Kokkos::parallel_for( "AddVectorPotential", mesh.rangeActiveCells(), - Lambda(index_t i1, index_t i2) { + Lambda(cellidx_t i1, cellidx_t i2) { buffer(i1, i2, buff_idx) += aphi_r(i1 - N_GHOSTS); }); } - template + template void Metadomain::CommunicateVectorPotential(unsigned short buff_idx) { if constexpr (M::Dim == Dim::_2D) { auto local_domain = subdomain_ptr(l_subdomain_indices()[0]); @@ -280,7 +282,7 @@ namespace ntt { for (auto nr1 { 0u }; nr1 < nranks_x1; ++nr1) { const auto rank_send = rank_send_pre + nr1; const auto rank_recv = rank_recv_pre + nr1; - if (local_domain->mpi_rank() == rank_send) { + if (static_cast(local_domain->mpi_rank()) == rank_send) { array_t aphi_r { "Aphi_r", nx1 }; Kokkos::deep_copy( aphi_r, @@ -288,14 +290,26 @@ namespace ntt { std::make_pair(N_GHOSTS, N_GHOSTS + nx1), N_GHOSTS + nx2 - 1, buff_idx)); + #if !defined(DEVICE_ENABLED) || defined(GPU_AWARE_MPI) MPI_Send(aphi_r.data(), nx1, mpi::get_type(), rank_recv, 0, MPI_COMM_WORLD); + #else + auto aphi_r_h = Kokkos::create_mirror_view(aphi_r); + Kokkos::deep_copy(aphi_r_h, aphi_r); + MPI_Send(aphi_r_h.data(), + nx1, + mpi::get_type(), + rank_recv, + 0, + MPI_COMM_WORLD); + #endif } else if (local_domain->mpi_rank() == rank_recv) { array_t aphi_r { "Aphi_r", nx1 }; + #if !defined(DEVICE_ENABLED) || defined(GPU_AWARE_MPI) MPI_Recv(aphi_r.data(), nx1, mpi::get_type(), @@ -303,6 +317,17 @@ namespace ntt { 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + #else + auto aphi_r_h = Kokkos::create_mirror_view(aphi_r); + MPI_Recv(aphi_r_h.data(), + nx1, + mpi::get_type(), + rank_send, + 0, + MPI_COMM_WORLD, + MPI_STATUS_IGNORE); + Kokkos::deep_copy(aphi_r, aphi_r_h); + #endif ExtractVectorPotential(buffer, aphi_r, buff_idx, local_domain->mesh); } } @@ -315,19 +340,19 @@ namespace ntt { } #endif - template + template auto Metadomain::Write( - const SimulationParams& params, - timestep_t current_step, - timestep_t finished_step, - simtime_t current_time, - simtime_t finished_time, - std::function&, - index_t, - timestep_t, - simtime_t, - const Domain&)> CustomFieldOutput) -> bool { + const SimulationParams& params, + timestep_t current_step, + timestep_t finished_step, + simtime_t current_time, + simtime_t finished_time, + const std::function&, + uint32_t, + timestep_t, + simtime_t, + const Domain&)>& CustomFieldOutput) -> bool { raise::ErrorIf( l_subdomain_indices().size() != 1, "Output for now is only supported for one subdomain per rank", @@ -394,8 +419,8 @@ namespace ntt { const auto add_ghost = (incl_ghosts ? 2 * N_GHOSTS : 0); const auto add_last = (is_last ? 1 : 0); - array_t xc { "Xc", l_size_dwn + add_ghost }; - array_t xe { "Xe", l_size_dwn + add_ghost + add_last }; + const array_t xc { "Xc", l_size_dwn + add_ghost }; + const array_t xe { "Xe", l_size_dwn + add_ghost + add_last }; const auto offset = (incl_ghosts ? N_GHOSTS : 0); const auto ncells = l_size_dwn; @@ -405,7 +430,7 @@ namespace ntt { Kokkos::parallel_for( "GenerateMesh", ncells, - Lambda(index_t i_dwn) { + Lambda(cellidx_t i_dwn) { const auto i = first_cell + i_dwn * dwn_in_dim; const auto i_ = static_cast(i); coord_t x_Cd { ZERO }, x_Ph { ZERO }; @@ -433,8 +458,8 @@ namespace ntt { for (auto& fld : g_writer.fieldWriters()) { Kokkos::deep_copy(local_domain->fields.bckp, ZERO); std::vector names; - std::vector addresses; - if (fld.comp.size() == 0 || fld.comp.size() == 1) { // scalar + std::vector addresses; + if (fld.comp.empty() || fld.comp.size() == 1) { // scalar names.push_back(fld.name()); addresses.push_back(0); if (fld.is_moment()) { @@ -484,18 +509,6 @@ namespace ntt { {}, local_domain->fields.bckp, c); - } else if (fld.id() == FldsID::V) { - if constexpr (S != SimEngine::GRPIC) { - ComputeMoments(params, - local_domain->mesh, - local_domain->species, - fld.species, - fld.comp[0], - local_domain->fields.bckp, - c); - } else { - raise::Error("Bulk velocity not supported for GRPIC", HERE); - } } else { raise::Error("Wrong moment requested for output", HERE); } @@ -614,7 +627,7 @@ namespace ntt { } else { // copy fields to bckp (:, 0, 1, 2) // if as-is specified ==> copy directly to 3, 4, 5 - range_tuple_t copy_to = { 0, 3 }; + cell_range_t copy_to = { 0, 3 }; if (output_asis) { copy_to = { 3, 6 }; } @@ -659,8 +672,8 @@ namespace ntt { if (not output_asis) { // copy fields from bckp(:, 0, 1, 2) -> bckp(:, 3, 4, 5) // converting to proper basis and properly interpolating - list_t comp_from = { 0, 1, 2 }; - list_t comp_to = { 3, 4, 5 }; + list_t comp_from = { 0, 1, 2 }; + list_t comp_to = { 3, 4, 5 }; DeepCopyFields(local_domain->fields.bckp, local_domain->fields.bckp, { 0, 3 }, @@ -734,15 +747,18 @@ namespace ntt { e_min = math::log10(e_min); e_max = math::log10(e_max); } - array_t energy { "energy", n_bins + 1 }; + const array_t energy { "energy", n_bins + 1 }; Kokkos::parallel_for( "GenerateEnergyBins", n_bins + 1, - Lambda(index_t e) { + Lambda(uint32_t e) { if (log_bins) { - energy(e) = math::pow(10.0, e_min + (e_max - e_min) * e / n_bins); + energy(e) = math::pow(static_cast(10), + e_min + (e_max - e_min) * static_cast(e) / + static_cast(n_bins)); } else { - energy(e) = e_min + (e_max - e_min) * e / n_bins; + energy(e) = e_min + (e_max - e_min) * static_cast(e) / + static_cast(n_bins); } }); for (const auto& spec : g_writer.spectraWriters()) { @@ -758,7 +774,7 @@ namespace ntt { Kokkos::parallel_for( "ComputeSpectra", species.rangeActiveParticles(), - Lambda(index_t p) { + Lambda(prtlidx_t p) { if (tag(p) != ParticleTag::alive) { return; } @@ -793,6 +809,7 @@ namespace ntt { return true; } + // NOLINTBEGIN(bugprone-macro-parentheses) #define METADOMAIN_OUTPUT(S, M, D) \ template void Metadomain>::InitWriter(adios2::ADIOS*, \ const SimulationParams&); \ @@ -802,12 +819,12 @@ namespace ntt { timestep_t, \ simtime_t, \ simtime_t, \ - std::function::Dim, 6>&, \ - index_t, \ - timestep_t, \ - simtime_t, \ - const Domain>&)>) -> bool; + const std::function::Dim, 6>&, \ + uint32_t, \ + timestep_t, \ + simtime_t, \ + const Domain>&)>&) -> bool; NTT_FOREACH_SPECIALIZATION(METADOMAIN_OUTPUT) @@ -821,5 +838,6 @@ namespace ntt { #undef COMMVECTORPOTENTIAL #endif + // NOLINTEND(bugprone-macro-parentheses) } // namespace ntt diff --git a/src/framework/domain/metadomain_reshape.cpp b/src/framework/domain/metadomain_reshape.cpp new file mode 100644 index 000000000..5104bcd6f --- /dev/null +++ b/src/framework/domain/metadomain_reshape.cpp @@ -0,0 +1,53 @@ +#include "enums.h" +#include "global.h" + +#include "traits/metric.h" +#include "utils/error.h" + +#include "framework/domain/mesh.h" +#include "framework/domain/metadomain.h" +#include "framework/specialization_registry.h" + +namespace ntt { + + template + void Metadomain::ShiftByCells(int n_cells, in dir) + requires(CartesianMetricClass) + { + raise::ErrorIf( + n_cells == 0, + "ShiftByCells called with n_cells = 0, no shift will be performed", + HERE); + + { + // update metadomain g_mesh + auto new_extent = g_mesh.extent(); + const auto delta_shift = static_cast(n_cells) * + g_mesh.metric.get_dx(); + new_extent[static_cast(dir)].first += delta_shift; + new_extent[static_cast(dir)].second += delta_shift; + + g_mesh.set_extent(new_extent); + } + + // update individual subdomain meshes + for (auto& subdomain : g_subdomains) { + auto new_extent = subdomain.mesh.extent(); + const auto delta_shift = static_cast(n_cells) * + subdomain.mesh.metric.get_dx(); + new_extent[static_cast(dir)].first += delta_shift; + new_extent[static_cast(dir)].second += delta_shift; + + subdomain.mesh.set_extent(new_extent); + } + } + + // NOLINTBEGIN(bugprone-macro-parentheses) +#define METADOMAIN_SHIFT(S, M, D) \ + template void Metadomain>::ShiftByCells(int n_cells, in dir); + + NTT_FOREACH_CARTESIAN_SPECIALIZATION(METADOMAIN_SHIFT) +#undef METADOMAIN_SHIFT + // NOLINTEND(bugprone-macro-parentheses) + +} // namespace ntt \ No newline at end of file diff --git a/src/framework/domain/metadomain_sort.cpp b/src/framework/domain/metadomain_sort.cpp new file mode 100644 index 000000000..791bf31a8 --- /dev/null +++ b/src/framework/domain/metadomain_sort.cpp @@ -0,0 +1,47 @@ +#include "enums.h" +#include "global.h" + +#include "traits/metric.h" + +#include "framework/domain/domain.h" +#include "framework/domain/metadomain.h" +#include "framework/parameters/parameters.h" +#include "framework/specialization_registry.h" + +#include + +namespace ntt { + + template + void Metadomain::SortParticles(simtime_t, + timestep_t step, + const SimulationParams&, + Domain& domain) const { + for (auto& species : domain.species) { + const auto clearing_interval = species.clearing_interval(); + if ((clearing_interval > 0u) and (step % clearing_interval == 0u) and + (step > 0u)) { + for (auto& species : domain.species) { + species.RemoveDead(); + } + } + const auto spatial_sorting_interval = species.spatial_sorting_interval(); + if ((spatial_sorting_interval > 0u) and + (step % spatial_sorting_interval == 0u)) { + species.SortSpatially(domain.mesh); + } + } + } + + // NOLINTBEGIN(bugprone-macro-parentheses) +#define METADOMAIN_COMM(S, M, D) \ + template void Metadomain>::SortParticles(simtime_t, \ + timestep_t, \ + const SimulationParams&, \ + Domain>&) const; + + NTT_FOREACH_SPECIALIZATION(METADOMAIN_COMM) +#undef METADOMAIN_COMM + // NOLINTEND(bugprone-macro-parentheses) + +} // namespace ntt diff --git a/src/framework/domain/stats.cpp b/src/framework/domain/metadomain_stats.cpp similarity index 92% rename from src/framework/domain/stats.cpp rename to src/framework/domain/metadomain_stats.cpp index 64690d695..9d3ded210 100644 --- a/src/framework/domain/stats.cpp +++ b/src/framework/domain/metadomain_stats.cpp @@ -1,6 +1,7 @@ #include "enums.h" #include "global.h" +#include "traits/metric.h" #include "utils/comparators.h" #include "utils/error.h" #include "utils/log.h" @@ -8,10 +9,10 @@ #include "framework/containers/particles.h" #include "framework/domain/domain.h" +#include "framework/domain/mesh.h" #include "framework/domain/metadomain.h" -#include "framework/parameters.h" +#include "framework/parameters/parameters.h" #include "framework/specialization_registry.h" - #include "kernels/reduced_stats.hpp" #include @@ -19,10 +20,13 @@ #include #include +#include +#include +#include namespace ntt { - template + template void Metadomain::InitStatsWriter(const SimulationParams& params, bool is_resuming) { raise::ErrorIf( @@ -62,7 +66,7 @@ namespace ntt { } } - template + template auto ComputeMoments(const SimulationParams& params, const Mesh& mesh, const M& global_metric, @@ -70,7 +74,7 @@ namespace ntt { const std::vector& species, const std::vector& components) -> real_t { std::vector specs = species; - if (specs.size() == 0) { + if (specs.empty()) { // if no species specified, take all massive species for (auto& sp : prtl_species) { if (sp.mass() > 0) { @@ -119,13 +123,13 @@ namespace ntt { } } - template + template auto ReduceFields(Domain* domain, const M& global_metric, const std::vector& components) -> real_t { auto buffer { ZERO }; if constexpr (F == StatsID::JdotE) { - if (components.size() == 0) { + if (components.empty()) { Kokkos::parallel_reduce( "ReduceFields", domain->mesh.rangeActiveCells(), @@ -181,15 +185,16 @@ namespace ntt { return buffer / global_metric.totVolume(); } - template + template auto Metadomain::WriteStats( const SimulationParams& params, timestep_t current_step, timestep_t finished_step, simtime_t current_time, simtime_t finished_time, - std::function&)> - CustomStat) -> bool { + const std::function< + real_t(const std::string&, timestep_t, simtime_t, const Domain&)>& CustomStat) + -> bool { if (not(params.template get("output.stats.enable") and g_stats_writer.shouldWrite(finished_step, finished_time))) { return false; @@ -271,7 +276,7 @@ namespace ntt { } } else { raise::Error("StatsID not implemented for particular SimEngine: " + - std::to_string(static_cast(S)), + std::string(SimEngine(S).to_string()), HERE); } } @@ -279,20 +284,22 @@ namespace ntt { return true; } -#define METADOMAIN_STATS(S, M, D) \ - template void Metadomain>::InitStatsWriter(const SimulationParams&, \ - bool); \ - template auto Metadomain>::WriteStats( \ - const SimulationParams&, \ - timestep_t, \ - timestep_t, \ - simtime_t, \ - simtime_t, \ - std::function< \ - real_t(const std::string&, timestep_t, simtime_t, const Domain>&)>) -> bool; + // NOLINTBEGIN(bugprone-macro-parentheses) +#define METADOMAIN_STATS(S, M, D) \ + template void Metadomain>::InitStatsWriter(const SimulationParams&, \ + bool); \ + template auto Metadomain>::WriteStats( \ + const SimulationParams&, \ + timestep_t, \ + timestep_t, \ + simtime_t, \ + simtime_t, \ + const std::function< \ + real_t(const std::string&, timestep_t, simtime_t, const Domain>&)>&) \ + -> bool; NTT_FOREACH_SPECIALIZATION(METADOMAIN_STATS) - #undef METADOMAIN_STATS + // NOLINTEND(bugprone-macro-parentheses) } // namespace ntt diff --git a/src/framework/parameters.cpp b/src/framework/parameters.cpp deleted file mode 100644 index 550bb20e0..000000000 --- a/src/framework/parameters.cpp +++ /dev/null @@ -1,1120 +0,0 @@ -#include "framework/parameters.h" - -#include "defaults.h" -#include "enums.h" -#include "global.h" - -#include "utils/error.h" -#include "utils/formatting.h" -#include "utils/log.h" -#include "utils/numeric.h" -#include "utils/toml.h" - -#include "metrics/kerr_schild.h" -#include "metrics/kerr_schild_0.h" -#include "metrics/minkowski.h" -#include "metrics/qkerr_schild.h" -#include "metrics/qspherical.h" -#include "metrics/spherical.h" - -#include "framework/containers/species.h" - -#if defined(MPI_ENABLED) - #include -#endif - -#include -#include -#include -#include -#include -#include - -namespace ntt { - - template - auto get_dx0_V0( - const std::vector& resolution, - const boundaries_t& extent, - const std::map& params) -> std::pair { - const auto metric = M(resolution, extent, params); - const auto dx0 = metric.dxMin(); - coord_t x_corner { ZERO }; - for (auto d { 0u }; d < M::Dim; ++d) { - x_corner[d] = HALF; - } - const auto V0 = metric.sqrt_det_h(x_corner); - return { dx0, V0 }; - } - - /* - * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - * Parameters that must not be changed during the checkpoint restart - * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - */ - void SimulationParams::setImmutableParams(const toml::value& toml_data) { - /* [simulation] --------------------------------------------------------- */ - const auto engine_enum = SimEngine::pick( - fmt::toLower(toml::find(toml_data, "simulation", "engine")).c_str()); - set("simulation.engine", engine_enum); - - int default_ndomains = 1; -#if defined(MPI_ENABLED) - raise::ErrorIf(MPI_Comm_size(MPI_COMM_WORLD, &default_ndomains) != MPI_SUCCESS, - "MPI_Comm_size failed", - HERE); -#endif - const auto ndoms = toml::find_or(toml_data, - "simulation", - "domain", - "number", - default_ndomains); - set("simulation.domain.number", (unsigned int)ndoms); - - auto decomposition = toml::find_or>( - toml_data, - "simulation", - "domain", - "decomposition", - std::vector { -1, -1, -1 }); - promiseToDefine("simulation.domain.decomposition"); - - /* [grid] --------------------------------------------------------------- */ - const auto res = toml::find>(toml_data, - "grid", - "resolution"); - raise::ErrorIf(res.size() < 1 || res.size() > 3, - "invalid `grid.resolution`", - HERE); - set("grid.resolution", res); - const auto dim = static_cast(res.size()); - set("grid.dim", dim); - - if (decomposition.size() > dim) { - decomposition.erase(decomposition.begin() + (std::size_t)(dim), - decomposition.end()); - } - raise::ErrorIf(decomposition.size() != dim, - "invalid `simulation.domain.decomposition`", - HERE); - set("simulation.domain.decomposition", decomposition); - - auto extent = toml::find>>(toml_data, - "grid", - "extent"); - raise::ErrorIf(extent.size() < 1 || extent.size() > 3, - "invalid `grid.extent`", - HERE); - promiseToDefine("grid.extent"); - - /* [grid.metric] -------------------------------------------------------- */ - const auto metric_enum = Metric::pick( - fmt::toLower(toml::find(toml_data, "grid", "metric", "metric")) - .c_str()); - promiseToDefine("grid.metric.metric"); - std::string coord; - if (metric_enum == Metric::Minkowski) { - raise::ErrorIf(engine_enum != SimEngine::SRPIC, - "minkowski metric is only supported for SRPIC", - HERE); - coord = "cart"; - } else if (metric_enum == Metric::QKerr_Schild or - metric_enum == Metric::QSpherical) { - // quasi-spherical geometry - raise::ErrorIf(dim == Dim::_1D, - "not enough dimensions for qspherical geometry", - HERE); - raise::ErrorIf(dim == Dim::_3D, - "3D not implemented for qspherical geometry", - HERE); - coord = "qsph"; - set("grid.metric.qsph_r0", - toml::find_or(toml_data, "grid", "metric", "qsph_r0", defaults::qsph::r0)); - set("grid.metric.qsph_h", - toml::find_or(toml_data, "grid", "metric", "qsph_h", defaults::qsph::h)); - } else { - // spherical geometry - raise::ErrorIf(dim == Dim::_1D, - "not enough dimensions for spherical geometry", - HERE); - raise::ErrorIf(dim == Dim::_3D, - "3D not implemented for spherical geometry", - HERE); - coord = "sph"; - } - if ((engine_enum == SimEngine::GRPIC) && - (metric_enum != Metric::Kerr_Schild_0)) { - const auto ks_a = toml::find_or(toml_data, - "grid", - "metric", - "ks_a", - defaults::ks::a); - set("grid.metric.ks_a", ks_a); - set("grid.metric.ks_rh", ONE + math::sqrt(ONE - SQR(ks_a))); - } - const auto coord_enum = Coord::pick(coord.c_str()); - set("grid.metric.coord", coord_enum); - - /* [scales] ------------------------------------------------------------- */ - const auto larmor0 = toml::find(toml_data, "scales", "larmor0"); - const auto skindepth0 = toml::find(toml_data, "scales", "skindepth0"); - raise::ErrorIf(larmor0 <= ZERO || skindepth0 <= ZERO, - "larmor0 and skindepth0 must be positive", - HERE); - set("scales.larmor0", larmor0); - set("scales.skindepth0", skindepth0); - promiseToDefine("scales.dx0"); - promiseToDefine("scales.V0"); - promiseToDefine("scales.n0"); - promiseToDefine("scales.q0"); - set("scales.sigma0", SQR(skindepth0 / larmor0)); - set("scales.B0", ONE / larmor0); - set("scales.omegaB0", ONE / larmor0); - - /* [particles] ---------------------------------------------------------- */ - const auto ppc0 = toml::find(toml_data, "particles", "ppc0"); - set("particles.ppc0", ppc0); - raise::ErrorIf(ppc0 <= 0.0, "ppc0 must be positive", HERE); - set("particles.use_weights", - toml::find_or(toml_data, "particles", "use_weights", false)); - - /* [particles.species] -------------------------------------------------- */ - std::vector species; - const auto species_tab = toml::find_or(toml_data, - "particles", - "species", - toml::array {}); - set("particles.nspec", species_tab.size()); - - spidx_t idx = 1; - for (const auto& sp : species_tab) { - const auto label = toml::find_or(sp, - "label", - "s" + std::to_string(idx)); - const auto mass = toml::find(sp, "mass"); - const auto charge = toml::find(sp, "charge"); - raise::ErrorIf((charge != 0.0f) && (mass == 0.0f), - "mass of the charged species must be non-zero", - HERE); - const auto is_massless = (mass == 0.0f) && (charge == 0.0f); - const auto def_pusher = (is_massless ? defaults::ph_pusher - : defaults::em_pusher); - const auto maxnpart_real = toml::find(sp, "maxnpart"); - const auto maxnpart = static_cast(maxnpart_real); - auto pusher = toml::find_or(sp, "pusher", std::string(def_pusher)); - const auto npayloads_real = toml::find_or(sp, - "n_payloads_real", - static_cast(0)); - const auto use_tracking = toml::find_or(sp, "tracking", false); - auto npayloads_int = toml::find_or(sp, - "n_payloads_int", - static_cast(0)); - if (use_tracking) { -#if !defined(MPI_ENABLED) - npayloads_int += 1; -#else - npayloads_int += 2; -#endif - } - const auto cooling = toml::find_or(sp, "cooling", std::string("None")); - raise::ErrorIf((fmt::toLower(cooling) != "none") && is_massless, - "cooling is only applicable to massive particles", - HERE); - raise::ErrorIf((fmt::toLower(pusher) == "photon") && !is_massless, - "photon pusher is only applicable to massless particles", - HERE); - bool use_gca = false; - if (pusher.find(',') != std::string::npos) { - raise::ErrorIf(fmt::toLower(pusher.substr(pusher.find(',') + 1, - pusher.size())) != "gca", - "invalid pusher syntax", - HERE); - use_gca = true; - pusher = pusher.substr(0, pusher.find(',')); - } - const auto pusher_enum = PrtlPusher::pick(pusher.c_str()); - const auto cooling_enum = Cooling::pick(cooling.c_str()); - if (use_gca) { - raise::ErrorIf(engine_enum != SimEngine::SRPIC, - "GCA pushers are only supported for SRPIC", - HERE); - promiseToDefine("algorithms.gca.e_ovr_b_max"); - promiseToDefine("algorithms.gca.larmor_max"); - } - if (cooling_enum == Cooling::SYNCHROTRON) { - raise::ErrorIf(engine_enum != SimEngine::SRPIC, - "Synchrotron cooling is only supported for SRPIC", - HERE); - promiseToDefine("algorithms.synchrotron.gamma_rad"); - } - - if (cooling_enum == Cooling::COMPTON) { - raise::ErrorIf(engine_enum != SimEngine::SRPIC, - "Inverse Compton cooling is only supported for SRPIC", - HERE); - promiseToDefine("algorithms.compton.gamma_rad"); - } - - species.emplace_back(ParticleSpecies(idx, - label, - mass, - charge, - maxnpart, - pusher_enum, - use_tracking, - use_gca, - cooling_enum, - npayloads_real, - npayloads_int)); - idx += 1; - } - set("particles.species", species); - - /* inferred variables --------------------------------------------------- */ - // extent - if (extent.size() > dim) { - extent.erase(extent.begin() + (std::size_t)(dim), extent.end()); - } - raise::ErrorIf(extent[0].size() != 2, "invalid `grid.extent[0]`", HERE); - if (coord_enum != Coord::Cart) { - raise::ErrorIf(extent.size() > 1, - "invalid `grid.extent` for non-cartesian geometry", - HERE); - extent.push_back({ ZERO, constant::PI }); - if (dim == Dim::_3D) { - extent.push_back({ ZERO, TWO * constant::PI }); - } - } - raise::ErrorIf(extent.size() != dim, "invalid inferred `grid.extent`", HERE); - boundaries_t extent_pairwise; - for (auto d { 0u }; d < (dim_t)dim; ++d) { - raise::ErrorIf(extent[d].size() != 2, - fmt::format("invalid inferred `grid.extent[%d]`", d), - HERE); - extent_pairwise.push_back({ extent[d][0], extent[d][1] }); - } - set("grid.extent", extent_pairwise); - - // metric, dx0, V0, n0, q0 - { - boundaries_t ext; - for (const auto& e : extent) { - ext.push_back({ e[0], e[1] }); - } - std::map params; - if (coord_enum == Coord::Qsph) { - params["r0"] = get("grid.metric.qsph_r0"); - params["h"] = get("grid.metric.qsph_h"); - } - if ((engine_enum == SimEngine::GRPIC) && - (metric_enum != Metric::Kerr_Schild_0)) { - params["a"] = get("grid.metric.ks_a"); - } - set("grid.metric.params", params); - - std::pair dx0_V0; - if (metric_enum == Metric::Minkowski) { - if (dim == Dim::_1D) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (dim == Dim::_2D) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else { - dx0_V0 = get_dx0_V0>(res, ext, params); - } - } else if (metric_enum == Metric::Spherical) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (metric_enum == Metric::QSpherical) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (metric_enum == Metric::Kerr_Schild) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (metric_enum == Metric::Kerr_Schild_0) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } else if (metric_enum == Metric::QKerr_Schild) { - dx0_V0 = get_dx0_V0>(res, ext, params); - } - auto [dx0, V0] = dx0_V0; - set("scales.dx0", dx0); - set("scales.V0", V0); - set("scales.n0", ppc0 / V0); - set("scales.q0", V0 / (ppc0 * SQR(skindepth0))); - - set("grid.metric.metric", metric_enum); - } - } - - /* - * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - * Parameters that may be changed during the checkpoint restart - * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - */ - void SimulationParams::setMutableParams(const toml::value& toml_data) { - const auto engine_enum = get("simulation.engine"); - const auto coord_enum = get("grid.metric.coord"); - const auto dim = get("grid.dim"); - const auto extent_pairwise = get>("grid.extent"); - - /* [simulation] --------------------------------------------------------- */ - set("simulation.name", - toml::find(toml_data, "simulation", "name")); - set("simulation.runtime", - toml::find(toml_data, "simulation", "runtime")); - - /* [grid.boundaraies] --------------------------------------------------- */ - auto flds_bc = toml::find>>( - toml_data, - "grid", - "boundaries", - "fields"); - { - raise::ErrorIf(flds_bc.size() < 1 || flds_bc.size() > 3, - "invalid `grid.boundaries.fields`", - HERE); - promiseToDefine("grid.boundaries.fields"); - auto atm_defined = false; - for (const auto& bcs : flds_bc) { - for (const auto& bc : bcs) { - if (fmt::toLower(bc) == "match") { - promiseToDefine("grid.boundaries.match.ds"); - } - if (fmt::toLower(bc) == "atmosphere") { - raise::ErrorIf(atm_defined, - "ATMOSPHERE is only allowed in one direction", - HERE); - atm_defined = true; - promiseToDefine("grid.boundaries.atmosphere.temperature"); - promiseToDefine("grid.boundaries.atmosphere.density"); - promiseToDefine("grid.boundaries.atmosphere.height"); - promiseToDefine("grid.boundaries.atmosphere.ds"); - promiseToDefine("grid.boundaries.atmosphere.species"); - promiseToDefine("grid.boundaries.atmosphere.g"); - } - } - } - } - - auto prtl_bc = toml::find>>( - toml_data, - "grid", - "boundaries", - "particles"); - { - raise::ErrorIf(prtl_bc.size() < 1 || prtl_bc.size() > 3, - "invalid `grid.boundaries.particles`", - HERE); - promiseToDefine("grid.boundaries.particles"); - auto atm_defined = false; - for (const auto& bcs : prtl_bc) { - for (const auto& bc : bcs) { - if (fmt::toLower(bc) == "absorb") { - promiseToDefine("grid.boundaries.absorb.ds"); - } - if (fmt::toLower(bc) == "atmosphere") { - raise::ErrorIf(atm_defined, - "ATMOSPHERE is only allowed in one direction", - HERE); - atm_defined = true; - promiseToDefine("grid.boundaries.atmosphere.temperature"); - promiseToDefine("grid.boundaries.atmosphere.density"); - promiseToDefine("grid.boundaries.atmosphere.height"); - promiseToDefine("grid.boundaries.atmosphere.ds"); - promiseToDefine("grid.boundaries.atmosphere.species"); - promiseToDefine("grid.boundaries.atmosphere.g"); - } - } - } - } - - /* [algorithms] --------------------------------------------------------- */ - set("algorithms.current_filters", - toml::find_or(toml_data, - "algorithms", - "current_filters", - defaults::current_filters)); - - /* [algorithms.deposit] ------------------------------------------------- */ - set("algorithms.deposit.enable", - toml::find_or(toml_data, "algorithms", "deposit", "enable", true)); - set("algorithms.deposit.order", - toml::find_or(toml_data, "algorithms", "deposit", "order", 1)); - - /* [algorithms.fieldsolver] --------------------------------------------- */ - set("algorithms.fieldsolver.enable", - toml::find_or(toml_data, "algorithms", "fieldsolver", "enable", true)); - - set("algorithms.fieldsolver.delta_x", - toml::find_or(toml_data, - "algorithms", - "fieldsolver", - "delta_x", - defaults::fieldsolver::delta_x)); - set("algorithms.fieldsolver.delta_y", - toml::find_or(toml_data, - "algorithms", - "fieldsolver", - "delta_y", - defaults::fieldsolver::delta_y)); - set("algorithms.fieldsolver.delta_z", - toml::find_or(toml_data, - "algorithms", - "fieldsolver", - "delta_z", - defaults::fieldsolver::delta_z)); - set("algorithms.fieldsolver.beta_xy", - toml::find_or(toml_data, - "algorithms", - "fieldsolver", - "beta_xy", - defaults::fieldsolver::beta_xy)); - set("algorithms.fieldsolver.beta_yx", - toml::find_or(toml_data, - "algorithms", - "fieldsolver", - "beta_yx", - defaults::fieldsolver::beta_yx)); - set("algorithms.fieldsolver.beta_xz", - toml::find_or(toml_data, - "algorithms", - "fieldsolver", - "beta_xz", - defaults::fieldsolver::beta_xz)); - set("algorithms.fieldsolver.beta_zx", - toml::find_or(toml_data, - "algorithms", - "fieldsolver", - "beta_zx", - defaults::fieldsolver::beta_zx)); - set("algorithms.fieldsolver.beta_yz", - toml::find_or(toml_data, - "algorithms", - "fieldsolver", - "beta_yz", - defaults::fieldsolver::beta_yz)); - set("algorithms.fieldsolver.beta_zy", - toml::find_or(toml_data, - "algorithms", - "fieldsolver", - "beta_zy", - defaults::fieldsolver::beta_zy)); - /* [algorithms.timestep] ------------------------------------------------ */ - set("algorithms.timestep.CFL", - toml::find_or(toml_data, "algorithms", "timestep", "CFL", defaults::cfl)); - set("algorithms.timestep.dt", - get("algorithms.timestep.CFL") * get("scales.dx0")); - set("algorithms.timestep.correction", - toml::find_or(toml_data, - "algorithms", - "timestep", - "correction", - defaults::correction)); - - /* [algorithms.gr] ------------------------------------------------------ */ - if (engine_enum == SimEngine::GRPIC) { - set("algorithms.gr.pusher_eps", - toml::find_or(toml_data, - "algorithms", - "gr", - "pusher_eps", - defaults::gr::pusher_eps)); - set("algorithms.gr.pusher_niter", - toml::find_or(toml_data, - "algorithms", - "gr", - "pusher_niter", - defaults::gr::pusher_niter)); - } - /* [particles] ---------------------------------------------------------- */ - set("particles.clear_interval", - toml::find_or(toml_data, "particles", "clear_interval", defaults::clear_interval)); - const auto species_tab = toml::find_or(toml_data, - "particles", - "species", - toml::array {}); - std::vector species = get>( - "particles.species"); - raise::ErrorIf(species_tab.size() != species.size(), - "number of species changed after restart", - HERE); - - std::vector new_species; - - spidx_t idxM1 = 0; - for (const auto& sp : species_tab) { - const auto maxnpart_real = toml::find(sp, "maxnpart"); - const auto maxnpart = static_cast(maxnpart_real); - const auto particle_species = species[idxM1]; - new_species.emplace_back(particle_species.index(), - particle_species.label(), - particle_species.mass(), - particle_species.charge(), - maxnpart, - particle_species.pusher(), - particle_species.use_tracking(), - particle_species.use_gca(), - particle_species.cooling(), - particle_species.npld_r(), - particle_species.npld_i()); - idxM1++; - } - set("particles.species", new_species); - - /* [output] ------------------------------------------------------------- */ - // fields - set("output.format", - toml::find_or(toml_data, "output", "format", defaults::output::format)); - set("output.interval", - toml::find_or(toml_data, "output", "interval", defaults::output::interval)); - set("output.interval_time", - toml::find_or(toml_data, "output", "interval_time", -1.0)); - set("output.separate_files", - toml::find_or(toml_data, "output", "separate_files", true)); - - promiseToDefine("output.fields.enable"); - promiseToDefine("output.fields.interval"); - promiseToDefine("output.fields.interval_time"); - promiseToDefine("output.particles.enable"); - promiseToDefine("output.particles.interval"); - promiseToDefine("output.particles.interval_time"); - promiseToDefine("output.spectra.enable"); - promiseToDefine("output.spectra.interval"); - promiseToDefine("output.spectra.interval_time"); - promiseToDefine("output.stats.enable"); - promiseToDefine("output.stats.interval"); - promiseToDefine("output.stats.interval_time"); - - const auto flds_out = toml::find_or(toml_data, - "output", - "fields", - "quantities", - std::vector {}); - const auto custom_flds_out = toml::find_or(toml_data, - "output", - "fields", - "custom", - std::vector {}); - if (flds_out.size() == 0) { - raise::Warning("No fields output specified", HERE); - } - set("output.fields.quantities", flds_out); - set("output.fields.custom", custom_flds_out); - set("output.fields.mom_smooth", - toml::find_or(toml_data, - "output", - "fields", - "mom_smooth", - defaults::output::mom_smooth)); - std::vector field_dwn; - try { - auto field_dwn_ = toml::find>(toml_data, - "output", - "fields", - "downsampling"); - for (auto i = 0u; i < field_dwn_.size(); ++i) { - field_dwn.push_back(field_dwn_[i]); - } - } catch (...) { - try { - auto field_dwn_ = toml::find(toml_data, - "output", - "fields", - "downsampling"); - for (auto i = 0u; i < dim; ++i) { - field_dwn.push_back(field_dwn_); - } - } catch (...) { - for (auto i = 0u; i < dim; ++i) { - field_dwn.push_back(1u); - } - } - } - raise::ErrorIf(field_dwn.size() > 3, "invalid `output.fields.downsampling`", HERE); - if (field_dwn.size() > dim) { - field_dwn.erase(field_dwn.begin() + (std::size_t)(dim), field_dwn.end()); - } - for (const auto& dwn : field_dwn) { - raise::ErrorIf(dwn == 0, "downsampling factor must be nonzero", HERE); - } - set("output.fields.downsampling", field_dwn); - - // particles - auto all_specs = std::vector {}; - const auto nspec = get("particles.nspec"); - for (auto i = 0u; i < nspec; ++i) { - all_specs.push_back(static_cast(i + 1)); - } - const auto prtl_out = toml::find_or(toml_data, - "output", - "particles", - "species", - all_specs); - set("output.particles.species", prtl_out); - set("output.particles.stride", - toml::find_or(toml_data, - "output", - "particles", - "stride", - defaults::output::prtl_stride)); - - // spectra - set("output.spectra.e_min", - toml::find_or(toml_data, "output", "spectra", "e_min", defaults::output::spec_emin)); - set("output.spectra.e_max", - toml::find_or(toml_data, "output", "spectra", "e_max", defaults::output::spec_emax)); - set("output.spectra.log_bins", - toml::find_or(toml_data, - "output", - "spectra", - "log_bins", - defaults::output::spec_log)); - set("output.spectra.n_bins", - toml::find_or(toml_data, - "output", - "spectra", - "n_bins", - defaults::output::spec_nbins)); - - // stats - set("output.stats.quantities", - toml::find_or(toml_data, - "output", - "stats", - "quantities", - defaults::output::stats_quantities)); - set("output.stats.custom", - toml::find_or(toml_data, - "output", - "stats", - "custom", - std::vector {})); - - // intervals - for (const auto& type : { "fields", "particles", "spectra", "stats" }) { - const auto q_int = toml::find_or(toml_data, - "output", - std::string(type), - "interval", - 0); - const auto q_int_time = toml::find_or(toml_data, - "output", - std::string(type), - "interval_time", - -1.0); - set("output." + std::string(type) + ".enable", - toml::find_or(toml_data, "output", std::string(type), "enable", true)); - if ((q_int == 0) and (q_int_time == -1.0)) { - set("output." + std::string(type) + ".interval", - get("output.interval")); - set("output." + std::string(type) + ".interval_time", - get("output.interval_time")); - } else { - set("output." + std::string(type) + ".interval", q_int); - set("output." + std::string(type) + ".interval_time", q_int_time); - } - } - - /* [output.debug] ------------------------------------------------------- */ - set("output.debug.as_is", - toml::find_or(toml_data, "output", "debug", "as_is", false)); - const auto output_ghosts = toml::find_or(toml_data, - "output", - "debug", - "ghosts", - false); - set("output.debug.ghosts", output_ghosts); - if (output_ghosts) { - for (const auto& dwn : field_dwn) { - raise::ErrorIf( - dwn != 1, - "full resolution required when outputting with ghost cells", - HERE); - } - } - - /* [checkpoint] --------------------------------------------------------- */ - set("checkpoint.interval", - toml::find_or(toml_data, - "checkpoint", - "interval", - defaults::checkpoint::interval)); - set("checkpoint.interval_time", - toml::find_or(toml_data, "checkpoint", "interval_time", -1.0)); - set("checkpoint.keep", - toml::find_or(toml_data, "checkpoint", "keep", defaults::checkpoint::keep)); - auto walltime_str = toml::find_or(toml_data, - "checkpoint", - "walltime", - defaults::checkpoint::walltime); - if (walltime_str.empty()) { - walltime_str = defaults::checkpoint::walltime; - } - set("checkpoint.walltime", walltime_str); - - const auto checkpoint_write_path = toml::find_or( - toml_data, - "checkpoint", - "write_path", - fmt::format(defaults::checkpoint::write_path.c_str(), - get("simulation.name").c_str())); - set("checkpoint.write_path", checkpoint_write_path); - set("checkpoint.read_path", - toml::find_or(toml_data, "checkpoint", "read_path", checkpoint_write_path)); - - /* [diagnostics] -------------------------------------------------------- */ - set("diagnostics.interval", - toml::find_or(toml_data, "diagnostics", "interval", defaults::diag::interval)); - set("diagnostics.blocking_timers", - toml::find_or(toml_data, "diagnostics", "blocking_timers", false)); - set("diagnostics.colored_stdout", - toml::find_or(toml_data, "diagnostics", "colored_stdout", false)); - set("diagnostics.log_level", - toml::find_or(toml_data, "diagnostics", "log_level", defaults::diag::log_level)); - - /* inferred variables --------------------------------------------------- */ - // fields/particle boundaries - std::vector> flds_bc_enum; - std::vector> prtl_bc_enum; - if (coord_enum == Coord::Cart) { - raise::ErrorIf(flds_bc.size() != (std::size_t)dim, - "invalid `grid.boundaries.fields`", - HERE); - raise::ErrorIf(prtl_bc.size() != (std::size_t)dim, - "invalid `grid.boundaries.particles`", - HERE); - for (auto d { 0u }; d < (dim_t)dim; ++d) { - flds_bc_enum.push_back({}); - prtl_bc_enum.push_back({}); - const auto fbc = flds_bc[d]; - const auto pbc = prtl_bc[d]; - raise::ErrorIf(fbc.size() < 1 || fbc.size() > 2, - "invalid `grid.boundaries.fields`", - HERE); - raise::ErrorIf(pbc.size() < 1 || pbc.size() > 2, - "invalid `grid.boundaries.particles`", - HERE); - auto fbc_enum = FldsBC::pick(fmt::toLower(fbc[0]).c_str()); - auto pbc_enum = PrtlBC::pick(fmt::toLower(pbc[0]).c_str()); - if (fbc.size() == 1) { - raise::ErrorIf(fbc_enum != FldsBC::PERIODIC, - "invalid `grid.boundaries.fields`", - HERE); - flds_bc_enum.back().push_back(FldsBC(FldsBC::PERIODIC)); - flds_bc_enum.back().push_back(FldsBC(FldsBC::PERIODIC)); - } else { - raise::ErrorIf(fbc_enum == FldsBC::PERIODIC, - "invalid `grid.boundaries.fields`", - HERE); - flds_bc_enum.back().push_back(fbc_enum); - auto fbc_enum = FldsBC::pick(fmt::toLower(fbc[1]).c_str()); - raise::ErrorIf(fbc_enum == FldsBC::PERIODIC, - "invalid `grid.boundaries.fields`", - HERE); - flds_bc_enum.back().push_back(fbc_enum); - } - if (pbc.size() == 1) { - raise::ErrorIf(pbc_enum != PrtlBC::PERIODIC, - "invalid `grid.boundaries.particles`", - HERE); - prtl_bc_enum.back().push_back(PrtlBC(PrtlBC::PERIODIC)); - prtl_bc_enum.back().push_back(PrtlBC(PrtlBC::PERIODIC)); - } else { - raise::ErrorIf(pbc_enum == PrtlBC::PERIODIC, - "invalid `grid.boundaries.particles`", - HERE); - prtl_bc_enum.back().push_back(pbc_enum); - auto pbc_enum = PrtlBC::pick(fmt::toLower(pbc[1]).c_str()); - raise::ErrorIf(pbc_enum == PrtlBC::PERIODIC, - "invalid `grid.boundaries.particles`", - HERE); - prtl_bc_enum.back().push_back(pbc_enum); - } - } - } else { - raise::ErrorIf(flds_bc.size() > 1, "invalid `grid.boundaries.fields`", HERE); - raise::ErrorIf(prtl_bc.size() > 1, "invalid `grid.boundaries.particles`", HERE); - if (engine_enum == SimEngine::SRPIC) { - raise::ErrorIf(flds_bc[0].size() != 2, - "invalid `grid.boundaries.fields`", - HERE); - flds_bc_enum.push_back( - { FldsBC::pick(fmt::toLower(flds_bc[0][0]).c_str()), - FldsBC::pick(fmt::toLower(flds_bc[0][1]).c_str()) }); - flds_bc_enum.push_back({ FldsBC::AXIS, FldsBC::AXIS }); - if (dim == Dim::_3D) { - flds_bc_enum.push_back({ FldsBC::PERIODIC, FldsBC::PERIODIC }); - } - raise::ErrorIf(prtl_bc[0].size() != 2, - "invalid `grid.boundaries.particles`", - HERE); - prtl_bc_enum.push_back( - { PrtlBC::pick(fmt::toLower(prtl_bc[0][0]).c_str()), - PrtlBC::pick(fmt::toLower(prtl_bc[0][1]).c_str()) }); - prtl_bc_enum.push_back({ PrtlBC::AXIS, PrtlBC::AXIS }); - if (dim == Dim::_3D) { - prtl_bc_enum.push_back({ PrtlBC::PERIODIC, PrtlBC::PERIODIC }); - } - } else { - raise::ErrorIf(flds_bc[0].size() != 1, - "invalid `grid.boundaries.fields`", - HERE); - raise::ErrorIf(prtl_bc[0].size() != 1, - "invalid `grid.boundaries.particles`", - HERE); - flds_bc_enum.push_back( - { FldsBC::HORIZON, FldsBC::pick(fmt::toLower(flds_bc[0][0]).c_str()) }); - flds_bc_enum.push_back({ FldsBC::AXIS, FldsBC::AXIS }); - if (dim == Dim::_3D) { - flds_bc_enum.push_back({ FldsBC::PERIODIC, FldsBC::PERIODIC }); - } - prtl_bc_enum.push_back( - { PrtlBC::HORIZON, PrtlBC::pick(fmt::toLower(prtl_bc[0][0]).c_str()) }); - prtl_bc_enum.push_back({ PrtlBC::AXIS, PrtlBC::AXIS }); - if (dim == Dim::_3D) { - prtl_bc_enum.push_back({ PrtlBC::PERIODIC, PrtlBC::PERIODIC }); - } - } - } - - raise::ErrorIf(flds_bc_enum.size() != (std::size_t)dim, - "invalid inferred `grid.boundaries.fields`", - HERE); - raise::ErrorIf(prtl_bc_enum.size() != (std::size_t)dim, - "invalid inferred `grid.boundaries.particles`", - HERE); - boundaries_t flds_bc_pairwise; - boundaries_t prtl_bc_pairwise; - for (auto d { 0u }; d < (dim_t)dim; ++d) { - raise::ErrorIf( - flds_bc_enum[d].size() != 2, - fmt::format("invalid inferred `grid.boundaries.fields[%d]`", d), - HERE); - raise::ErrorIf( - prtl_bc_enum[d].size() != 2, - fmt::format("invalid inferred `grid.boundaries.particles[%d]`", d), - HERE); - flds_bc_pairwise.push_back({ flds_bc_enum[d][0], flds_bc_enum[d][1] }); - prtl_bc_pairwise.push_back({ prtl_bc_enum[d][0], prtl_bc_enum[d][1] }); - } - set("grid.boundaries.fields", flds_bc_pairwise); - set("grid.boundaries.particles", prtl_bc_pairwise); - - if (isPromised("grid.boundaries.match.ds")) { - if (coord_enum == Coord::Cart) { - auto min_extent = std::numeric_limits::max(); - for (const auto& e : extent_pairwise) { - min_extent = std::min(min_extent, e.second - e.first); - } - const auto default_ds = min_extent * defaults::bc::match::ds_frac; - boundaries_t ds_array; - try { - auto ds = toml::find(toml_data, "grid", "boundaries", "match", "ds"); - for (auto d = 0u; d < dim; ++d) { - ds_array.push_back({ ds, ds }); - } - } catch (...) { - try { - const auto ds = toml::find>>( - toml_data, - "grid", - "boundaries", - "match", - "ds"); - raise::ErrorIf(ds.size() != dim, - "invalid # in `grid.boundaries.match.ds`", - HERE); - for (auto d = 0u; d < dim; ++d) { - if (ds[d].size() == 1) { - ds_array.push_back({ ds[d][0], ds[d][0] }); - } else if (ds[d].size() == 2) { - ds_array.push_back({ ds[d][0], ds[d][1] }); - } else if (ds[d].size() == 0) { - ds_array.push_back({}); - } else { - raise::Error("invalid `grid.boundaries.match.ds`", HERE); - } - } - } catch (...) { - for (auto d = 0u; d < dim; ++d) { - ds_array.push_back({ default_ds, default_ds }); - } - } - } - set("grid.boundaries.match.ds", ds_array); - } else { - auto r_extent = extent_pairwise[0].second - extent_pairwise[0].first; - const auto ds = toml::find_or( - toml_data, - "grid", - "boundaries", - "match", - "ds", - r_extent * defaults::bc::match::ds_frac); - boundaries_t ds_array { - { ds, ds } - }; - set("grid.boundaries.match.ds", ds_array); - } - } - - if (isPromised("grid.boundaries.absorb.ds")) { - if (coord_enum == Coord::Cart) { - auto min_extent = std::numeric_limits::max(); - for (const auto& e : extent_pairwise) { - min_extent = std::min(min_extent, e.second - e.first); - } - set("grid.boundaries.absorb.ds", - toml::find_or(toml_data, - "grid", - "boundaries", - "absorb", - "ds", - min_extent * defaults::bc::absorb::ds_frac)); - } else { - auto r_extent = extent_pairwise[0].second - extent_pairwise[0].first; - set("grid.boundaries.absorb.ds", - toml::find_or(toml_data, - "grid", - "boundaries", - "absorb", - "ds", - r_extent * defaults::bc::absorb::ds_frac)); - } - } - - if (isPromised("grid.boundaries.atmosphere.temperature")) { - const auto atm_T = toml::find(toml_data, - "grid", - "boundaries", - "atmosphere", - "temperature"); - const auto atm_h = toml::find(toml_data, - "grid", - "boundaries", - "atmosphere", - "height"); - set("grid.boundaries.atmosphere.temperature", atm_T); - set("grid.boundaries.atmosphere.density", - toml::find(toml_data, "grid", "boundaries", "atmosphere", "density")); - set("grid.boundaries.atmosphere.ds", - toml::find_or(toml_data, "grid", "boundaries", "atmosphere", "ds", ZERO)); - set("grid.boundaries.atmosphere.height", atm_h); - set("grid.boundaries.atmosphere.g", atm_T / atm_h); - const auto atm_species = toml::find>( - toml_data, - "grid", - "boundaries", - "atmosphere", - "species"); - set("grid.boundaries.atmosphere.species", atm_species); - } - - // gca - if (isPromised("algorithms.gca.e_ovr_b_max")) { - set("algorithms.gca.e_ovr_b_max", - toml::find_or(toml_data, - "algorithms", - "gca", - "e_ovr_b_max", - defaults::gca::EovrB_max)); - set("algorithms.gca.larmor_max", - toml::find_or(toml_data, "algorithms", "gca", "larmor_max", ZERO)); - } - - // cooling - if (isPromised("algorithms.synchrotron.gamma_rad")) { - set("algorithms.synchrotron.gamma_rad", - toml::find_or(toml_data, - "algorithms", - "synchrotron", - "gamma_rad", - defaults::synchrotron::gamma_rad)); - } - if (isPromised("algorithms.compton.gamma_rad")) { - set("algorithms.compton.gamma_rad", - toml::find_or(toml_data, - "algorithms", - "compton", - "gamma_rad", - defaults::compton::gamma_rad)); - } - - // @TODO: disabling stats for non-Cartesian - if (coord_enum != Coord::Cart) { - set("output.stats.enable", false); - } - } - - void SimulationParams::setSetupParams(const toml::value& toml_data) { - /* [setup] -------------------------------------------------------------- */ - const auto setup = toml::find_or(toml_data, "setup", toml::table {}); - for (const auto& [key, val] : setup) { - if (val.is_boolean()) { - set("setup." + key, (bool)(val.as_boolean())); - } else if (val.is_integer()) { - set("setup." + key, (int)(val.as_integer())); - } else if (val.is_floating()) { - set("setup." + key, (real_t)(val.as_floating())); - } else if (val.is_string()) { - set("setup." + key, (std::string)(val.as_string())); - } else if (val.is_array()) { - const auto val_arr = val.as_array(); - if (val_arr.size() == 0) { - continue; - } else { - if (val_arr[0].is_integer()) { - std::vector vec; - for (const auto& v : val_arr) { - vec.push_back(v.as_integer()); - } - set("setup." + key, vec); - } else if (val_arr[0].is_floating()) { - std::vector vec; - for (const auto& v : val_arr) { - vec.push_back(v.as_floating()); - } - set("setup." + key, vec); - } else if (val_arr[0].is_boolean()) { - std::vector vec; - for (const auto& v : val_arr) { - vec.push_back(v.as_boolean()); - } - set("setup." + key, vec); - } else if (val_arr[0].is_string()) { - std::vector vec; - for (const auto& v : val_arr) { - vec.push_back(v.as_string()); - } - set("setup." + key, vec); - } else if (val_arr[0].is_array()) { - raise::Error("only 1D arrays allowed in [setup]", HERE); - } else { - raise::Error("invalid setup variable type", HERE); - } - } - } - } - } - - void SimulationParams::setCheckpointParams(bool is_resuming, - timestep_t start_step, - simtime_t start_time) { - set("checkpoint.is_resuming", is_resuming); - set("checkpoint.start_step", start_step); - set("checkpoint.start_time", start_time); - } - - void SimulationParams::checkPromises() const { - raise::ErrorIf(!promisesFulfilled(), - "Have not defined all the necessary variables", - HERE); - } - - void SimulationParams::saveTOML(const std::string& path, simtime_t time) const { - CallOnce([&]() { - std::ofstream metadata; - metadata.open(path); - metadata << fmt::format("[metadata]\n time = %f\n\n", time) << data() - << std::endl; - metadata.close(); - }); - } - -} // namespace ntt diff --git a/src/framework/parameters/algorithms.cpp b/src/framework/parameters/algorithms.cpp new file mode 100644 index 000000000..4766db965 --- /dev/null +++ b/src/framework/parameters/algorithms.cpp @@ -0,0 +1,161 @@ +#include "framework/parameters/algorithms.h" + +#include "defaults.h" +#include "global.h" + +#include "utils/numeric.h" + +#include "framework/parameters/parameters.h" + +#include + +#include +#include + +namespace ntt { + namespace params { + + void Algorithms::read(real_t dx0, + const std::map& extra, + const toml::value& toml_data) { + CFL = toml::find_or(toml_data, "algorithms", "timestep", "CFL", defaults::cfl); + dt = CFL.value() * dx0; + dt_correction_factor = toml::find_or(toml_data, + "algorithms", + "timestep", + "correction", + defaults::correction); + + number_of_current_filters = toml::find_or(toml_data, + "algorithms", + "current_filters", + defaults::current_filters); + + deposit_enable = toml::find_or(toml_data, "algorithms", "deposit", "enable", true); + deposit_order = static_cast(SHAPE_ORDER); + + fieldsolver_enable = toml::find_or(toml_data, + "algorithms", + "fieldsolver", + "enable", + true); + fieldsolver_stencil_coeffs.emplace(); + (*fieldsolver_stencil_coeffs)["delta_x"] = toml::find_or( + toml_data, + "algorithms", + "fieldsolver", + "delta_x", + defaults::fieldsolver::delta_x); + + (*fieldsolver_stencil_coeffs)["delta_y"] = toml::find_or( + toml_data, + "algorithms", + "fieldsolver", + "delta_y", + defaults::fieldsolver::delta_y); + + (*fieldsolver_stencil_coeffs)["delta_z"] = toml::find_or( + toml_data, + "algorithms", + "fieldsolver", + "delta_z", + defaults::fieldsolver::delta_z); + + (*fieldsolver_stencil_coeffs)["beta_xy"] = toml::find_or( + toml_data, + "algorithms", + "fieldsolver", + "beta_xy", + defaults::fieldsolver::beta_xy); + + (*fieldsolver_stencil_coeffs)["beta_yx"] = toml::find_or( + toml_data, + "algorithms", + "fieldsolver", + "beta_yx", + defaults::fieldsolver::beta_yx); + + (*fieldsolver_stencil_coeffs)["beta_xz"] = toml::find_or( + toml_data, + "algorithms", + "fieldsolver", + "beta_xz", + defaults::fieldsolver::beta_xz); + + (*fieldsolver_stencil_coeffs)["beta_zx"] = toml::find_or( + toml_data, + "algorithms", + "fieldsolver", + "beta_zx", + defaults::fieldsolver::beta_zx); + + (*fieldsolver_stencil_coeffs)["beta_yz"] = toml::find_or( + toml_data, + "algorithms", + "fieldsolver", + "beta_yz", + defaults::fieldsolver::beta_yz); + + (*fieldsolver_stencil_coeffs)["beta_zy"] = toml::find_or( + toml_data, + "algorithms", + "fieldsolver", + "beta_zy", + defaults::fieldsolver::beta_zy); + + if (extra.at("gr")) { + gr_pusher_eps = toml::find_or(toml_data, + "algorithms", + "gr", + "pusher_eps", + defaults::gr::pusher_eps); + gr_pusher_niter = toml::find_or(toml_data, + "algorithms", + "gr", + "pusher_niter", + defaults::gr::pusher_niter); + } + + if (extra.at("gca")) { + gca_e_ovr_b_max = toml::find_or(toml_data, + "algorithms", + "gca", + "e_ovr_b_max", + defaults::gca::EovrB_max); + gca_larmor_max = toml::find_or(toml_data, + "algorithms", + "gca", + "larmor_max", + ZERO); + } + } + + void Algorithms::setParams(const std::map& extra, + SimulationParams* params) const { + params->set("algorithms.timestep.CFL", CFL.value()); + params->set("algorithms.timestep.dt", dt.value()); + params->set("algorithms.timestep.correction", dt_correction_factor.value()); + + params->set("algorithms.current_filters", number_of_current_filters.value()); + + params->set("algorithms.deposit.enable", deposit_enable.value()); + params->set("algorithms.deposit.order", deposit_order.value()); + + params->set("algorithms.fieldsolver.enable", fieldsolver_enable.value()); + for (const auto& [key, value] : fieldsolver_stencil_coeffs.value()) { + params->set("algorithms.fieldsolver." + key, value); + } + + if (extra.at("gr")) { + params->set("algorithms.gr.pusher_eps", gr_pusher_eps.value()); + params->set("algorithms.gr.pusher_niter", gr_pusher_niter.value()); + } + + if (extra.at("gca")) { + params->set("algorithms.gca.e_ovr_b_max", gca_e_ovr_b_max.value()); + params->set("algorithms.gca.larmor_max", gca_larmor_max.value()); + } + } + + } // namespace params +} // namespace ntt diff --git a/src/framework/parameters/algorithms.h b/src/framework/parameters/algorithms.h new file mode 100644 index 000000000..97edb244a --- /dev/null +++ b/src/framework/parameters/algorithms.h @@ -0,0 +1,57 @@ +/** + * @file framework/parameters/algorithms.h + * @brief Auxiliary functions for reading in algorithms parameters + * @implements + * - ntt::params::Algorithms + * @cpp: + * - algorithms.cpp + * @namespaces: + * - ntt::params:: + */ + +#ifndef FRAMEWORK_PARAMETERS_ALGORITHMS_H +#define FRAMEWORK_PARAMETERS_ALGORITHMS_H + +#include "global.h" + +#include "framework/parameters/parameters.h" + +#include + +#include +#include +#include + +namespace ntt { + namespace params { + + struct Algorithms { + std::optional CFL; + std::optional dt; + std::optional dt_correction_factor; + + std::optional number_of_current_filters; + + std::optional deposit_enable; + std::optional deposit_order; + + std::optional fieldsolver_enable; + std::optional> fieldsolver_stencil_coeffs; + + std::optional gr_pusher_eps; + std::optional gr_pusher_niter; + + std::optional gca_e_ovr_b_max; + std::optional gca_larmor_max; + + std::optional synchrotron_gamma_rad; + std::optional compton_gamma_rad; + + void read(real_t, const std::map&, const toml::value&); + void setParams(const std::map&, SimulationParams*) const; + }; + + } // namespace params +} // namespace ntt + +#endif // FRAMEWORK_PARAMETERS_ALGORITHMS_H diff --git a/src/framework/parameters/extra.cpp b/src/framework/parameters/extra.cpp new file mode 100644 index 000000000..dbee39c7c --- /dev/null +++ b/src/framework/parameters/extra.cpp @@ -0,0 +1,164 @@ +#include "framework/parameters/extra.h" + +#include "defaults.h" +#include "global.h" + +#include "utils/numeric.h" + +#include "framework/parameters/parameters.h" + +#include + +#include +#include + +namespace ntt { + namespace params { + + void Extra::read(const std::map& extra, + const toml::value& toml_data, + const SimulationParams* const params) { + if (extra.at("synchrotron_drag")) { + synchrotron_gamma_rad = toml::find_or(toml_data, + "radiation", + "drag", + "synchrotron", + "gamma_rad", + defaults::synchrotron::gamma_rad); + } + + if (extra.at("compton_drag")) { + compton_gamma_rad = toml::find_or(toml_data, + "radiation", + "drag", + "compton", + "gamma_rad", + defaults::compton::gamma_rad); + } + + if (extra.at("synchrotron_emission")) { + synchrotron_gamma_rad = toml::find_or(toml_data, + "radiation", + "drag", + "synchrotron", + "gamma_rad", + defaults::synchrotron::gamma_rad); + synchrotron_gamma_qed = toml::find_or(toml_data, + "radiation", + "emission", + "synchrotron", + "gamma_qed", + defaults::synchrotron::gamma_qed); + synchrotron_energy_min = toml::find_or(toml_data, + "radiation", + "emission", + "synchrotron", + "photon_energy_min", + defaults::synchrotron::energy_min); + synchrotron_photon_weight = toml::find_or(toml_data, + "radiation", + "emission", + "synchrotron", + "photon_weight", + ONE); + synchrotron_photon_species = toml::find(toml_data, + "radiation", + "emission", + "synchrotron", + "photon_species"); + synchrotron_nominal_probability = + params->template get("scales.omegaB0") * + static_cast(0.1) * + params->template get("algorithms.timestep.dt") * + SQR(synchrotron_gamma_qed.value() / synchrotron_gamma_rad.value()) / + synchrotron_photon_weight.value(); + synchrotron_nominal_photon_energy = ONE / + SQR(synchrotron_gamma_qed.value()); + } + + if (extra.at("compton_emission")) { + compton_gamma_rad = toml::find_or(toml_data, + "radiation", + "drag", + "compton", + "gamma_rad", + defaults::compton::gamma_rad); + compton_gamma_qed = toml::find_or(toml_data, + "radiation", + "emission", + "compton", + "gamma_qed", + defaults::compton::gamma_qed); + compton_energy_min = toml::find_or(toml_data, + "radiation", + "emission", + "compton", + "photon_energy_min", + defaults::compton::energy_min); + compton_photon_weight = toml::find_or(toml_data, + "radiation", + "emission", + "compton", + "photon_weight", + ONE); + compton_photon_species = toml::find(toml_data, + "radiation", + "emission", + "compton", + "photon_species"); + compton_nominal_probability = + params->template get("scales.omegaB0") * + static_cast(0.1) * + params->template get("algorithms.timestep.dt") * + SQR(compton_gamma_qed.value() / compton_gamma_rad.value()) / + compton_photon_weight.value(); + compton_nominal_photon_energy = ONE / SQR(compton_gamma_qed.value()); + } + } + + void Extra::setParams(const std::map& extra, + SimulationParams* params) const { + if (extra.at("synchrotron_drag")) { + params->set("radiation.drag.synchrotron.gamma_rad", + synchrotron_gamma_rad.value()); + } + + if (extra.at("compton_drag")) { + params->set("radiation.drag.compton.gamma_rad", compton_gamma_rad.value()); + } + + if (extra.at("synchrotron_emission")) { + params->set("radiation.drag.synchrotron.gamma_rad", + synchrotron_gamma_rad.value()); + params->set("radiation.emission.synchrotron.gamma_qed", + synchrotron_gamma_qed.value()); + params->set("radiation.emission.synchrotron.photon_energy_min", + synchrotron_energy_min.value()); + params->set("radiation.emission.synchrotron.photon_weight", + synchrotron_photon_weight.value()); + params->set("radiation.emission.synchrotron.photon_species", + synchrotron_photon_species.value()); + params->set("radiation.emission.synchrotron.nominal_probability", + synchrotron_nominal_probability.value()); + params->set("radiation.emission.synchrotron.nominal_photon_energy", + synchrotron_nominal_photon_energy.value()); + } + + if (extra.at("compton_emission")) { + params->set("radiation.drag.compton.gamma_rad", compton_gamma_rad.value()); + params->set("radiation.emission.compton.gamma_qed", + compton_gamma_qed.value()); + params->set("radiation.emission.compton.photon_energy_min", + compton_energy_min.value()); + params->set("radiation.emission.compton.photon_weight", + compton_photon_weight.value()); + params->set("radiation.emission.compton.photon_species", + compton_photon_species.value()); + params->set("radiation.emission.compton.nominal_probability", + compton_nominal_probability.value()); + params->set("radiation.emission.compton.nominal_photon_energy", + compton_nominal_photon_energy.value()); + } + } + } // namespace params +} // namespace ntt diff --git a/src/framework/parameters/extra.h b/src/framework/parameters/extra.h new file mode 100644 index 000000000..29de4527a --- /dev/null +++ b/src/framework/parameters/extra.h @@ -0,0 +1,57 @@ +/** + * @file framework/parameters/extra.h + * @brief Auxiliary functions for reading in extra physics parameters + * @implements + * - ntt::params::Extra + * @cpp: + * - extra.cpp + * @namespaces: + * - ntt::params:: + */ + +#ifndef FRAMEWORK_PARAMETERS_EXTRA_H +#define FRAMEWORK_PARAMETERS_EXTRA_H + +#include "global.h" + +#include "framework/parameters/parameters.h" + +#include + +#include +#include +#include + +namespace ntt { + namespace params { + + struct Extra { + // radiative drag parameters + std::optional synchrotron_gamma_rad; + std::optional compton_gamma_rad; + + // emission parameters + std::optional synchrotron_energy_min; + std::optional synchrotron_gamma_qed; + std::optional synchrotron_photon_weight; + std::optional synchrotron_photon_species; + std::optional synchrotron_nominal_probability; + std::optional synchrotron_nominal_photon_energy; + + std::optional compton_energy_min; + std::optional compton_gamma_qed; + std::optional compton_photon_weight; + std::optional compton_photon_species; + std::optional compton_nominal_probability; + std::optional compton_nominal_photon_energy; + + void read(const std::map&, + const toml::value&, + const SimulationParams* const); + void setParams(const std::map&, SimulationParams*) const; + }; + + } // namespace params +} // namespace ntt + +#endif // FRAMEWORK_PARAMETERS_EXTRA_H diff --git a/src/framework/parameters/grid.cpp b/src/framework/parameters/grid.cpp new file mode 100644 index 000000000..b701ab98e --- /dev/null +++ b/src/framework/parameters/grid.cpp @@ -0,0 +1,578 @@ +#include "framework/parameters/grid.h" + +#include "defaults.h" +#include "enums.h" +#include "global.h" + +#include "utils/error.h" +#include "utils/formatting.h" +#include "utils/numeric.h" + +#include "metrics/kerr_schild.h" +#include "metrics/kerr_schild_0.h" +#include "metrics/minkowski.h" +#include "metrics/qkerr_schild.h" +#include "metrics/qspherical.h" +#include "metrics/spherical.h" + +#include "framework/parameters/parameters.h" + +#include + +#if defined(MPI_ENABLED) + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ntt { + namespace params { + + template + auto get_dx0_V0(const std::vector& resolution, + const boundaries_t& extent, + const std::map& params) + -> std::pair { + const auto metric = M(resolution, extent, params); + const auto dx0 = metric.dxMin(); + coord_t x_corner { ZERO }; + for (auto d { 0u }; d < M::Dim; ++d) { + x_corner[d] = HALF; + } + const auto V0 = metric.sqrt_det_h(x_corner); + return { dx0, V0 }; + } + + auto GetBoundaryConditions(SimulationParams* params, + const SimEngine& engine_enum, + Dimension dim, + const Coord& coord_enum, + const toml::value& toml_data) + -> std::tuple, boundaries_t> { + auto flds_bc = toml::find>>( + toml_data, + "grid", + "boundaries", + "fields"); + { + raise::ErrorIf(flds_bc.empty() || flds_bc.size() > 3, + "invalid `grid.boundaries.fields`", + HERE); + params->promiseToDefine("grid.boundaries.fields"); + auto atm_defined = false; + for (const auto& bcs : flds_bc) { + for (const auto& bc : bcs) { + if (fmt::toLower(bc) == "match") { + params->promiseToDefine("grid.boundaries.match.ds"); + } + if (fmt::toLower(bc) == "atmosphere") { + raise::ErrorIf(atm_defined, + "ATMOSPHERE is only allowed in one direction", + HERE); + atm_defined = true; + params->promiseToDefine("grid.boundaries.atmosphere.temperature"); + params->promiseToDefine("grid.boundaries.atmosphere.density"); + params->promiseToDefine("grid.boundaries.atmosphere.height"); + params->promiseToDefine("grid.boundaries.atmosphere.ds"); + params->promiseToDefine("grid.boundaries.atmosphere.species"); + params->promiseToDefine("grid.boundaries.atmosphere.g"); + } + } + } + } + + auto prtl_bc = toml::find>>( + toml_data, + "grid", + "boundaries", + "particles"); + { + raise::ErrorIf(prtl_bc.empty() || prtl_bc.size() > 3, + "invalid `grid.boundaries.particles`", + HERE); + params->promiseToDefine("grid.boundaries.particles"); + auto atm_defined = false; + for (const auto& bcs : prtl_bc) { + for (const auto& bc : bcs) { + if (fmt::toLower(bc) == "absorb") { + params->promiseToDefine("grid.boundaries.absorb.ds"); + } + if (fmt::toLower(bc) == "atmosphere") { + raise::ErrorIf(atm_defined, + "ATMOSPHERE is only allowed in one direction", + HERE); + atm_defined = true; + params->promiseToDefine("grid.boundaries.atmosphere.temperature"); + params->promiseToDefine("grid.boundaries.atmosphere.density"); + params->promiseToDefine("grid.boundaries.atmosphere.height"); + params->promiseToDefine("grid.boundaries.atmosphere.ds"); + params->promiseToDefine("grid.boundaries.atmosphere.species"); + params->promiseToDefine("grid.boundaries.atmosphere.g"); + } + } + } + } + std::vector> flds_bc_enum; + std::vector> prtl_bc_enum; + if (coord_enum == Coord::Cartesian) { + raise::ErrorIf(flds_bc.size() != (std::size_t)dim, + "invalid `grid.boundaries.fields`", + HERE); + raise::ErrorIf(prtl_bc.size() != (std::size_t)dim, + "invalid `grid.boundaries.particles`", + HERE); + for (auto d { 0u }; d < (dim_t)dim; ++d) { + flds_bc_enum.emplace_back(); + prtl_bc_enum.emplace_back(); + const auto& fbc = flds_bc[d]; + const auto& pbc = prtl_bc[d]; + raise::ErrorIf(fbc.empty() || fbc.size() > 2, + "invalid `grid.boundaries.fields`", + HERE); + raise::ErrorIf(pbc.empty() || pbc.size() > 2, + "invalid `grid.boundaries.particles`", + HERE); + auto fbc_enum = FldsBC::pick(fmt::toLower(fbc[0]).c_str()); + auto pbc_enum = PrtlBC::pick(fmt::toLower(pbc[0]).c_str()); + if (fbc.size() == 1) { + raise::ErrorIf(fbc_enum != FldsBC::PERIODIC, + "invalid `grid.boundaries.fields`", + HERE); + flds_bc_enum.back().emplace_back(FldsBC::PERIODIC); + flds_bc_enum.back().emplace_back(FldsBC::PERIODIC); + } else { + raise::ErrorIf(fbc_enum == FldsBC::PERIODIC, + "invalid `grid.boundaries.fields`", + HERE); + flds_bc_enum.back().emplace_back(fbc_enum); + auto fbc_enum = FldsBC::pick(fmt::toLower(fbc[1]).c_str()); + raise::ErrorIf(fbc_enum == FldsBC::PERIODIC, + "invalid `grid.boundaries.fields`", + HERE); + flds_bc_enum.back().emplace_back(fbc_enum); + } + if (pbc.size() == 1) { + raise::ErrorIf(pbc_enum != PrtlBC::PERIODIC, + "invalid `grid.boundaries.particles`", + HERE); + prtl_bc_enum.back().emplace_back(PrtlBC::PERIODIC); + prtl_bc_enum.back().emplace_back(PrtlBC::PERIODIC); + } else { + raise::ErrorIf(pbc_enum == PrtlBC::PERIODIC, + "invalid `grid.boundaries.particles`", + HERE); + prtl_bc_enum.back().emplace_back(pbc_enum); + auto pbc_enum = PrtlBC::pick(fmt::toLower(pbc[1]).c_str()); + raise::ErrorIf(pbc_enum == PrtlBC::PERIODIC, + "invalid `grid.boundaries.particles`", + HERE); + prtl_bc_enum.back().emplace_back(pbc_enum); + } + } + } else { + raise::ErrorIf(flds_bc.size() > 1, "invalid `grid.boundaries.fields`", HERE); + raise::ErrorIf(prtl_bc.size() > 1, + "invalid `grid.boundaries.particles`", + HERE); + if (engine_enum == SimEngine::SRPIC) { + raise::ErrorIf(flds_bc[0].size() != 2, + "invalid `grid.boundaries.fields`", + HERE); + flds_bc_enum.push_back( + { FldsBC::pick(fmt::toLower(flds_bc[0][0]).c_str()), + FldsBC::pick(fmt::toLower(flds_bc[0][1]).c_str()) }); + flds_bc_enum.push_back({ FldsBC::AXIS, FldsBC::AXIS }); + if (dim == Dim::_3D) { + flds_bc_enum.push_back({ FldsBC::PERIODIC, FldsBC::PERIODIC }); + } + raise::ErrorIf(prtl_bc[0].size() != 2, + "invalid `grid.boundaries.particles`", + HERE); + prtl_bc_enum.push_back( + { PrtlBC::pick(fmt::toLower(prtl_bc[0][0]).c_str()), + PrtlBC::pick(fmt::toLower(prtl_bc[0][1]).c_str()) }); + prtl_bc_enum.push_back({ PrtlBC::AXIS, PrtlBC::AXIS }); + if (dim == Dim::_3D) { + prtl_bc_enum.push_back({ PrtlBC::PERIODIC, PrtlBC::PERIODIC }); + } + } else { + raise::ErrorIf(flds_bc[0].size() != 1, + "invalid `grid.boundaries.fields`", + HERE); + raise::ErrorIf(prtl_bc[0].size() != 1, + "invalid `grid.boundaries.particles`", + HERE); + flds_bc_enum.push_back( + { FldsBC::HORIZON, FldsBC::pick(fmt::toLower(flds_bc[0][0]).c_str()) }); + flds_bc_enum.push_back({ FldsBC::AXIS, FldsBC::AXIS }); + if (dim == Dim::_3D) { + flds_bc_enum.push_back({ FldsBC::PERIODIC, FldsBC::PERIODIC }); + } + prtl_bc_enum.push_back( + { PrtlBC::HORIZON, PrtlBC::pick(fmt::toLower(prtl_bc[0][0]).c_str()) }); + prtl_bc_enum.push_back({ PrtlBC::AXIS, PrtlBC::AXIS }); + if (dim == Dim::_3D) { + prtl_bc_enum.push_back({ PrtlBC::PERIODIC, PrtlBC::PERIODIC }); + } + } + } + + raise::ErrorIf(flds_bc_enum.size() != (std::size_t)dim, + "invalid inferred `grid.boundaries.fields`", + HERE); + raise::ErrorIf(prtl_bc_enum.size() != (std::size_t)dim, + "invalid inferred `grid.boundaries.particles`", + HERE); + boundaries_t flds_bc_pairwise; + boundaries_t prtl_bc_pairwise; + for (auto d { 0u }; d < (dim_t)dim; ++d) { + raise::ErrorIf( + flds_bc_enum[d].size() != 2, + fmt::format("invalid inferred `grid.boundaries.fields[%d]`", d), + HERE); + raise::ErrorIf( + prtl_bc_enum[d].size() != 2, + fmt::format("invalid inferred `grid.boundaries.particles[%d]`", d), + HERE); + flds_bc_pairwise.emplace_back(flds_bc_enum[d][0], flds_bc_enum[d][1]); + prtl_bc_pairwise.emplace_back(prtl_bc_enum[d][0], prtl_bc_enum[d][1]); + } + return { flds_bc_pairwise, prtl_bc_pairwise }; + } + + void Boundaries::read(Dimension dim, + const Coord& coord_enum, + const boundaries_t& extent_pairwise, + const toml::value& toml_data) { + if (needs_match_boundaries) { + match_ds_array.emplace(); + if (coord_enum == Coord::Cartesian) { + auto min_extent = std::numeric_limits::max(); + for (const auto& e : extent_pairwise) { + min_extent = std::min(min_extent, e.second - e.first); + } + const auto default_ds = min_extent * defaults::bc::match::ds_frac; + try { + auto ds = toml::find(toml_data, "grid", "boundaries", "match", "ds"); + for (auto d = 0u; d < dim; ++d) { + match_ds_array->emplace_back(ds, ds); + } + } catch (...) { + try { + const auto ds = toml::find>>( + toml_data, + "grid", + "boundaries", + "match", + "ds"); + raise::ErrorIf(ds.size() != dim, + "invalid # in `grid.boundaries.match.ds`", + HERE); + for (auto d = 0u; d < dim; ++d) { + if (ds[d].size() == 1) { + match_ds_array->emplace_back(ds[d][0], ds[d][0]); + } else if (ds[d].size() == 2) { + match_ds_array->emplace_back(ds[d][0], ds[d][1]); + } else if (ds[d].empty()) { + match_ds_array->emplace_back(); + } else { + raise::Error("invalid `grid.boundaries.match.ds`", HERE); + } + } + } catch (...) { + for (auto d = 0u; d < dim; ++d) { + match_ds_array->emplace_back(default_ds, default_ds); + } + } + } + } else { + auto r_extent = extent_pairwise[0].second - extent_pairwise[0].first; + const auto ds = toml::find_or( + toml_data, + "grid", + "boundaries", + "match", + "ds", + r_extent * defaults::bc::match::ds_frac); + match_ds_array->emplace_back(ds, ds); + } + } + + if (needs_absorb_boundaries) { + if (coord_enum == Coord::Cartesian) { + auto min_extent = std::numeric_limits::max(); + for (const auto& e : extent_pairwise) { + min_extent = std::min(min_extent, e.second - e.first); + } + absorb_ds = toml::find_or(toml_data, + "grid", + "boundaries", + "absorb", + "ds", + min_extent * defaults::bc::absorb::ds_frac); + } else { + auto r_extent = extent_pairwise[0].second - extent_pairwise[0].first; + absorb_ds = toml::find_or(toml_data, + "grid", + "boundaries", + "absorb", + "ds", + r_extent * defaults::bc::absorb::ds_frac); + } + } + + if (needs_atmosphere_boundaries) { + atmosphere_temperature = toml::find(toml_data, + "grid", + "boundaries", + "atmosphere", + "temperature"); + atmosphere_height = toml::find(toml_data, + "grid", + "boundaries", + "atmosphere", + "height"); + atmosphere_density = toml::find(toml_data, + "grid", + "boundaries", + "atmosphere", + "density"); + atmosphere_ds = + toml::find_or(toml_data, "grid", "boundaries", "atmosphere", "ds", ZERO); + atmosphere_g = atmosphere_temperature.value() / atmosphere_height.value(); + atmosphere_species = toml::find>( + toml_data, + "grid", + "boundaries", + "atmosphere", + "species"); + } + } + + void Boundaries::setParams(SimulationParams* params) const { + if (needs_match_boundaries) { + params->set("grid.boundaries.match.ds", match_ds_array.value()); + } + if (needs_absorb_boundaries) { + params->set("grid.boundaries.absorb.ds", absorb_ds.value()); + } + if (needs_atmosphere_boundaries) { + params->set("grid.boundaries.atmosphere.temperature", + atmosphere_temperature.value()); + params->set("grid.boundaries.atmosphere.density", + atmosphere_density.value()); + params->set("grid.boundaries.atmosphere.height", atmosphere_height.value()); + params->set("grid.boundaries.atmosphere.ds", atmosphere_ds.value()); + params->set("grid.boundaries.atmosphere.g", atmosphere_g.value()); + params->set("grid.boundaries.atmosphere.species", + atmosphere_species.value()); + } + } + + void Grid::read(const SimEngine& engine_enum, const toml::value& toml_data) { + /* domain decomposition ------------------------------------------------ */ + int default_ndomains = 1; +#if defined(MPI_ENABLED) + raise::ErrorIf(MPI_Comm_size(MPI_COMM_WORLD, &default_ndomains) != MPI_SUCCESS, + "MPI_Comm_size failed", + HERE); +#endif + number_of_domains = toml::find_or(toml_data, + "simulation", + "domain", + "number", + (unsigned int)default_ndomains); + + domain_decomposition = toml::find_or>( + toml_data, + "simulation", + "domain", + "decomposition", + std::vector { -1, -1, -1 }); + + /* resolution and dimension ------------------------------------------- */ + resolution = toml::find>(toml_data, "grid", "resolution"); + raise::ErrorIf(resolution->empty() or resolution->size() > 3, + "invalid `grid.resolution`", + HERE); + dim = static_cast(resolution->size()); + + if (domain_decomposition->size() > dim.value()) { + domain_decomposition->erase( + domain_decomposition->begin() + static_cast(dim.value()), + domain_decomposition->end()); + } + raise::ErrorIf(domain_decomposition->size() != dim.value(), + "invalid `simulation.domain.decomposition`", + HERE); + + /* metric and coordinates -------------------------------------------- */ + metric_enum = Metric::pick( + fmt::toLower(toml::find(toml_data, "grid", "metric", "metric")) + .c_str()); + metric_params.emplace(); + metric_params_short_.emplace(); + std::string coord; + if (metric_enum == Metric::Minkowski) { + raise::ErrorIf(engine_enum != SimEngine::SRPIC, + "minkowski metric is only supported for SRPIC", + HERE); + coord = "cart"; + } else if (metric_enum == Metric::QKerr_Schild or + metric_enum == Metric::QSpherical) { + // quasi-spherical geometry + raise::ErrorIf(dim.value() == Dim::_1D, + "not enough dimensions for qspherical geometry", + HERE); + raise::ErrorIf(dim.value() == Dim::_3D, + "3D not implemented for qspherical geometry", + HERE); + coord = "qsph"; + (*metric_params)["qsph_r0"] = toml::find_or(toml_data, + "grid", + "metric", + "qsph_r0", + defaults::qsph::r0); + (*metric_params)["qsph_h"] = toml::find_or(toml_data, + "grid", + "metric", + "qsph_h", + defaults::qsph::h); + } else { + // spherical geometry + raise::ErrorIf(dim.value() == Dim::_1D, + "not enough dimensions for spherical geometry", + HERE); + raise::ErrorIf(dim.value() == Dim::_3D, + "3D not implemented for spherical geometry", + HERE); + coord = "sph"; + } + if ((engine_enum == SimEngine::GRPIC) && + (metric_enum != Metric::Kerr_Schild_0)) { + const auto ks_a = toml::find_or(toml_data, + "grid", + "metric", + "ks_a", + defaults::ks::a); + (*metric_params)["ks_a"] = ks_a; + (*metric_params)["ks_rh"] = ONE + math::sqrt(ONE - SQR(ks_a)); + } + coord_enum = Coord::pick(coord.c_str()); + + /* extent ------------------------------------------------------------- */ + extent = toml::find>>(toml_data, + "grid", + "extent"); + extent_pairwise_.emplace(); + if (extent->size() > dim.value()) { + extent->erase(extent->begin() + static_cast(dim.value()), + extent->end()); + } + raise::ErrorIf(extent->at(0).size() != 2, "invalid `grid.extent[0]`", HERE); + if (coord_enum != Coord::Cartesian) { + raise::ErrorIf(extent->size() > 1, + "invalid `grid.extent` for non-cartesian geometry", + HERE); + extent->push_back({ ZERO, constant::PI }); + if (dim.value() == Dim::_3D) { + extent->push_back({ ZERO, TWO * constant::PI }); + } + } + raise::ErrorIf(extent->size() != dim.value(), + "invalid inferred `grid.extent`", + HERE); + for (auto d { 0u }; d < (dim_t)(dim.value()); ++d) { + raise::ErrorIf(extent->at(d).size() != 2, + fmt::format("invalid inferred `grid.extent[%d]`", d), + HERE); + extent_pairwise_->emplace_back(extent->at(d)[0], extent->at(d)[1]); + } + + /* metric parameters ------------------------------------------------------ */ + if (coord_enum == Coord::Qspherical) { + (*metric_params_short_)["r0"] = (*metric_params)["qsph_r0"]; + (*metric_params_short_)["h"] = (*metric_params)["qsph_h"]; + } + if ((engine_enum == SimEngine::GRPIC) && + (metric_enum != Metric::Kerr_Schild_0)) { + (*metric_params_short_)["a"] = (*metric_params)["ks_a"]; + } + // set("grid.metric.params", params); + + std::pair dx0_V0; + if (metric_enum == Metric::Minkowski) { + if (dim == Dim::_1D) { + dx0_V0 = get_dx0_V0>( + resolution.value(), + extent_pairwise_.value(), + metric_params_short_.value()); + } else if (dim == Dim::_2D) { + dx0_V0 = get_dx0_V0>( + resolution.value(), + extent_pairwise_.value(), + metric_params_short_.value()); + } else { + dx0_V0 = get_dx0_V0>( + resolution.value(), + extent_pairwise_.value(), + metric_params_short_.value()); + } + } else if (metric_enum == Metric::Spherical) { + dx0_V0 = get_dx0_V0>( + resolution.value(), + extent_pairwise_.value(), + metric_params_short_.value()); + } else if (metric_enum == Metric::QSpherical) { + dx0_V0 = get_dx0_V0>( + resolution.value(), + extent_pairwise_.value(), + metric_params_short_.value()); + } else if (metric_enum == Metric::Kerr_Schild) { + dx0_V0 = get_dx0_V0>( + resolution.value(), + extent_pairwise_.value(), + metric_params_short_.value()); + } else if (metric_enum == Metric::Kerr_Schild_0) { + dx0_V0 = get_dx0_V0>( + resolution.value(), + extent_pairwise_.value(), + metric_params_short_.value()); + } else if (metric_enum == Metric::QKerr_Schild) { + dx0_V0 = get_dx0_V0>( + resolution.value(), + extent_pairwise_.value(), + metric_params_short_.value()); + } + auto [dx0, V0] = dx0_V0; + scale_dx0 = dx0; + scale_V0 = V0; + } + + void Grid::setParams(SimulationParams* params) const { + params->set("simulation.domain.number", number_of_domains.value()); + params->set("simulation.domain.decomposition", domain_decomposition.value()); + + params->set("grid.resolution", resolution.value()); + params->set("grid.dim", dim.value()); + params->set("grid.metric.metric", metric_enum); + params->set("grid.metric.coord", coord_enum); + for (const auto& [key, value] : metric_params.value()) { + params->set("grid.metric." + key, value); + } + params->set("grid.metric.params", metric_params_short_.value()); + params->set("grid.extent", extent_pairwise_.value()); + + params->set("scales.dx0", scale_dx0.value()); + params->set("scales.V0", scale_V0.value()); + } + + } // namespace params +} // namespace ntt diff --git a/src/framework/parameters/grid.h b/src/framework/parameters/grid.h new file mode 100644 index 000000000..3aa6aa090 --- /dev/null +++ b/src/framework/parameters/grid.h @@ -0,0 +1,89 @@ +/** + * @file framework/parameters/grid.h + * @brief Auxiliary functions for reading in grid/box parameters + * @implements + * - ntt::params::Boundaries + * - ntt::params::GetBoundaryConditions -> (boundaries_t, boundaries_t) + * @cpp: + * - grid.cpp + * @namespaces: + * - ntt::params:: + */ +#ifndef FRAMEWORK_PARAMETERS_GRID_H +#define FRAMEWORK_PARAMETERS_GRID_H + +#include "enums.h" +#include "global.h" + +#include "framework/parameters/parameters.h" + +#include + +#include +#include +#include +#include + +namespace ntt { + namespace params { + + struct Boundaries { + const bool needs_match_boundaries; + std::optional> match_ds_array; + + const bool needs_absorb_boundaries; + std::optional absorb_ds; + + const bool needs_atmosphere_boundaries; + std::optional atmosphere_temperature; + std::optional atmosphere_height; + std::optional atmosphere_density; + std::optional atmosphere_g; + std::optional atmosphere_ds; + std::optional> atmosphere_species; + + Boundaries(bool needs_match, bool needs_absorb, bool needs_atmosphere) + : needs_match_boundaries { needs_match } + , needs_absorb_boundaries { needs_absorb } + , needs_atmosphere_boundaries { needs_atmosphere } {} + + void read(Dimension, + const Coord&, + const boundaries_t&, + const toml::value&); + void setParams(SimulationParams*) const; + }; + + struct Grid { + std::optional number_of_domains; + std::optional> domain_decomposition; + + std::optional> resolution; + std::optional dim; + + std::optional>> extent; + std::optional> extent_pairwise_; + + Metric metric_enum = Metric::INVALID; + Coord coord_enum = Coord::INVALID; + std::optional> metric_params; + std::optional> metric_params_short_; + + std::optional scale_dx0; + std::optional scale_V0; + + void read(const SimEngine&, const toml::value&); + void setParams(SimulationParams*) const; + }; + + auto GetBoundaryConditions(SimulationParams* params, + const SimEngine&, + Dimension, + const Coord&, + const toml::value&) + -> std::tuple, boundaries_t>; + + } // namespace params +} // namespace ntt + +#endif // FRAMEWORK_PARAMETERS_GRID_H diff --git a/src/framework/parameters/output.cpp b/src/framework/parameters/output.cpp new file mode 100644 index 000000000..badb6d9bd --- /dev/null +++ b/src/framework/parameters/output.cpp @@ -0,0 +1,212 @@ +#include "framework/parameters/output.h" + +#include "defaults.h" +#include "global.h" + +#include "utils/error.h" +#include "utils/log.h" + +#include "framework/parameters/parameters.h" + +#include + +#include +#include +#include + +namespace ntt { + namespace params { + + void Output::read(Dimension dim, std::size_t nspec, const toml::value& toml_data) { + format = toml::find_or(toml_data, "output", "format", defaults::output::format); + global_interval = toml::find_or(toml_data, + "output", + "interval", + defaults::output::interval); + global_interval_time = toml::find_or(toml_data, + "output", + "interval_time", + -1.0); + raise::ErrorIf( + not toml::find_or(toml_data, "output", "separate_files", true), + "separate_files=false is deprecated", + HERE); + + categories.emplace(); + for (const auto& category : { "fields", "particles", "spectra", "stats" }) { + const auto q_int = toml::find_or(toml_data, + "output", + category, + "interval", + 0); + const auto q_int_time = toml::find_or(toml_data, + "output", + category, + "interval_time", + -1.0); + (*categories)[category].enable = toml::find_or(toml_data, + "output", + category, + "enable", + true); + if ((q_int == 0) and (q_int_time == -1.0)) { + (*categories)[category].interval = global_interval.value(); + (*categories)[category].interval_time = global_interval_time.value(); + } else { + (*categories)[category].interval = q_int; + (*categories)[category].interval_time = q_int_time; + } + } + + /* Fields --------------------------------------------------------------- */ + const auto flds_out = toml::find_or(toml_data, + "output", + "fields", + "quantities", + std::vector {}); + const auto custom_flds_out = toml::find_or(toml_data, + "output", + "fields", + "custom", + std::vector {}); + if (flds_out.empty()) { + raise::Warning("No fields output specified", HERE); + } + fields_quantities = flds_out; + fields_custom_quantities = custom_flds_out; + fields_mom_smooth = toml::find_or(toml_data, + "output", + "fields", + "mom_smooth", + defaults::output::mom_smooth); + fields_downsampling.emplace(); + try { + auto field_dwn_ = toml::find>(toml_data, + "output", + "fields", + "downsampling"); + for (const auto& dwn : field_dwn_) { + fields_downsampling->push_back(dwn); + } + } catch (...) { + try { + auto field_dwn_ = toml::find(toml_data, + "output", + "fields", + "downsampling"); + for (auto i = 0u; i < dim; ++i) { + fields_downsampling->push_back(field_dwn_); + } + } catch (...) { + for (auto i = 0u; i < dim; ++i) { + fields_downsampling->push_back(1u); + } + } + } + raise::ErrorIf(fields_downsampling->size() > 3, + "invalid `output.fields.downsampling`", + HERE); + if (fields_downsampling->size() > dim) { + fields_downsampling->erase(fields_downsampling->begin() + dim, + fields_downsampling->end()); + } + for (const auto& dwn : fields_downsampling.value()) { + raise::ErrorIf(dwn == 0, "downsampling factor must be nonzero", HERE); + } + + /* Particles ------------------------------------------------------------ */ + auto all_specs = std::vector {}; + for (auto i = 0u; i < nspec; ++i) { + all_specs.push_back(static_cast(i + 1)); + } + particles_species = toml::find_or(toml_data, + "output", + "particles", + "species", + all_specs); + particles_stride = toml::find_or(toml_data, + "output", + "particles", + "stride", + defaults::output::prtl_stride); + + /* Spectra -------------------------------------------------------------- */ + spectra_e_min = toml::find_or(toml_data, + "output", + "spectra", + "e_min", + defaults::output::spec_emin); + spectra_e_max = toml::find_or(toml_data, + "output", + "spectra", + "e_max", + defaults::output::spec_emax); + spectra_log_bins = toml::find_or(toml_data, + "output", + "spectra", + "log_bins", + defaults::output::spec_log); + spectra_n_bins = toml::find_or(toml_data, + "output", + "spectra", + "n_bins", + defaults::output::spec_nbins); + + /* Stats ---------------------------------------------------------------- */ + stats_quantities = toml::find_or(toml_data, + "output", + "stats", + "quantities", + defaults::output::stats_quantities); + stats_custom_quantities = toml::find_or(toml_data, + "output", + "stats", + "custom", + std::vector {}); + + /* Debug ---------------------------------------------------------------- */ + debug_as_is = toml::find_or(toml_data, "output", "debug", "as_is", false); + debug_ghosts = toml::find_or(toml_data, "output", "debug", "ghosts", false); + if (debug_ghosts.value()) { + for (const auto& dwn : fields_downsampling.value()) { + raise::ErrorIf( + dwn != 1, + "full resolution required when outputting with ghost cells", + HERE); + } + } + } + + void Output::setParams(SimulationParams* params) const { + params->set("output.format", format.value()); + params->set("output.interval", global_interval.value()); + params->set("output.interval_time", global_interval_time.value()); + for (const auto& [category, cat_params] : categories.value()) { + params->set("output." + category + ".enable", cat_params.enable); + params->set("output." + category + ".interval", cat_params.interval); + params->set("output." + category + ".interval_time", + cat_params.interval_time); + } + + params->set("output.fields.quantities", fields_quantities.value()); + params->set("output.fields.custom", fields_custom_quantities.value()); + params->set("output.fields.mom_smooth", fields_mom_smooth.value()); + params->set("output.fields.downsampling", fields_downsampling.value()); + + params->set("output.particles.species", particles_species.value()); + params->set("output.particles.stride", particles_stride.value()); + + params->set("output.spectra.e_min", spectra_e_min.value()); + params->set("output.spectra.e_max", spectra_e_max.value()); + params->set("output.spectra.log_bins", spectra_log_bins.value()); + params->set("output.spectra.n_bins", spectra_n_bins.value()); + + params->set("output.stats.quantities", stats_quantities.value()); + params->set("output.stats.custom", stats_custom_quantities.value()); + + params->set("output.debug.as_is", debug_as_is.value()); + params->set("output.debug.ghosts", debug_ghosts.value()); + } + + } // namespace params +} // namespace ntt diff --git a/src/framework/parameters/output.h b/src/framework/parameters/output.h new file mode 100644 index 000000000..94afff324 --- /dev/null +++ b/src/framework/parameters/output.h @@ -0,0 +1,70 @@ +/** + * @file framework/parameters/output.h + * @brief Auxiliary functions for reading in output parameters + * @implements + * - ntt::params::Output + * - ntt::params::OutputCategory + * @cpp: + * - output.cpp + * @namespaces: + * - ntt::params:: + */ +#ifndef FRAMEWORK_PARAMETERS_OUTPUT_H +#define FRAMEWORK_PARAMETERS_OUTPUT_H + +#include "global.h" + +#include "framework/parameters/parameters.h" + +#include + +#include +#include +#include +#include +#include + +namespace ntt { + namespace params { + + struct OutputCategory { + bool enable; + timestep_t interval; + simtime_t interval_time; + }; + + struct Output { + std::optional format; + + std::optional global_interval; + std::optional global_interval_time; + + std::optional> categories; + + std::optional> fields_quantities; + std::optional> fields_custom_quantities; + std::optional fields_mom_smooth; + std::optional> fields_downsampling; + + std::optional> particles_species; + std::optional particles_stride; + + std::optional spectra_e_min; + std::optional spectra_e_max; + std::optional spectra_log_bins; + std::optional spectra_n_bins; + + std::optional> stats_quantities; + std::optional> stats_custom_quantities; + + std::optional debug_as_is; + std::optional debug_ghosts; + + void read(Dimension, std::size_t, const toml::value&); + void setParams(SimulationParams*) const; + }; + + } // namespace params +} // namespace ntt + +#endif // FRAMEWORK_PARAMETERS_OUTPUT_H diff --git a/src/framework/parameters/parameters.cpp b/src/framework/parameters/parameters.cpp new file mode 100644 index 000000000..e56c68e5e --- /dev/null +++ b/src/framework/parameters/parameters.cpp @@ -0,0 +1,362 @@ +#include "framework/parameters/parameters.h" + +#include "defaults.h" +#include "enums.h" +#include "global.h" + +#include "arch/mpi_aliases.h" +#include "utils/error.h" +#include "utils/formatting.h" +#include "utils/numeric.h" + +#include "framework/containers/species.h" +#include "framework/parameters/algorithms.h" +#include "framework/parameters/extra.h" +#include "framework/parameters/grid.h" +#include "framework/parameters/output.h" +#include "framework/parameters/particles.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace ntt { + + /* + * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + * Parameters that must not be changed during the checkpoint restart + * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + */ + void SimulationParams::setImmutableParams(const toml::value& toml_data) { + /* [simulation] --------------------------------------------------------- */ + const auto engine_enum = SimEngine::pick( + fmt::toLower(toml::find(toml_data, "simulation", "engine")).c_str()); + set("simulation.engine", engine_enum); + + /* grid and decomposition ------------------------------------------------ */ + params::Grid grid_params {}; + grid_params.read(engine_enum, toml_data); + grid_params.setParams(this); + + /* [scales] ------------------------------------------------------------- */ + const auto larmor0 = toml::find(toml_data, "scales", "larmor0"); + const auto skindepth0 = toml::find(toml_data, "scales", "skindepth0"); + raise::ErrorIf(larmor0 <= ZERO || skindepth0 <= ZERO, + "larmor0 and skindepth0 must be positive", + HERE); + set("scales.larmor0", larmor0); + set("scales.skindepth0", skindepth0); + set("scales.sigma0", SQR(skindepth0 / larmor0)); + set("scales.B0", ONE / larmor0); + set("scales.omegaB0", ONE / larmor0); + + /* [particles] ---------------------------------------------------------- */ + const auto ppc0 = toml::find(toml_data, "particles", "ppc0"); + set("particles.ppc0", ppc0); + raise::ErrorIf(ppc0 <= 0.0, "ppc0 must be positive", HERE); + set("particles.use_weights", + toml::find_or(toml_data, "particles", "use_weights", false)); + const auto global_clearing_interval = toml::find_or( + toml_data, + "particles", + "clear_interval", + defaults::clear_interval); + set("particles.clear_interval", global_clearing_interval); + const auto global_spatial_sorting_interval = toml::find_or( + toml_data, + "particles", + "spatial_sorting_interval", + 0u); + set("particles.spatial_sorting_interval", global_spatial_sorting_interval); + + set("scales.n0", ppc0 / get("scales.V0")); + set("scales.q0", get("scales.V0") / (ppc0 * SQR(skindepth0))); + + /* [particles.species] -------------------------------------------------- */ + std::vector species; + const auto species_tab = toml::find_or(toml_data, + "particles", + "species", + toml::array {}); + set("particles.nspec", species_tab.size()); + + spidx_t idx = 1; + for (const auto& sp : species_tab) { + species.emplace_back( + params::GetParticleSpecies(this, + engine_enum, + idx, + sp, + global_clearing_interval, + global_spatial_sorting_interval)); + idx += 1; + } + set("particles.species", species); + } + + /* + * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + * Parameters that may be changed during the checkpoint restart + * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + */ + void SimulationParams::setMutableParams(const toml::value& toml_data) { + const auto engine_enum = get("simulation.engine"); + const auto coord_enum = get("grid.metric.coord"); + const auto dim = get("grid.dim"); + const auto extent_pairwise = get>("grid.extent"); + + /* [simulation] --------------------------------------------------------- */ + set("simulation.name", + toml::find(toml_data, "simulation", "name")); + set("simulation.runtime", + toml::find(toml_data, "simulation", "runtime")); + + /* [grid.boundaraies] --------------------------------------------------- */ + const auto [flds_bc, prtl_bc] = params::GetBoundaryConditions(this, + engine_enum, + dim, + coord_enum, + toml_data); + set("grid.boundaries.fields", flds_bc); + set("grid.boundaries.particles", prtl_bc); + + /* [particles] ---------------------------------------------------------- */ + const auto species_tab = toml::find_or(toml_data, + "particles", + "species", + toml::array {}); + std::vector species = get>( + "particles.species"); + raise::ErrorIf(species_tab.size() != species.size(), + "number of species changed after restart", + HERE); + + std::vector new_species; + + spidx_t idxM1 = 0; + for (const auto& sp : species_tab) { + const auto maxnpart_real = toml::find(sp, "maxnpart"); + const auto maxnpart = static_cast(maxnpart_real); + const auto& particle_species = species[idxM1]; + new_species.emplace_back(particle_species.index(), + particle_species.label(), + particle_species.mass(), + particle_species.charge(), + maxnpart, + particle_species.clearing_interval(), + particle_species.spatial_sorting_interval(), + particle_species.pusher(), + particle_species.use_tracking(), + particle_species.radiative_drag_flags(), + particle_species.emission_policy_flag(), + particle_species.npld_r(), + particle_species.npld_i()); + idxM1++; + } + set("particles.species", new_species); + + /* [output] ------------------------------------------------------------- */ + params::Output output_params; + output_params.read(dim, get("particles.nspec"), toml_data); + output_params.setParams(this); + + /* [checkpoint] --------------------------------------------------------- */ + set("checkpoint.interval", + toml::find_or(toml_data, + "checkpoint", + "interval", + defaults::checkpoint::interval)); + set("checkpoint.interval_time", + toml::find_or(toml_data, "checkpoint", "interval_time", -1.0)); + set("checkpoint.keep", + toml::find_or(toml_data, "checkpoint", "keep", defaults::checkpoint::keep)); + auto walltime_str = toml::find_or(toml_data, + "checkpoint", + "walltime", + defaults::checkpoint::walltime); + if (walltime_str.empty()) { + walltime_str = defaults::checkpoint::walltime; + } + set("checkpoint.walltime", walltime_str); + + const auto checkpoint_write_path = toml::find_or( + toml_data, + "checkpoint", + "write_path", + fmt::format(defaults::checkpoint::write_path.c_str(), + get("simulation.name").c_str())); + set("checkpoint.write_path", checkpoint_write_path); + set("checkpoint.read_path", + toml::find_or(toml_data, "checkpoint", "read_path", checkpoint_write_path)); + + /* [diagnostics] -------------------------------------------------------- */ + set("diagnostics.interval", + toml::find_or(toml_data, "diagnostics", "interval", defaults::diag::interval)); + set("diagnostics.blocking_timers", + toml::find_or(toml_data, "diagnostics", "blocking_timers", false)); + set("diagnostics.colored_stdout", + toml::find_or(toml_data, "diagnostics", "colored_stdout", false)); + set("diagnostics.log_level", + toml::find_or(toml_data, "diagnostics", "log_level", defaults::diag::log_level)); + + /* inferred variables --------------------------------------------------- */ + params::Boundaries boundaries_params { + isPromised("grid.boundaries.match.ds"), + isPromised("grid.boundaries.absorb.ds"), + isPromised("grid.boundaries.atmosphere.temperature") + }; + boundaries_params.read(dim, coord_enum, extent_pairwise, toml_data); + boundaries_params.setParams(this); + + /* [algorithms] --------------------------------------------------------- */ + params::Algorithms alg_params {}; + const std::map alg_extra_flags = { + { "gr", engine_enum == SimEngine::GRPIC }, + { "gca", isPromised("algorithms.gca.e_ovr_b_max") }, + }; + alg_params.read(get("scales.dx0"), alg_extra_flags, toml_data); + alg_params.setParams(alg_extra_flags, this); + + /* extra physics ------------------------------------------------------ */ + params::Extra extra_params {}; + const std::map extra_extra_flags = { + { "synchrotron_drag",isPromised("radiation.drag.synchrotron.gamma_rad") }, + { "compton_drag", isPromised("radiation.drag.compton.gamma_rad") }, + { "synchrotron_emission", + isPromised("radiation.emission.synchrotron.photon_species") }, + { "compton_emission", isPromised("radiation.emission.compton.photon_species") } + }; + extra_params.read(extra_extra_flags, toml_data, this); + extra_params.setParams(extra_extra_flags, this); + + // @TODO: disabling stats for non-Cartesian + if (coord_enum != Coord::Cartesian) { + set("output.stats.enable", false); + } + } + + void SimulationParams::setSetupParams(const toml::value& toml_data) { + /* [setup] -------------------------------------------------------------- */ + const auto setup = toml::find_or(toml_data, "setup", toml::table {}); + for (const auto& [key, val] : setup) { + if (val.is_boolean()) { + set("setup." + key, (bool)(val.as_boolean())); + } else if (val.is_integer()) { + set("setup." + key, (int)(val.as_integer())); + } else if (val.is_floating()) { + set("setup." + key, (real_t)(val.as_floating())); + } else if (val.is_string()) { + set("setup." + key, (std::string)(val.as_string())); + } else if (val.is_array()) { + const auto val_arr = val.as_array(); + if (val_arr.empty()) { + continue; + } else { + if (val_arr[0].is_integer()) { + std::vector vec; + for (const auto& v : val_arr) { + vec.push_back(static_cast(v.as_integer())); + } + set("setup." + key, vec); + } else if (val_arr[0].is_floating()) { + std::vector vec; + for (const auto& v : val_arr) { + vec.push_back(static_cast(v.as_floating())); + } + set("setup." + key, vec); + } else if (val_arr[0].is_boolean()) { + std::vector vec; + for (const auto& v : val_arr) { + vec.push_back(v.as_boolean()); + } + set("setup." + key, vec); + } else if (val_arr[0].is_string()) { + std::vector vec; + for (const auto& v : val_arr) { + vec.push_back(v.as_string()); + } + set("setup." + key, vec); + } else if (val_arr[0].is_array()) { + if (val_arr[0].as_array().empty()) { + raise::Error("empty inner arrays not allowed in [setup]", HERE); + } else if (val_arr[0][0].is_integer()) { + std::vector> vec; + for (const auto& v1 : val_arr) { + std::vector inner_vec; + for (const auto& v2 : v1.as_array()) { + inner_vec.push_back(static_cast(v2.as_integer())); + } + vec.push_back(inner_vec); + } + set("setup." + key, vec); + } else if (val_arr[0][0].is_floating()) { + std::vector> vec; + for (const auto& v1 : val_arr) { + std::vector inner_vec; + for (const auto& v2 : v1.as_array()) { + inner_vec.push_back(static_cast(v2.as_floating())); + } + vec.push_back(inner_vec); + } + set("setup." + key, vec); + } else if (val_arr[0][0].is_boolean()) { + std::vector> vec; + for (const auto& v : val_arr) { + std::vector inner_vec; + for (const auto& v2 : v.as_array()) { + inner_vec.push_back(v2.as_boolean()); + } + vec.push_back(inner_vec); + } + set("setup." + key, vec); + } else if (val_arr[0][0].is_string()) { + std::vector> vec; + for (const auto& v : val_arr) { + std::vector inner_vec; + for (const auto& v2 : v.as_array()) { + inner_vec.push_back(v2.as_string()); + } + vec.push_back(inner_vec); + } + set("setup." + key, vec); + } else if (val_arr[0][0].is_array()) { + raise::Error("up to 2D arrays allowed in [setup]", HERE); + } + } else { + raise::Error("invalid setup variable type", HERE); + } + } + } + } + } + + void SimulationParams::setCheckpointParams(bool is_resuming, + timestep_t start_step, + simtime_t start_time) { + set("checkpoint.is_resuming", is_resuming); + set("checkpoint.start_step", start_step); + set("checkpoint.start_time", start_time); + } + + void SimulationParams::checkPromises() const { + raise::ErrorIf(!promisesFulfilled(), + "Have not defined all the necessary variables", + HERE); + } + + void SimulationParams::saveTOML(const std::string& path, simtime_t time) const { + CallOnce([&]() { + std::ofstream metadata; + metadata.open(path); + metadata << fmt::format("[metadata]\n time = %f\n\n", time) << data() + << '\n'; + metadata.close(); + }); + } + +} // namespace ntt diff --git a/src/framework/parameters.h b/src/framework/parameters/parameters.h similarity index 82% rename from src/framework/parameters.h rename to src/framework/parameters/parameters.h index 0af4fa405..0c9a7d847 100644 --- a/src/framework/parameters.h +++ b/src/framework/parameters/parameters.h @@ -1,5 +1,5 @@ /** - * @file framework/parameters.h + * @file framework/parameters/parameters.h * @brief Structure for defining and holding initial simulation parameters * @implements * - ntt::SimulationParams : ntt::Parameters @@ -14,11 +14,12 @@ * @note A proper metric is used to infer the minimum cell size/volume etc. */ -#ifndef FRAMEWORK_PARAMETERS_H -#define FRAMEWORK_PARAMETERS_H +#ifndef FRAMEWORK_PARAMETERS_PARAMETERS_H +#define FRAMEWORK_PARAMETERS_PARAMETERS_H #include "utils/param_container.h" -#include "utils/toml.h" + +#include #include @@ -31,9 +32,9 @@ namespace ntt { SimulationParams(const SimulationParams&) = default; SimulationParams& operator=(const SimulationParams& other) { - vars = std::move(other.vars); - promises = std::move(other.promises); - raw_data = std::move(other.raw_data); + vars = other.vars; + promises = other.promises; + raw_data = other.raw_data; return *this; } @@ -62,4 +63,4 @@ namespace ntt { } // namespace ntt -#endif // FRAMEWORK_PARAMETERS_H +#endif // FRAMEWORK_PARAMETERS_PARAMETERS_H diff --git a/src/framework/parameters/particles.cpp b/src/framework/parameters/particles.cpp new file mode 100644 index 000000000..6f1a23c03 --- /dev/null +++ b/src/framework/parameters/particles.cpp @@ -0,0 +1,229 @@ +#include "framework/parameters/particles.h" + +#include "defaults.h" +#include "enums.h" +#include "global.h" + +#include "utils/error.h" +#include "utils/formatting.h" + +#include "framework/containers/species.h" +#include "framework/parameters/parameters.h" + +#include + +#include +#include + +namespace ntt { + + namespace params { + + /* + * Auxiliary functions + */ + auto getRadiativeDragFlags(const std::string& radiative_drag_str) + -> RadiativeDragFlags { + if (fmt::toLower(radiative_drag_str) == "none") { + return RadiativeDrag::NONE; + } else { + // separate comas + RadiativeDragFlags flags = RadiativeDrag::NONE; + std::string token; + std::istringstream tokenStream(radiative_drag_str); + while (std::getline(tokenStream, token, ',')) { + const auto token_lower = fmt::toLower(token); + if (token_lower == "synchrotron") { + flags |= RadiativeDrag::SYNCHROTRON; + } else if (token_lower == "compton") { + flags |= RadiativeDrag::COMPTON; + } else { + raise::Error(fmt::format("Invalid radiative_drag value: %s", + radiative_drag_str.c_str()), + HERE); + } + } + return flags; + } + } + + auto getPusherFlags(const std::string& particle_pusher_str) + -> ParticlePusherFlags { + if (fmt::toLower(particle_pusher_str) == "none") { + return ParticlePusher::NONE; + } else { + // separate comas + ParticlePusherFlags flags = ParticlePusher::NONE; + std::string token; + std::istringstream tokenStream(particle_pusher_str); + while (std::getline(tokenStream, token, ',')) { + const auto token_lower = fmt::toLower(token); + if (token_lower == "photon") { + flags |= ParticlePusher::PHOTON; + } else if (token_lower == "boris") { + flags |= ParticlePusher::BORIS; + } else if (token_lower == "vay") { + flags |= ParticlePusher::VAY; + } else if (token_lower == "gca") { + flags |= ParticlePusher::GCA; + } else { + raise::Error(fmt::format("Invalid pusher value: %s", + particle_pusher_str.c_str()), + HERE); + } + } + if (flags & ParticlePusher::PHOTON and flags & ParticlePusher::GCA) { + raise::Error("Photon pusher cannot be used with GCA", HERE); + } + return flags; + } + } + + auto getEmissionPolicyFlag(const std::string& emission_policy_str) + -> EmissionTypeFlag { + if (fmt::toLower(emission_policy_str) == "none") { + return EmissionType::NONE; + } else if (fmt::toLower(emission_policy_str) == "synchrotron") { + return EmissionType::SYNCHROTRON; + } else if (fmt::toLower(emission_policy_str) == "compton") { + return EmissionType::COMPTON; + } else if (fmt::toLower(emission_policy_str) == "custom") { + return EmissionType::CUSTOM; + } else { + raise::Error(fmt::format("Invalid emission_policy value: %s", + emission_policy_str.c_str()), + HERE); + return EmissionType::NONE; + } + } + + auto GetParticleSpecies(SimulationParams* params, + const SimEngine& engine_enum, + spidx_t idx, + const toml::value& sp, + timestep_t global_clearing_interval, + timestep_t global_spatial_sorting_interval) + -> ParticleSpecies { + const auto label = toml::find_or(sp, + "label", + "s" + std::to_string(idx)); + const auto mass = toml::find(sp, "mass"); + const auto charge = toml::find(sp, "charge"); + raise::ErrorIf((charge != 0.0f) && (mass == 0.0f), + "mass of the charged species must be non-zero", + HERE); + const auto is_massless = (mass == 0.0f) && (charge == 0.0f); + const auto def_pusher = (is_massless ? defaults::ph_pusher + : defaults::em_pusher); + const auto maxnpart_real = toml::find(sp, "maxnpart"); + const auto maxnpart = static_cast(maxnpart_real); + const auto clearing_interval = toml::find_or( + sp, + "clear_interval", + global_clearing_interval); + const auto spatial_sorting_interval = toml::find_or( + sp, + "spatial_sorting_interval", + global_spatial_sorting_interval); + auto pusher_str = toml::find_or(sp, "pusher", std::string(def_pusher)); + const auto npayloads_real = toml::find_or(sp, + "n_payloads_real", + static_cast(0)); + const auto use_tracking = toml::find_or(sp, "tracking", false); + auto npayloads_int = toml::find_or(sp, + "n_payloads_int", + static_cast(0)); + if (use_tracking) { +#if !defined(MPI_ENABLED) + npayloads_int += 1; +#else + npayloads_int += 2; +#endif + } + auto radiative_drag_str = toml::find_or(sp, + "radiative_drag", + std::string("default")); + + const auto radiative_drag_defaulted = (fmt::toLower(radiative_drag_str) == + "default"); + if (radiative_drag_defaulted) { + radiative_drag_str = "none"; + } + + const auto emission_policy_str = toml::find_or(sp, + "emission", + std::string("none")); + raise::ErrorIf((fmt::toLower(radiative_drag_str) != "none") && is_massless, + "radiative drag is only applicable to massive particles", + HERE); + raise::ErrorIf((fmt::toLower(pusher_str) == "photon") && !is_massless, + "photon pusher is only applicable to massless particles", + HERE); + + auto particle_pusher_flags = getPusherFlags(pusher_str); + auto radiative_drag_flags = getRadiativeDragFlags(radiative_drag_str); + auto emission_policy_flag = getEmissionPolicyFlag(emission_policy_str); + + raise::ErrorIf((emission_policy_flag == EmissionType::SYNCHROTRON or + emission_policy_flag == EmissionType::COMPTON) and + is_massless, + "Radiative emission policies are only applicable to " + "massive particles", + HERE); + + if (radiative_drag_defaulted) { + if (emission_policy_flag == EmissionType::SYNCHROTRON) { + radiative_drag_flags |= RadiativeDrag::SYNCHROTRON; + } else if (emission_policy_flag == EmissionType::COMPTON) { + radiative_drag_flags |= RadiativeDrag::COMPTON; + } + } + + if (radiative_drag_flags & RadiativeDrag::SYNCHROTRON) { + raise::ErrorIf(engine_enum != SimEngine::SRPIC, + "Synchrotron radiative drag is only supported for SRPIC", + HERE); + params->promiseToDefine("radiation.drag.synchrotron.gamma_rad"); + } + if (radiative_drag_flags & RadiativeDrag::COMPTON) { + raise::ErrorIf( + engine_enum != SimEngine::SRPIC, + "Inverse Compton radiative drag is only supported for SRPIC", + HERE); + params->promiseToDefine("radiation.drag.compton.gamma_rad"); + } + if (particle_pusher_flags & ParticlePusher::GCA) { + raise::ErrorIf(engine_enum != SimEngine::SRPIC, + "GCA pushers are only supported for SRPIC", + HERE); + params->promiseToDefine("algorithms.gca.e_ovr_b_max"); + params->promiseToDefine("algorithms.gca.larmor_max"); + } + + if (emission_policy_flag == EmissionType::SYNCHROTRON) { + params->promiseToDefine( + "radiation.emission.synchrotron.photon_species"); + params->promiseToDefine("radiation.drag.synchrotron.gamma_rad"); + } else if (emission_policy_flag == EmissionType::COMPTON) { + params->promiseToDefine("radiation.emission.compton.photon_species"); + params->promiseToDefine("radiation.drag.compton.gamma_rad"); + } + + return ParticleSpecies(idx, + label, + mass, + charge, + maxnpart, + clearing_interval, + spatial_sorting_interval, + particle_pusher_flags, + use_tracking, + radiative_drag_flags, + emission_policy_flag, + npayloads_real, + npayloads_int); + } + + } // namespace params + +} // namespace ntt diff --git a/src/framework/parameters/particles.h b/src/framework/parameters/particles.h new file mode 100644 index 000000000..21ce303ba --- /dev/null +++ b/src/framework/parameters/particles.h @@ -0,0 +1,36 @@ +/** + * @file framework/parameters/particles.h + * @brief Auxiliary functions for reading in particle species parameters + * @implements + * - ntt::params::GetParticleSpecies -> ParticleSpecies + * @cpp: + * - particles.cpp + * @namespaces: + * - ntt::params:: + */ +#ifndef FRAMEWORK_PARAMETERS_PARTICLES_H +#define FRAMEWORK_PARAMETERS_PARTICLES_H + +#include "enums.h" + +#include "framework/containers/species.h" +#include "framework/parameters/parameters.h" + +#include + +namespace ntt { + + namespace params { + + auto GetParticleSpecies(SimulationParams*, + const SimEngine&, + spidx_t, + const toml::value&, + timestep_t, + timestep_t) -> ParticleSpecies; + + } // namespace params + +} // namespace ntt + +#endif diff --git a/src/framework/simulation.cpp b/src/framework/simulation.cpp index 20dc39486..d037aae64 100644 --- a/src/framework/simulation.cpp +++ b/src/framework/simulation.cpp @@ -9,10 +9,12 @@ #include "utils/formatting.h" #include "utils/log.h" #include "utils/plog.h" -#include "utils/toml.h" + +#include #include #include +#include namespace ntt { @@ -47,9 +49,7 @@ namespace ntt { const auto res = toml::find>(raw_params, "grid", "resolution"); - raise::ErrorIf(res.size() < 1 || res.size() > 3, - "invalid `grid.resolution`", - HERE); + raise::ErrorIf(res.empty() || res.size() > 3, "invalid `grid.resolution`", HERE); m_requested_dimension = static_cast(res.size()); m_params.setRawData(raw_params); diff --git a/src/framework/simulation.h b/src/framework/simulation.h index 33750030f..9a5526d66 100644 --- a/src/framework/simulation.h +++ b/src/framework/simulation.h @@ -16,11 +16,11 @@ #include "enums.h" -#include "arch/traits.h" #include "utils/error.h" -#include "utils/toml.h" -#include "framework/parameters.h" +#include "framework/parameters/parameters.h" + +#include namespace ntt { @@ -36,12 +36,8 @@ namespace ntt { ~Simulation(); template