Initial MOCCA standalone import
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
build/
|
||||
cmake-build-*/
|
||||
*.swp
|
||||
*.swo
|
||||
__pycache__/
|
||||
@@ -0,0 +1,39 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(MOCCA VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
add_library(mocca_lib
|
||||
src/api.cpp
|
||||
src/config_bridge.cpp
|
||||
src/embedded_tables.cpp
|
||||
src/json.cpp
|
||||
src/kernel.cpp
|
||||
src/line_codec.cpp
|
||||
src/physics_engine.cpp
|
||||
)
|
||||
|
||||
target_include_directories(mocca_lib
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
target_compile_options(mocca_lib
|
||||
PRIVATE
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
)
|
||||
|
||||
add_executable(mocca
|
||||
src/cli.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(mocca PRIVATE mocca_lib)
|
||||
|
||||
install(TARGETS mocca mocca_lib)
|
||||
@@ -0,0 +1,55 @@
|
||||
# MOCCA
|
||||
|
||||
MOCCA is the standalone modern cascade application extracted from the larger refactor work.
|
||||
The name stands for `MOdern Code for CAscade`.
|
||||
|
||||
This repository contains only:
|
||||
- the modern C++ cascade implementation
|
||||
- a helper that translates legacy card decks into the modern JSON schema
|
||||
- the original literature and published source material kept for provenance
|
||||
|
||||
It intentionally does not depend on the old Python implementation or the earlier C++ refactor.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
cmake --build build -j
|
||||
```
|
||||
|
||||
This produces the `mocca` executable in `build/`.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
./build/mocca examples/fe_stat_n15.json --output result.json
|
||||
```
|
||||
|
||||
The executable reads a structured JSON input file and writes a structured JSON result artifact.
|
||||
The bundled coefficient tables are compiled into the binary, so no external runtime data files are required.
|
||||
|
||||
## Translate Legacy Decks
|
||||
|
||||
```bash
|
||||
python3 tools/card_to_json.py legacy_case.inp --output case.json
|
||||
```
|
||||
|
||||
The translator is a standalone helper built on the Python standard library only.
|
||||
It converts the historical card input into the JSON schema used by MOCCA.
|
||||
|
||||
## Project Layout
|
||||
|
||||
- `include/mocca`: public headers
|
||||
- `src`: MOCCA implementation
|
||||
- `tools/card_to_json.py`: legacy-card to JSON translator
|
||||
- `examples`: example JSON inputs
|
||||
- `docs`: high-level documentation
|
||||
- `literature`: original papers and published source material
|
||||
|
||||
## Literature
|
||||
|
||||
The `literature/` folder keeps the original papers and source material that the implementation was checked against:
|
||||
|
||||
- `Akylas_VR_1978.pdf`
|
||||
- `1-s2.0-0010465578900991-main.pdf`
|
||||
- `aama_v1_0.f`
|
||||
@@ -0,0 +1,19 @@
|
||||
# Architecture
|
||||
|
||||
MOCCA keeps a small public surface:
|
||||
|
||||
- `mocca::SimulationConfig` describes one cascade run as typed JSON-backed data.
|
||||
- `mocca::run_simulation()` validates the config, runs the kernel, and returns a structured result artifact.
|
||||
- `mocca` is the command-line wrapper around that library API.
|
||||
|
||||
Internally, the code is split into a few focused layers:
|
||||
|
||||
- `api.cpp`: JSON parsing and artifact serialization
|
||||
- `config_bridge.cpp`: conversion from the public schema into the internal kernel state
|
||||
- `physics_engine.cpp`: matrix elements, transition rates, and atomic-model preparation
|
||||
- `kernel.cpp`: cascade propagation and state/line collection
|
||||
- `line_codec.cpp`: packed line bookkeeping and decoding
|
||||
- `embedded_tables.cpp`: compiled-in coefficient tables
|
||||
|
||||
The modern public interface deliberately avoids the historical card vocabulary.
|
||||
The old deck format survives only in the optional translator helper.
|
||||
@@ -0,0 +1,18 @@
|
||||
# JSON Schema Notes
|
||||
|
||||
The MOCCA input schema is organized around physics concepts rather than card names:
|
||||
|
||||
- `atom`: atomic number, shell charges, binding energies, and mass information
|
||||
- `transitions`: explicit transition-energy inputs and optional Dirac-energy overrides
|
||||
- `capture`: initial capture distribution
|
||||
- `channels`: active monopole, dipole, quadrupole, and octupole channel settings
|
||||
- `shell_model`: subshell populations, refill behavior, polarization handling, and shell cutoffs
|
||||
- `reporting`: line filtering and output thresholds
|
||||
- `model`: low-level physical constants that remain configurable
|
||||
- `numerics`: numerical control knobs exposed by the standalone implementation
|
||||
|
||||
The output artifact mirrors that structure and adds:
|
||||
|
||||
- provenance fields such as `implementation_name`, `numerical_backend`, and `coefficient_table_id`
|
||||
- the normalized `input`
|
||||
- the computed `result`, including line catalog, state summaries, warnings, and Lyman sum
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"metadata": {
|
||||
"case_name": "fe_stat_n15"
|
||||
},
|
||||
"atom": {
|
||||
"atomic_number": 26.0,
|
||||
"effective_shell_charges": [24.0, 22.0, 17.0],
|
||||
"binding_energies_ev": [7.112e3, 8.46e2, 7.6e1],
|
||||
"atomic_mass": 56.0,
|
||||
"exact_mass_number": null
|
||||
},
|
||||
"masses": {
|
||||
"muon_electron_masses": 206.7686,
|
||||
"electron_mass_ev": 511003.4,
|
||||
"nucleon_mass_mev": 931.48
|
||||
},
|
||||
"transitions": {
|
||||
"two_p_to_one_s_energy_ev": null,
|
||||
"two_s_to_two_p_split_ev": null,
|
||||
"dirac_energies": []
|
||||
},
|
||||
"capture": {
|
||||
"mode": "statistical_l",
|
||||
"n_max": 15,
|
||||
"alpha": 0.22
|
||||
},
|
||||
"channels": {
|
||||
"case_counts": [3, 4, 4, 4],
|
||||
"monopole_shells": [1, 2, 3],
|
||||
"dipole_shells": [0, 1, 2, 3],
|
||||
"quadrupole_shells": [0, 1, 2, 3],
|
||||
"octupole_shells": [0, 1, 2, 3],
|
||||
"dipole_subshell_channels": [0, 0, 0, 0],
|
||||
"quadrupole_subshell_channels": [0, 0, 0, 0],
|
||||
"octupole_subshell_channels": [0, 0, 0, 0],
|
||||
"dipole_penetration_codes": [0, 1, 1, 1],
|
||||
"quadrupole_penetration_codes": [0, 1, 1, 1],
|
||||
"octupole_penetration_codes": [0, 1, 1, 1],
|
||||
"dipole_penetration_avg_n_cutoffs": [0, 0, 0, 0],
|
||||
"quadrupole_penetration_avg_n_cutoffs": [0, 0, 0, 0],
|
||||
"octupole_penetration_avg_n_cutoffs": [0, 0, 0, 0]
|
||||
},
|
||||
"shell_model": {
|
||||
"subshell_populations": [1, 1, 1, 1, 1, 1],
|
||||
"refill_codes": [0, 0, 0],
|
||||
"penetration_cutoffs": [1, 1, 1, 1],
|
||||
"width_k_ev": 0.0,
|
||||
"track_polarization": true
|
||||
},
|
||||
"reporting": {
|
||||
"line_energy_min_mev": 0.04,
|
||||
"line_energy_max_mev": 20.0,
|
||||
"line_intensity_threshold": 1.0e-6,
|
||||
"energy_resolution_mev": 0.0003
|
||||
},
|
||||
"model": {
|
||||
"factorial_divider": 15.0
|
||||
},
|
||||
"numerics": {
|
||||
"matrix_element_precision_digits": 120
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mocca/json.hpp"
|
||||
|
||||
namespace mocca {
|
||||
|
||||
struct DiracEnergy {
|
||||
int n{};
|
||||
int kappa{};
|
||||
double vacuum_polarization_kev{};
|
||||
double binding_kev{};
|
||||
};
|
||||
|
||||
struct NlWeight {
|
||||
int n{};
|
||||
int l{};
|
||||
double weight{};
|
||||
};
|
||||
|
||||
enum class CaptureMode {
|
||||
statistical_l,
|
||||
quadratic_l,
|
||||
explicit_l,
|
||||
explicit_nl,
|
||||
legacy_empty,
|
||||
};
|
||||
|
||||
struct MetadataConfig {
|
||||
std::string case_name;
|
||||
};
|
||||
|
||||
struct AtomConfig {
|
||||
double atomic_number{};
|
||||
std::vector<double> effective_shell_charges;
|
||||
std::vector<double> binding_energies_ev;
|
||||
double atomic_mass{};
|
||||
std::optional<double> exact_mass_number;
|
||||
};
|
||||
|
||||
struct MassConfig {
|
||||
double muon_electron_masses{206.7682827};
|
||||
double electron_mass_ev{510998.95069};
|
||||
double nucleon_mass_mev{938.272013};
|
||||
};
|
||||
|
||||
struct TransitionConfig {
|
||||
std::optional<double> two_p_to_one_s_energy_ev;
|
||||
std::optional<double> two_s_to_two_p_split_ev;
|
||||
std::vector<DiracEnergy> dirac_energies;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initial capture distribution configuration.
|
||||
*
|
||||
* The modern interface names the supported distribution families explicitly
|
||||
* instead of routing them through the historical `NOP`/`IP` control cards.
|
||||
*/
|
||||
struct CaptureConfig {
|
||||
CaptureMode mode{CaptureMode::statistical_l};
|
||||
int n_max{15};
|
||||
double alpha{0.0};
|
||||
std::vector<double> quadratic_coefficients;
|
||||
std::vector<double> l_weights;
|
||||
std::vector<NlWeight> nl_weights;
|
||||
};
|
||||
|
||||
struct ChannelConfig {
|
||||
std::vector<int> case_counts{3, 4, 4, 4};
|
||||
std::vector<int> monopole_shells{1, 2, 3};
|
||||
std::vector<int> dipole_shells{0, 1, 2, 3};
|
||||
std::vector<int> quadrupole_shells{0, 1, 2, 3};
|
||||
std::vector<int> octupole_shells{0, 1, 2, 3};
|
||||
std::vector<int> dipole_subshell_channels{0, 0, 0, 0};
|
||||
std::vector<int> quadrupole_subshell_channels{0, 0, 0, 0};
|
||||
std::vector<int> octupole_subshell_channels{0, 0, 0, 0};
|
||||
std::vector<int> dipole_penetration_codes{0, 1, 1, 1};
|
||||
std::vector<int> quadrupole_penetration_codes{0, 1, 1, 1};
|
||||
std::vector<int> octupole_penetration_codes{0, 1, 1, 1};
|
||||
std::vector<int> dipole_penetration_avg_n_cutoffs{0, 0, 0, 0};
|
||||
std::vector<int> quadrupole_penetration_avg_n_cutoffs{0, 0, 0, 0};
|
||||
std::vector<int> octupole_penetration_avg_n_cutoffs{0, 0, 0, 0};
|
||||
};
|
||||
|
||||
struct ShellModelConfig {
|
||||
std::vector<double> subshell_populations{1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
|
||||
std::vector<int> refill_codes{0, 0, 0};
|
||||
std::vector<double> penetration_cutoffs{1.0, 1.0, 1.0, 1.0};
|
||||
double width_k_ev{0.0};
|
||||
bool track_polarization{true};
|
||||
};
|
||||
|
||||
struct ReportingConfig {
|
||||
double line_energy_min_mev{0.040};
|
||||
double line_energy_max_mev{20.0};
|
||||
double line_intensity_threshold{1.0e-06};
|
||||
double energy_resolution_mev{0.000300};
|
||||
};
|
||||
|
||||
struct ModelConfig {
|
||||
double factorial_divider{15.0};
|
||||
};
|
||||
|
||||
struct NumericsConfig {
|
||||
int matrix_element_precision_digits{120};
|
||||
};
|
||||
|
||||
/**
|
||||
* Structured input schema for the new public-facing cascade interface.
|
||||
*
|
||||
* The schema is deliberately aligned with the validated cascade model while
|
||||
* removing the legacy punch-card interface. It exposes the physics controls
|
||||
* and precision settings needed by the modern kernel without leaking the
|
||||
* historical deck/card vocabulary into the public API.
|
||||
*/
|
||||
struct SimulationConfig {
|
||||
int schema_version{1};
|
||||
MetadataConfig metadata;
|
||||
AtomConfig atom;
|
||||
MassConfig masses;
|
||||
TransitionConfig transitions;
|
||||
CaptureConfig capture;
|
||||
ChannelConfig channels;
|
||||
ShellModelConfig shell_model;
|
||||
ReportingConfig reporting;
|
||||
ModelConfig model;
|
||||
NumericsConfig numerics;
|
||||
};
|
||||
|
||||
struct TransitionLine {
|
||||
int n1{};
|
||||
int l1{};
|
||||
int j1_twice{};
|
||||
int n2{};
|
||||
int l2{};
|
||||
int j2_twice{};
|
||||
std::string multipole;
|
||||
double energy_kev{};
|
||||
double intensity{};
|
||||
};
|
||||
|
||||
struct StateSummary {
|
||||
int n{};
|
||||
int l{};
|
||||
double population{};
|
||||
std::optional<double> polar_up;
|
||||
std::optional<double> polar_down;
|
||||
std::optional<double> width_ev;
|
||||
std::optional<double> rad_to_auger;
|
||||
std::optional<double> spin_orbit_ev;
|
||||
double k_electrons{};
|
||||
double l_electrons{};
|
||||
double m_electrons{};
|
||||
};
|
||||
|
||||
struct SimulationResult {
|
||||
double lyman_sum{};
|
||||
int num_lines{};
|
||||
std::vector<TransitionLine> lines;
|
||||
std::vector<StateSummary> states;
|
||||
std::vector<std::string> warnings;
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON artifact emitted by the `mocca` CLI.
|
||||
*
|
||||
* It includes enough provenance to make regression outputs self-contained:
|
||||
* the parsed input, the selected matrix-element precision, the coefficient-table
|
||||
* source, and the resulting physics observables.
|
||||
*/
|
||||
struct SimulationArtifact {
|
||||
int schema_version{1};
|
||||
std::string implementation_name{"MOCCA"};
|
||||
std::string numerical_backend{"modern_kernel"};
|
||||
std::string coefficient_table_id{"aama_v1_0_block_data_v1"};
|
||||
int matrix_element_precision_digits{120};
|
||||
SimulationConfig input;
|
||||
SimulationResult result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a modern JSON configuration document into a typed schema object.
|
||||
*/
|
||||
SimulationConfig parse_config_text(std::string_view text);
|
||||
|
||||
/**
|
||||
* Load and parse a modern JSON configuration file.
|
||||
*/
|
||||
SimulationConfig load_config(const std::filesystem::path& path);
|
||||
|
||||
JsonValue to_json(const SimulationConfig& config);
|
||||
JsonValue to_json(const SimulationResult& result);
|
||||
JsonValue to_json(const SimulationArtifact& artifact);
|
||||
|
||||
/**
|
||||
* Run the modern cascade kernel on a structured config and return a fully
|
||||
* serialized-ready artifact.
|
||||
*/
|
||||
SimulationArtifact run_simulation(const SimulationConfig& config);
|
||||
|
||||
} // namespace mocca
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace mocca {
|
||||
|
||||
/**
|
||||
* Minimal JSON value type used by the modern configuration and result layer.
|
||||
*
|
||||
* The project only needs a small subset of JSON infrastructure, so this keeps
|
||||
* parsing and serialization self-contained instead of adding another external
|
||||
* dependency.
|
||||
*/
|
||||
class JsonValue {
|
||||
public:
|
||||
using Array = std::vector<JsonValue>;
|
||||
using Object = std::map<std::string, JsonValue>;
|
||||
|
||||
JsonValue() = default;
|
||||
JsonValue(std::nullptr_t);
|
||||
JsonValue(bool value);
|
||||
JsonValue(double value);
|
||||
JsonValue(int value);
|
||||
JsonValue(std::string value);
|
||||
JsonValue(const char* value);
|
||||
JsonValue(Array value);
|
||||
JsonValue(Object value);
|
||||
|
||||
[[nodiscard]] bool is_null() const;
|
||||
[[nodiscard]] bool is_bool() const;
|
||||
[[nodiscard]] bool is_number() const;
|
||||
[[nodiscard]] bool is_string() const;
|
||||
[[nodiscard]] bool is_array() const;
|
||||
[[nodiscard]] bool is_object() const;
|
||||
|
||||
[[nodiscard]] bool as_bool() const;
|
||||
[[nodiscard]] double as_number() const;
|
||||
[[nodiscard]] const std::string& as_string() const;
|
||||
[[nodiscard]] const Array& as_array() const;
|
||||
[[nodiscard]] const Object& as_object() const;
|
||||
[[nodiscard]] Array& as_array();
|
||||
[[nodiscard]] Object& as_object();
|
||||
|
||||
[[nodiscard]] bool contains(const std::string& key) const;
|
||||
[[nodiscard]] const JsonValue& at(const std::string& key) const;
|
||||
|
||||
private:
|
||||
std::variant<std::nullptr_t, bool, double, std::string, Array, Object> data_{nullptr};
|
||||
};
|
||||
|
||||
JsonValue parse_json(std::string_view text);
|
||||
std::string to_json_string(const JsonValue& value, int indent = 2);
|
||||
|
||||
} // namespace mocca
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "mocca/api.hpp"
|
||||
|
||||
namespace mocca {
|
||||
|
||||
/**
|
||||
* Modern execution façade for the cascade physics kernel.
|
||||
*
|
||||
* The public surface stays intentionally small: callers provide a normalized
|
||||
* `SimulationConfig`, and the implementation owns the setup, propagation, and
|
||||
* line-collection workflow behind a value-oriented interface.
|
||||
*/
|
||||
class ModernCascadeKernel {
|
||||
public:
|
||||
explicit ModernCascadeKernel(const SimulationConfig& config);
|
||||
~ModernCascadeKernel();
|
||||
|
||||
ModernCascadeKernel(ModernCascadeKernel&&) noexcept;
|
||||
ModernCascadeKernel& operator=(ModernCascadeKernel&&) noexcept;
|
||||
|
||||
ModernCascadeKernel(const ModernCascadeKernel&) = delete;
|
||||
ModernCascadeKernel& operator=(const ModernCascadeKernel&) = delete;
|
||||
|
||||
[[nodiscard]] SimulationResult run() const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
[[nodiscard]] SimulationResult run_modern_kernel(const SimulationConfig& config);
|
||||
|
||||
} // namespace mocca
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
+586
@@ -0,0 +1,586 @@
|
||||
#include "mocca/api.hpp"
|
||||
|
||||
#include "mocca/kernel.hpp"
|
||||
#include "embedded_tables.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace mocca {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string read_file(const std::filesystem::path& path) {
|
||||
std::ifstream input(path);
|
||||
if (!input) {
|
||||
throw std::runtime_error("Unable to open file: " + path.string());
|
||||
}
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
const JsonValue::Object& require_object(const JsonValue& value, std::string_view label) {
|
||||
if (!value.is_object()) {
|
||||
throw std::runtime_error(std::string(label) + " must be a JSON object");
|
||||
}
|
||||
return value.as_object();
|
||||
}
|
||||
|
||||
const JsonValue::Array& require_array(const JsonValue& value, std::string_view label) {
|
||||
if (!value.is_array()) {
|
||||
throw std::runtime_error(std::string(label) + " must be a JSON array");
|
||||
}
|
||||
return value.as_array();
|
||||
}
|
||||
|
||||
std::optional<const JsonValue*> find_value(const JsonValue::Object& object, const std::string& key) {
|
||||
const auto it = object.find(key);
|
||||
if (it == object.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
double require_number(const JsonValue::Object& object, const std::string& key) {
|
||||
const auto value = find_value(object, key);
|
||||
if (!value.has_value() || !(*value)->is_number()) {
|
||||
throw std::runtime_error("Missing numeric field: " + key);
|
||||
}
|
||||
return (*value)->as_number();
|
||||
}
|
||||
|
||||
int checked_int(double value, std::string_view key) {
|
||||
if (!std::isfinite(value) || std::trunc(value) != value) {
|
||||
throw std::runtime_error(std::string(key) + " must be an integer");
|
||||
}
|
||||
if (value < static_cast<double>(std::numeric_limits<int>::min()) ||
|
||||
value > static_cast<double>(std::numeric_limits<int>::max())) {
|
||||
throw std::runtime_error(std::string(key) + " is outside the supported integer range");
|
||||
}
|
||||
return static_cast<int>(value);
|
||||
}
|
||||
|
||||
int require_int(const JsonValue::Object& object, const std::string& key) {
|
||||
return checked_int(require_number(object, key), key);
|
||||
}
|
||||
|
||||
std::string require_string(const JsonValue::Object& object, const std::string& key) {
|
||||
const auto value = find_value(object, key);
|
||||
if (!value.has_value() || !(*value)->is_string()) {
|
||||
throw std::runtime_error("Missing string field: " + key);
|
||||
}
|
||||
return (*value)->as_string();
|
||||
}
|
||||
|
||||
std::optional<double> optional_number(const JsonValue::Object& object, const std::string& key) {
|
||||
const auto value = find_value(object, key);
|
||||
if (!value.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if ((*value)->is_null()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!(*value)->is_number()) {
|
||||
throw std::runtime_error("Field must be numeric: " + key);
|
||||
}
|
||||
return (*value)->as_number();
|
||||
}
|
||||
|
||||
std::optional<int> optional_int(const JsonValue::Object& object, const std::string& key) {
|
||||
const auto value = optional_number(object, key);
|
||||
if (!value.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return checked_int(*value, key);
|
||||
}
|
||||
|
||||
bool optional_bool(const JsonValue::Object& object, const std::string& key, bool default_value) {
|
||||
const auto value = find_value(object, key);
|
||||
if (!value.has_value()) {
|
||||
return default_value;
|
||||
}
|
||||
if (!(*value)->is_bool()) {
|
||||
throw std::runtime_error("Field must be bool: " + key);
|
||||
}
|
||||
return (*value)->as_bool();
|
||||
}
|
||||
|
||||
std::vector<double> number_array(const JsonValue::Object& object, const std::string& key) {
|
||||
const auto values = find_value(object, key);
|
||||
if (!values.has_value()) {
|
||||
return {};
|
||||
}
|
||||
std::vector<double> numbers;
|
||||
for (const JsonValue& item : require_array(*(*values), key)) {
|
||||
if (!item.is_number()) {
|
||||
throw std::runtime_error("Array " + key + " must contain only numbers");
|
||||
}
|
||||
numbers.push_back(item.as_number());
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
|
||||
std::optional<std::vector<double>> optional_number_array(
|
||||
const JsonValue::Object& object,
|
||||
const std::string& key) {
|
||||
const auto value = find_value(object, key);
|
||||
if (!value.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<double> numbers;
|
||||
for (const JsonValue& item : require_array(*(*value), key)) {
|
||||
if (!item.is_number()) {
|
||||
throw std::runtime_error("Array " + key + " must contain only numbers");
|
||||
}
|
||||
numbers.push_back(item.as_number());
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
|
||||
std::optional<std::vector<int>> optional_int_array(
|
||||
const JsonValue::Object& object,
|
||||
const std::string& key) {
|
||||
const auto values = optional_number_array(object, key);
|
||||
if (!values.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<int> ints;
|
||||
ints.reserve(values->size());
|
||||
for (double number : *values) {
|
||||
ints.push_back(checked_int(number, key));
|
||||
}
|
||||
return ints;
|
||||
}
|
||||
|
||||
JsonValue number_array_json(const std::vector<double>& values) {
|
||||
JsonValue::Array array;
|
||||
array.reserve(values.size());
|
||||
for (double value : values) {
|
||||
array.emplace_back(value);
|
||||
}
|
||||
return JsonValue(std::move(array));
|
||||
}
|
||||
|
||||
JsonValue int_array_json(const std::vector<int>& values) {
|
||||
JsonValue::Array array;
|
||||
array.reserve(values.size());
|
||||
for (int value : values) {
|
||||
array.emplace_back(value);
|
||||
}
|
||||
return JsonValue(std::move(array));
|
||||
}
|
||||
|
||||
CaptureMode parse_capture_mode(const std::string& raw_mode) {
|
||||
if (raw_mode == "statistical_l") {
|
||||
return CaptureMode::statistical_l;
|
||||
}
|
||||
if (raw_mode == "quadratic_l") {
|
||||
return CaptureMode::quadratic_l;
|
||||
}
|
||||
if (raw_mode == "explicit_l") {
|
||||
return CaptureMode::explicit_l;
|
||||
}
|
||||
if (raw_mode == "explicit_nl") {
|
||||
return CaptureMode::explicit_nl;
|
||||
}
|
||||
if (raw_mode == "legacy_empty") {
|
||||
return CaptureMode::legacy_empty;
|
||||
}
|
||||
throw std::runtime_error("Unsupported capture mode: " + raw_mode);
|
||||
}
|
||||
|
||||
std::string capture_mode_name(CaptureMode mode) {
|
||||
switch (mode) {
|
||||
case CaptureMode::statistical_l:
|
||||
return "statistical_l";
|
||||
case CaptureMode::quadratic_l:
|
||||
return "quadratic_l";
|
||||
case CaptureMode::explicit_l:
|
||||
return "explicit_l";
|
||||
case CaptureMode::explicit_nl:
|
||||
return "explicit_nl";
|
||||
case CaptureMode::legacy_empty:
|
||||
return "legacy_empty";
|
||||
}
|
||||
throw std::runtime_error("Unhandled capture mode");
|
||||
}
|
||||
|
||||
JsonValue dirac_energy_json(const DiracEnergy& entry) {
|
||||
return JsonValue::Object{
|
||||
{"binding_kev", entry.binding_kev},
|
||||
{"kappa", entry.kappa},
|
||||
{"n", entry.n},
|
||||
{"vacuum_polarization_kev", entry.vacuum_polarization_kev},
|
||||
};
|
||||
}
|
||||
|
||||
JsonValue nl_weight_json(const NlWeight& entry) {
|
||||
return JsonValue::Object{
|
||||
{"l", entry.l},
|
||||
{"n", entry.n},
|
||||
{"weight", entry.weight},
|
||||
};
|
||||
}
|
||||
|
||||
JsonValue optional_number_json(const std::optional<double>& value) {
|
||||
if (!value.has_value()) {
|
||||
return JsonValue(nullptr);
|
||||
}
|
||||
return JsonValue(*value);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SimulationConfig parse_config_text(std::string_view text) {
|
||||
const JsonValue root = parse_json(text);
|
||||
const JsonValue::Object& object = require_object(root, "root");
|
||||
|
||||
SimulationConfig config;
|
||||
if (const auto schema = optional_int(object, "schema_version"); schema.has_value()) {
|
||||
config.schema_version = *schema;
|
||||
}
|
||||
if (config.schema_version != 1) {
|
||||
throw std::runtime_error("Unsupported schema_version: " + std::to_string(config.schema_version));
|
||||
}
|
||||
if (const auto metadata_value = find_value(object, "metadata"); metadata_value.has_value()) {
|
||||
const JsonValue::Object& metadata = require_object(*(*metadata_value), "metadata");
|
||||
if (const auto case_name = find_value(metadata, "case_name"); case_name.has_value()) {
|
||||
if (!(*case_name)->is_string()) {
|
||||
throw std::runtime_error("metadata.case_name must be a string");
|
||||
}
|
||||
config.metadata.case_name = (*case_name)->as_string();
|
||||
}
|
||||
}
|
||||
|
||||
const JsonValue::Object& atom = require_object(object.at("atom"), "atom");
|
||||
config.atom.atomic_number = require_number(atom, "atomic_number");
|
||||
config.atom.effective_shell_charges = number_array(atom, "effective_shell_charges");
|
||||
config.atom.binding_energies_ev = number_array(atom, "binding_energies_ev");
|
||||
config.atom.atomic_mass = require_number(atom, "atomic_mass");
|
||||
config.atom.exact_mass_number = optional_number(atom, "exact_mass_number");
|
||||
|
||||
if (const auto masses_value = find_value(object, "masses"); masses_value.has_value()) {
|
||||
const JsonValue::Object& masses = require_object(*(*masses_value), "masses");
|
||||
if (const auto value = optional_number(masses, "muon_electron_masses"); value.has_value()) {
|
||||
config.masses.muon_electron_masses = *value;
|
||||
}
|
||||
if (const auto value = optional_number(masses, "electron_mass_ev"); value.has_value()) {
|
||||
config.masses.electron_mass_ev = *value;
|
||||
}
|
||||
if (const auto value = optional_number(masses, "nucleon_mass_mev"); value.has_value()) {
|
||||
config.masses.nucleon_mass_mev = *value;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto transitions_value = find_value(object, "transitions"); transitions_value.has_value()) {
|
||||
const JsonValue::Object& transitions = require_object(*(*transitions_value), "transitions");
|
||||
config.transitions.two_p_to_one_s_energy_ev = optional_number(transitions, "two_p_to_one_s_energy_ev");
|
||||
config.transitions.two_s_to_two_p_split_ev = optional_number(transitions, "two_s_to_two_p_split_ev");
|
||||
if (const auto entries_value = find_value(transitions, "dirac_energies"); entries_value.has_value()) {
|
||||
for (const JsonValue& entry_value : require_array(*(*entries_value), "transitions.dirac_energies")) {
|
||||
const JsonValue::Object& entry = require_object(entry_value, "dirac energy entry");
|
||||
config.transitions.dirac_energies.push_back(DiracEnergy{
|
||||
require_int(entry, "n"),
|
||||
require_int(entry, "kappa"),
|
||||
optional_number(entry, "vacuum_polarization_kev").value_or(0.0),
|
||||
require_number(entry, "binding_kev"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const JsonValue::Object& capture = require_object(object.at("capture"), "capture");
|
||||
config.capture.mode = parse_capture_mode(require_string(capture, "mode"));
|
||||
config.capture.n_max = require_int(capture, "n_max");
|
||||
if (const auto value = optional_number(capture, "alpha"); value.has_value()) {
|
||||
config.capture.alpha = *value;
|
||||
}
|
||||
config.capture.quadratic_coefficients = number_array(capture, "quadratic_coefficients");
|
||||
config.capture.l_weights = number_array(capture, "l_weights");
|
||||
if (const auto nl_weights_value = find_value(capture, "nl_weights"); nl_weights_value.has_value()) {
|
||||
for (const JsonValue& entry_value : require_array(*(*nl_weights_value), "capture.nl_weights")) {
|
||||
const JsonValue::Object& entry = require_object(entry_value, "capture.nl_weights entry");
|
||||
config.capture.nl_weights.push_back(NlWeight{
|
||||
require_int(entry, "n"),
|
||||
require_int(entry, "l"),
|
||||
require_number(entry, "weight"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto channels_value = find_value(object, "channels"); channels_value.has_value()) {
|
||||
const JsonValue::Object& channels = require_object(*(*channels_value), "channels");
|
||||
if (const auto values = optional_int_array(channels, "case_counts"); values.has_value()) {
|
||||
config.channels.case_counts = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "monopole_shells"); values.has_value()) {
|
||||
config.channels.monopole_shells = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "dipole_shells"); values.has_value()) {
|
||||
config.channels.dipole_shells = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "quadrupole_shells"); values.has_value()) {
|
||||
config.channels.quadrupole_shells = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "octupole_shells"); values.has_value()) {
|
||||
config.channels.octupole_shells = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "dipole_subshell_channels"); values.has_value()) {
|
||||
config.channels.dipole_subshell_channels = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "quadrupole_subshell_channels");
|
||||
values.has_value()) {
|
||||
config.channels.quadrupole_subshell_channels = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "octupole_subshell_channels");
|
||||
values.has_value()) {
|
||||
config.channels.octupole_subshell_channels = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "dipole_penetration_codes");
|
||||
values.has_value()) {
|
||||
config.channels.dipole_penetration_codes = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "quadrupole_penetration_codes");
|
||||
values.has_value()) {
|
||||
config.channels.quadrupole_penetration_codes = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "octupole_penetration_codes");
|
||||
values.has_value()) {
|
||||
config.channels.octupole_penetration_codes = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "dipole_penetration_avg_n_cutoffs");
|
||||
values.has_value()) {
|
||||
config.channels.dipole_penetration_avg_n_cutoffs = *values;
|
||||
}
|
||||
if (const auto values =
|
||||
optional_int_array(channels, "quadrupole_penetration_avg_n_cutoffs");
|
||||
values.has_value()) {
|
||||
config.channels.quadrupole_penetration_avg_n_cutoffs = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(channels, "octupole_penetration_avg_n_cutoffs");
|
||||
values.has_value()) {
|
||||
config.channels.octupole_penetration_avg_n_cutoffs = *values;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto shell_value = find_value(object, "shell_model"); shell_value.has_value()) {
|
||||
const JsonValue::Object& shell_model = require_object(*(*shell_value), "shell_model");
|
||||
if (const auto values = optional_number_array(shell_model, "subshell_populations");
|
||||
values.has_value()) {
|
||||
config.shell_model.subshell_populations = *values;
|
||||
}
|
||||
if (const auto values = optional_int_array(shell_model, "refill_codes"); values.has_value()) {
|
||||
config.shell_model.refill_codes = *values;
|
||||
}
|
||||
if (const auto values = optional_number_array(shell_model, "penetration_cutoffs");
|
||||
values.has_value()) {
|
||||
config.shell_model.penetration_cutoffs = *values;
|
||||
}
|
||||
if (const auto value = optional_number(shell_model, "width_k_ev"); value.has_value()) {
|
||||
config.shell_model.width_k_ev = *value;
|
||||
}
|
||||
config.shell_model.track_polarization = optional_bool(shell_model, "track_polarization", true);
|
||||
}
|
||||
|
||||
if (const auto reporting_value = find_value(object, "reporting"); reporting_value.has_value()) {
|
||||
const JsonValue::Object& reporting = require_object(*(*reporting_value), "reporting");
|
||||
if (const auto value = optional_number(reporting, "line_energy_min_mev"); value.has_value()) {
|
||||
config.reporting.line_energy_min_mev = *value;
|
||||
}
|
||||
if (const auto value = optional_number(reporting, "line_energy_max_mev"); value.has_value()) {
|
||||
config.reporting.line_energy_max_mev = *value;
|
||||
}
|
||||
if (const auto value = optional_number(reporting, "line_intensity_threshold"); value.has_value()) {
|
||||
config.reporting.line_intensity_threshold = *value;
|
||||
}
|
||||
if (const auto value = optional_number(reporting, "energy_resolution_mev"); value.has_value()) {
|
||||
config.reporting.energy_resolution_mev = *value;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto model_value = find_value(object, "model"); model_value.has_value()) {
|
||||
const JsonValue::Object& model = require_object(*(*model_value), "model");
|
||||
if (const auto value = optional_number(model, "factorial_divider"); value.has_value()) {
|
||||
config.model.factorial_divider = *value;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto numerics_value = find_value(object, "numerics"); numerics_value.has_value()) {
|
||||
const JsonValue::Object& numerics = require_object(*(*numerics_value), "numerics");
|
||||
if (const auto value = optional_int(numerics, "matrix_element_precision_digits");
|
||||
value.has_value()) {
|
||||
config.numerics.matrix_element_precision_digits = *value;
|
||||
} else if (const auto legacy_value = optional_int(numerics, "stable_precision_digits");
|
||||
legacy_value.has_value()) {
|
||||
config.numerics.matrix_element_precision_digits = *legacy_value;
|
||||
}
|
||||
}
|
||||
if (config.numerics.matrix_element_precision_digits <= 0) {
|
||||
throw std::runtime_error(
|
||||
"numerics.matrix_element_precision_digits must be a positive integer");
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
SimulationConfig load_config(const std::filesystem::path& path) {
|
||||
return parse_config_text(read_file(path));
|
||||
}
|
||||
|
||||
JsonValue to_json(const SimulationConfig& config) {
|
||||
JsonValue::Array dirac_energies;
|
||||
dirac_energies.reserve(config.transitions.dirac_energies.size());
|
||||
for (const DiracEnergy& entry : config.transitions.dirac_energies) {
|
||||
dirac_energies.push_back(dirac_energy_json(entry));
|
||||
}
|
||||
|
||||
JsonValue::Array nl_weights;
|
||||
nl_weights.reserve(config.capture.nl_weights.size());
|
||||
for (const NlWeight& entry : config.capture.nl_weights) {
|
||||
nl_weights.push_back(nl_weight_json(entry));
|
||||
}
|
||||
|
||||
return JsonValue::Object{
|
||||
{"schema_version", config.schema_version},
|
||||
{"metadata", JsonValue::Object{
|
||||
{"case_name", config.metadata.case_name},
|
||||
}},
|
||||
{"atom", JsonValue::Object{
|
||||
{"atomic_number", config.atom.atomic_number},
|
||||
{"atomic_mass", config.atom.atomic_mass},
|
||||
{"binding_energies_ev", number_array_json(config.atom.binding_energies_ev)},
|
||||
{"effective_shell_charges", number_array_json(config.atom.effective_shell_charges)},
|
||||
{"exact_mass_number", optional_number_json(config.atom.exact_mass_number)},
|
||||
}},
|
||||
{"masses", JsonValue::Object{
|
||||
{"electron_mass_ev", config.masses.electron_mass_ev},
|
||||
{"muon_electron_masses", config.masses.muon_electron_masses},
|
||||
{"nucleon_mass_mev", config.masses.nucleon_mass_mev},
|
||||
}},
|
||||
{"transitions", JsonValue::Object{
|
||||
{"dirac_energies", JsonValue(std::move(dirac_energies))},
|
||||
{"two_p_to_one_s_energy_ev", optional_number_json(config.transitions.two_p_to_one_s_energy_ev)},
|
||||
{"two_s_to_two_p_split_ev", optional_number_json(config.transitions.two_s_to_two_p_split_ev)},
|
||||
}},
|
||||
{"capture", JsonValue::Object{
|
||||
{"alpha", config.capture.alpha},
|
||||
{"l_weights", number_array_json(config.capture.l_weights)},
|
||||
{"mode", capture_mode_name(config.capture.mode)},
|
||||
{"n_max", config.capture.n_max},
|
||||
{"nl_weights", JsonValue(std::move(nl_weights))},
|
||||
{"quadratic_coefficients", number_array_json(config.capture.quadratic_coefficients)},
|
||||
}},
|
||||
{"channels", JsonValue::Object{
|
||||
{"case_counts", int_array_json(config.channels.case_counts)},
|
||||
{"dipole_penetration_avg_n_cutoffs", int_array_json(config.channels.dipole_penetration_avg_n_cutoffs)},
|
||||
{"dipole_penetration_codes", int_array_json(config.channels.dipole_penetration_codes)},
|
||||
{"dipole_shells", int_array_json(config.channels.dipole_shells)},
|
||||
{"dipole_subshell_channels", int_array_json(config.channels.dipole_subshell_channels)},
|
||||
{"monopole_shells", int_array_json(config.channels.monopole_shells)},
|
||||
{"octupole_penetration_avg_n_cutoffs", int_array_json(config.channels.octupole_penetration_avg_n_cutoffs)},
|
||||
{"octupole_penetration_codes", int_array_json(config.channels.octupole_penetration_codes)},
|
||||
{"octupole_shells", int_array_json(config.channels.octupole_shells)},
|
||||
{"octupole_subshell_channels", int_array_json(config.channels.octupole_subshell_channels)},
|
||||
{"quadrupole_penetration_avg_n_cutoffs", int_array_json(config.channels.quadrupole_penetration_avg_n_cutoffs)},
|
||||
{"quadrupole_penetration_codes", int_array_json(config.channels.quadrupole_penetration_codes)},
|
||||
{"quadrupole_shells", int_array_json(config.channels.quadrupole_shells)},
|
||||
{"quadrupole_subshell_channels", int_array_json(config.channels.quadrupole_subshell_channels)},
|
||||
}},
|
||||
{"shell_model", JsonValue::Object{
|
||||
{"penetration_cutoffs", number_array_json(config.shell_model.penetration_cutoffs)},
|
||||
{"refill_codes", int_array_json(config.shell_model.refill_codes)},
|
||||
{"subshell_populations", number_array_json(config.shell_model.subshell_populations)},
|
||||
{"track_polarization", config.shell_model.track_polarization},
|
||||
{"width_k_ev", config.shell_model.width_k_ev},
|
||||
}},
|
||||
{"reporting", JsonValue::Object{
|
||||
{"energy_resolution_mev", config.reporting.energy_resolution_mev},
|
||||
{"line_energy_max_mev", config.reporting.line_energy_max_mev},
|
||||
{"line_energy_min_mev", config.reporting.line_energy_min_mev},
|
||||
{"line_intensity_threshold", config.reporting.line_intensity_threshold},
|
||||
}},
|
||||
{"model", JsonValue::Object{
|
||||
{"factorial_divider", config.model.factorial_divider},
|
||||
}},
|
||||
{"numerics", JsonValue::Object{
|
||||
{"matrix_element_precision_digits", config.numerics.matrix_element_precision_digits},
|
||||
}},
|
||||
};
|
||||
}
|
||||
|
||||
JsonValue to_json(const SimulationResult& result) {
|
||||
JsonValue::Array lines;
|
||||
lines.reserve(result.lines.size());
|
||||
for (const TransitionLine& line : result.lines) {
|
||||
lines.push_back(JsonValue::Object{
|
||||
{"energy_kev", line.energy_kev},
|
||||
{"intensity", line.intensity},
|
||||
{"j1_twice", line.j1_twice},
|
||||
{"j2_twice", line.j2_twice},
|
||||
{"l1", line.l1},
|
||||
{"l2", line.l2},
|
||||
{"multipole", line.multipole},
|
||||
{"n1", line.n1},
|
||||
{"n2", line.n2},
|
||||
});
|
||||
}
|
||||
|
||||
JsonValue::Array states;
|
||||
states.reserve(result.states.size());
|
||||
for (const StateSummary& state : result.states) {
|
||||
states.push_back(JsonValue::Object{
|
||||
{"k_electrons", state.k_electrons},
|
||||
{"l", state.l},
|
||||
{"l_electrons", state.l_electrons},
|
||||
{"m_electrons", state.m_electrons},
|
||||
{"n", state.n},
|
||||
{"polar_down", optional_number_json(state.polar_down)},
|
||||
{"polar_up", optional_number_json(state.polar_up)},
|
||||
{"population", state.population},
|
||||
{"rad_to_auger", optional_number_json(state.rad_to_auger)},
|
||||
{"spin_orbit_ev", optional_number_json(state.spin_orbit_ev)},
|
||||
{"width_ev", optional_number_json(state.width_ev)},
|
||||
});
|
||||
}
|
||||
|
||||
JsonValue::Array warnings;
|
||||
warnings.reserve(result.warnings.size());
|
||||
for (const std::string& warning : result.warnings) {
|
||||
warnings.emplace_back(warning);
|
||||
}
|
||||
|
||||
return JsonValue::Object{
|
||||
{"lines", JsonValue(std::move(lines))},
|
||||
{"lyman_sum", result.lyman_sum},
|
||||
{"num_lines", result.num_lines},
|
||||
{"states", JsonValue(std::move(states))},
|
||||
{"warnings", JsonValue(std::move(warnings))},
|
||||
};
|
||||
}
|
||||
|
||||
JsonValue to_json(const SimulationArtifact& artifact) {
|
||||
return JsonValue::Object{
|
||||
{"coefficient_table_id", artifact.coefficient_table_id},
|
||||
{"implementation_name", artifact.implementation_name},
|
||||
{"input", to_json(artifact.input)},
|
||||
{"numerical_backend", artifact.numerical_backend},
|
||||
{"result", to_json(artifact.result)},
|
||||
{"schema_version", artifact.schema_version},
|
||||
{"matrix_element_precision_digits", artifact.matrix_element_precision_digits},
|
||||
};
|
||||
}
|
||||
|
||||
SimulationArtifact run_simulation(const SimulationConfig& config) {
|
||||
SimulationArtifact artifact;
|
||||
artifact.coefficient_table_id = std::string(detail::kBundledCoefficientTableId);
|
||||
artifact.matrix_element_precision_digits = config.numerics.matrix_element_precision_digits;
|
||||
artifact.input = config;
|
||||
artifact.result = run_modern_kernel(config);
|
||||
return artifact;
|
||||
}
|
||||
|
||||
} // namespace mocca
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
#include "mocca/api.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
void print_usage() {
|
||||
std::cerr << "Usage: mocca <config.json> [--output result.json]\n";
|
||||
}
|
||||
|
||||
void write_text(const std::filesystem::path& path, const std::string& text) {
|
||||
std::ofstream output(path);
|
||||
if (!output) {
|
||||
throw std::runtime_error("Unable to open output file: " + path.string());
|
||||
}
|
||||
output << text;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
if (argc < 2) {
|
||||
print_usage();
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::filesystem::path config_path = argv[1];
|
||||
std::filesystem::path output_path;
|
||||
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg == "--output" && i + 1 < argc) {
|
||||
output_path = argv[++i];
|
||||
} else {
|
||||
throw std::runtime_error("Unknown or incomplete argument: " + arg);
|
||||
}
|
||||
}
|
||||
|
||||
const mocca::SimulationConfig config = mocca::load_config(config_path);
|
||||
const mocca::SimulationArtifact artifact = mocca::run_simulation(config);
|
||||
const std::string payload =
|
||||
mocca::to_json_string(mocca::to_json(artifact), 2);
|
||||
|
||||
if (!output_path.empty()) {
|
||||
write_text(output_path, payload);
|
||||
} else {
|
||||
std::cout << payload;
|
||||
}
|
||||
return 0;
|
||||
} catch (const std::exception& exc) {
|
||||
std::cerr << exc.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
#include "config_bridge.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace mocca::detail {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
void set_one_based(std::vector<T>& destination, const std::vector<T>& source) {
|
||||
const std::size_t capacity = destination.empty() ? 0 : destination.size() - 1;
|
||||
if (source.size() > capacity) {
|
||||
throw std::runtime_error("Input vector exceeds the supported capacity");
|
||||
}
|
||||
for (std::size_t i = 0; i < source.size(); ++i) {
|
||||
destination[i + 1] = source[i];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void require_exact_size(
|
||||
const std::vector<T>& values,
|
||||
std::size_t expected_size,
|
||||
std::string_view field_name) {
|
||||
if (values.size() != expected_size) {
|
||||
std::ostringstream out;
|
||||
out << field_name << " must contain exactly " << expected_size
|
||||
<< " value(s); received " << values.size();
|
||||
throw std::runtime_error(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
void require_int_range(
|
||||
int value,
|
||||
int min_value,
|
||||
int max_value,
|
||||
std::string_view field_name) {
|
||||
if (value < min_value || value > max_value) {
|
||||
std::ostringstream out;
|
||||
out << field_name << " must be between " << min_value << " and " << max_value
|
||||
<< "; received " << value;
|
||||
throw std::runtime_error(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
void require_values_in_range(
|
||||
const std::vector<int>& values,
|
||||
int min_value,
|
||||
int max_value,
|
||||
std::string_view field_name) {
|
||||
for (std::size_t index = 0; index < values.size(); ++index) {
|
||||
if (values[index] < min_value || values[index] > max_value) {
|
||||
std::ostringstream out;
|
||||
out << field_name << "[" << index << "] must be between " << min_value << " and "
|
||||
<< max_value << "; received " << values[index];
|
||||
throw std::runtime_error(out.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void apply_dirac_energies(
|
||||
KernelState& cfg,
|
||||
const std::vector<DiracEnergy>& dirac_energies) {
|
||||
if (dirac_energies.empty()) {
|
||||
return;
|
||||
}
|
||||
cfg.icc = 1;
|
||||
for (const DiracEnergy& entry : dirac_energies) {
|
||||
if (entry.n < 0 || entry.n >= static_cast<int>(cfg.energy.size())) {
|
||||
throw std::runtime_error("Dirac energy n is out of supported range");
|
||||
}
|
||||
if (entry.kappa < 0 || entry.kappa >= static_cast<int>(cfg.energy[entry.n].size())) {
|
||||
throw std::runtime_error("Dirac energy kappa is out of supported range");
|
||||
}
|
||||
double value = entry.binding_kev + entry.vacuum_polarization_kev;
|
||||
if (entry.vacuum_polarization_kev < 0.0) {
|
||||
cfg.idr += 1;
|
||||
}
|
||||
cfg.energy[entry.n][entry.kappa] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void apply_capture(KernelState& cfg, const CaptureConfig& capture) {
|
||||
cfg.nmax = capture.n_max;
|
||||
cfg.ip8 = 0;
|
||||
cfg.nopt = 1;
|
||||
std::fill(cfg.pl.begin(), cfg.pl.end(), 0.0);
|
||||
std::fill(cfg.pln.begin(), cfg.pln.end(), 0.0);
|
||||
|
||||
switch (capture.mode) {
|
||||
case CaptureMode::statistical_l:
|
||||
cfg.nopt = 0;
|
||||
cfg.alexp = capture.alpha;
|
||||
break;
|
||||
case CaptureMode::quadratic_l:
|
||||
cfg.nopt = 2;
|
||||
require_exact_size(
|
||||
capture.quadratic_coefficients,
|
||||
2,
|
||||
"capture.quadratic_coefficients");
|
||||
cfg.cl1 = capture.quadratic_coefficients[0];
|
||||
cfg.cl2 = capture.quadratic_coefficients[1];
|
||||
break;
|
||||
case CaptureMode::explicit_l:
|
||||
require_exact_size(
|
||||
capture.l_weights,
|
||||
static_cast<std::size_t>(capture.n_max),
|
||||
"capture.l_weights");
|
||||
for (std::size_t index = 0; index < capture.l_weights.size(); ++index) {
|
||||
if (index + 1 >= cfg.pl.size()) {
|
||||
break;
|
||||
}
|
||||
cfg.pl[index + 1] = capture.l_weights[index];
|
||||
}
|
||||
break;
|
||||
case CaptureMode::explicit_nl:
|
||||
cfg.ip8 = 1;
|
||||
if (capture.nl_weights.empty()) {
|
||||
throw std::runtime_error("explicit_nl mode requires nl_weights");
|
||||
}
|
||||
for (const NlWeight& weight : capture.nl_weights) {
|
||||
if (weight.n < 1 || weight.n > cfg.nmax || weight.l < 0 || weight.l >= weight.n) {
|
||||
throw std::runtime_error("explicit_nl weights contain an invalid (n,l) state");
|
||||
}
|
||||
const int index = state_index(weight.n, weight.l);
|
||||
if (index >= static_cast<int>(cfg.pln.size())) {
|
||||
throw std::runtime_error("explicit_nl state exceeds the supported n range");
|
||||
}
|
||||
cfg.pln[index] = weight.weight;
|
||||
}
|
||||
break;
|
||||
case CaptureMode::legacy_empty:
|
||||
cfg.nopt = 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
KernelState build_kernel_config(
|
||||
const NumericTables& numeric_tables,
|
||||
const SimulationConfig& config) {
|
||||
KernelState cfg(numeric_tables);
|
||||
|
||||
require_exact_size(config.atom.effective_shell_charges, 3, "atom.effective_shell_charges");
|
||||
require_exact_size(config.atom.binding_energies_ev, 3, "atom.binding_energies_ev");
|
||||
if (config.capture.n_max < 1 || config.capture.n_max > 20) {
|
||||
throw std::runtime_error("capture.n_max must be between 1 and 20");
|
||||
}
|
||||
|
||||
cfg.z = config.atom.atomic_number;
|
||||
cfg.zsk = config.atom.effective_shell_charges[0];
|
||||
cfg.zsl = config.atom.effective_shell_charges[1];
|
||||
cfg.zsm = config.atom.effective_shell_charges[2];
|
||||
cfg.be[1] = config.atom.binding_energies_ev[0];
|
||||
cfg.be[2] = config.atom.binding_energies_ev[1];
|
||||
cfg.be[3] = config.atom.binding_energies_ev[2];
|
||||
cfg.a = config.atom.atomic_mass;
|
||||
if (config.atom.exact_mass_number.has_value()) {
|
||||
cfg.amassa = *config.atom.exact_mass_number;
|
||||
}
|
||||
|
||||
cfg.amassm = config.masses.muon_electron_masses;
|
||||
cfg.amasse = config.masses.electron_mass_ev;
|
||||
cfg.amassn = config.masses.nucleon_mass_mev;
|
||||
if (!std::isfinite(config.model.factorial_divider) || config.model.factorial_divider <= 0.0) {
|
||||
throw std::runtime_error("model.factorial_divider must be finite and strictly positive");
|
||||
}
|
||||
cfg.fd = config.model.factorial_divider;
|
||||
|
||||
if (config.transitions.two_p_to_one_s_energy_ev.has_value()) {
|
||||
cfg.d2p1s = *config.transitions.two_p_to_one_s_energy_ev;
|
||||
}
|
||||
if (config.transitions.two_s_to_two_p_split_ev.has_value()) {
|
||||
cfg.esp = *config.transitions.two_s_to_two_p_split_ev;
|
||||
}
|
||||
apply_dirac_energies(cfg, config.transitions.dirac_energies);
|
||||
|
||||
require_exact_size(config.channels.case_counts, 4, "channels.case_counts");
|
||||
require_int_range(
|
||||
config.channels.case_counts[0],
|
||||
0,
|
||||
static_cast<int>(cfg.nn0.size()) - 1,
|
||||
"channels.case_counts[0]");
|
||||
require_int_range(
|
||||
config.channels.case_counts[1],
|
||||
0,
|
||||
static_cast<int>(cfg.nn1.size()) - 1,
|
||||
"channels.case_counts[1]");
|
||||
require_int_range(
|
||||
config.channels.case_counts[2],
|
||||
0,
|
||||
static_cast<int>(cfg.nn2.size()) - 1,
|
||||
"channels.case_counts[2]");
|
||||
require_int_range(
|
||||
config.channels.case_counts[3],
|
||||
0,
|
||||
static_cast<int>(cfg.nn3.size()) - 1,
|
||||
"channels.case_counts[3]");
|
||||
cfg.k0 = config.channels.case_counts[0];
|
||||
cfg.k1 = config.channels.case_counts[1];
|
||||
cfg.k2 = config.channels.case_counts[2];
|
||||
cfg.k3 = config.channels.case_counts[3];
|
||||
require_exact_size(
|
||||
config.channels.monopole_shells,
|
||||
static_cast<std::size_t>(cfg.k0),
|
||||
"channels.monopole_shells");
|
||||
require_exact_size(
|
||||
config.channels.dipole_shells,
|
||||
static_cast<std::size_t>(cfg.k1),
|
||||
"channels.dipole_shells");
|
||||
require_exact_size(
|
||||
config.channels.quadrupole_shells,
|
||||
static_cast<std::size_t>(cfg.k2),
|
||||
"channels.quadrupole_shells");
|
||||
require_exact_size(
|
||||
config.channels.octupole_shells,
|
||||
static_cast<std::size_t>(cfg.k3),
|
||||
"channels.octupole_shells");
|
||||
require_exact_size(
|
||||
config.channels.dipole_subshell_channels,
|
||||
static_cast<std::size_t>(cfg.k1),
|
||||
"channels.dipole_subshell_channels");
|
||||
require_exact_size(
|
||||
config.channels.quadrupole_subshell_channels,
|
||||
static_cast<std::size_t>(cfg.k2),
|
||||
"channels.quadrupole_subshell_channels");
|
||||
require_exact_size(
|
||||
config.channels.octupole_subshell_channels,
|
||||
static_cast<std::size_t>(cfg.k3),
|
||||
"channels.octupole_subshell_channels");
|
||||
require_exact_size(
|
||||
config.channels.dipole_penetration_codes,
|
||||
static_cast<std::size_t>(cfg.k1),
|
||||
"channels.dipole_penetration_codes");
|
||||
require_exact_size(
|
||||
config.channels.quadrupole_penetration_codes,
|
||||
static_cast<std::size_t>(cfg.k2),
|
||||
"channels.quadrupole_penetration_codes");
|
||||
require_exact_size(
|
||||
config.channels.octupole_penetration_codes,
|
||||
static_cast<std::size_t>(cfg.k3),
|
||||
"channels.octupole_penetration_codes");
|
||||
require_exact_size(
|
||||
config.channels.dipole_penetration_avg_n_cutoffs,
|
||||
static_cast<std::size_t>(cfg.k1),
|
||||
"channels.dipole_penetration_avg_n_cutoffs");
|
||||
require_exact_size(
|
||||
config.channels.quadrupole_penetration_avg_n_cutoffs,
|
||||
static_cast<std::size_t>(cfg.k2),
|
||||
"channels.quadrupole_penetration_avg_n_cutoffs");
|
||||
require_exact_size(
|
||||
config.channels.octupole_penetration_avg_n_cutoffs,
|
||||
static_cast<std::size_t>(cfg.k3),
|
||||
"channels.octupole_penetration_avg_n_cutoffs");
|
||||
require_values_in_range(config.channels.monopole_shells, 1, 3, "channels.monopole_shells");
|
||||
require_values_in_range(config.channels.dipole_shells, 0, 3, "channels.dipole_shells");
|
||||
require_values_in_range(
|
||||
config.channels.quadrupole_shells,
|
||||
0,
|
||||
3,
|
||||
"channels.quadrupole_shells");
|
||||
require_values_in_range(config.channels.octupole_shells, 0, 3, "channels.octupole_shells");
|
||||
set_one_based(cfg.nn0, config.channels.monopole_shells);
|
||||
set_one_based(cfg.nn1, config.channels.dipole_shells);
|
||||
set_one_based(cfg.nn2, config.channels.quadrupole_shells);
|
||||
set_one_based(cfg.nn3, config.channels.octupole_shells);
|
||||
set_one_based(cfg.m1, config.channels.dipole_subshell_channels);
|
||||
set_one_based(cfg.m2, config.channels.quadrupole_subshell_channels);
|
||||
set_one_based(cfg.m3, config.channels.octupole_subshell_channels);
|
||||
set_one_based(cfg.ip1, config.channels.dipole_penetration_codes);
|
||||
set_one_based(cfg.ip2, config.channels.quadrupole_penetration_codes);
|
||||
set_one_based(cfg.ip3, config.channels.octupole_penetration_codes);
|
||||
set_one_based(cfg.iq1, config.channels.dipole_penetration_avg_n_cutoffs);
|
||||
set_one_based(cfg.iq2, config.channels.quadrupole_penetration_avg_n_cutoffs);
|
||||
set_one_based(cfg.iq3, config.channels.octupole_penetration_avg_n_cutoffs);
|
||||
|
||||
require_exact_size(config.shell_model.subshell_populations, 6, "shell_model.subshell_populations");
|
||||
require_exact_size(config.shell_model.refill_codes, 3, "shell_model.refill_codes");
|
||||
require_exact_size(config.shell_model.penetration_cutoffs, 4, "shell_model.penetration_cutoffs");
|
||||
set_one_based(cfg.pop, config.shell_model.subshell_populations);
|
||||
set_one_based(cfg.ipc, config.shell_model.refill_codes);
|
||||
set_one_based(cfg.yc, config.shell_model.penetration_cutoffs);
|
||||
cfg.widthk = config.shell_model.width_k_ev;
|
||||
cfg.ipol = config.shell_model.track_polarization ? 0 : 1;
|
||||
|
||||
cfg.elow = config.reporting.line_energy_min_mev;
|
||||
cfg.ehigh = config.reporting.line_energy_max_mev;
|
||||
cfg.climit = config.reporting.line_intensity_threshold;
|
||||
cfg.eres = config.reporting.energy_resolution_mev;
|
||||
|
||||
apply_capture(cfg, config.capture);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
} // namespace mocca::detail
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "mocca/api.hpp"
|
||||
#include "physics_engine.hpp"
|
||||
|
||||
namespace mocca::detail {
|
||||
|
||||
[[nodiscard]] KernelState build_kernel_config(
|
||||
const NumericTables& numeric_tables,
|
||||
const SimulationConfig& config);
|
||||
|
||||
} // namespace mocca::detail
|
||||
@@ -0,0 +1,395 @@
|
||||
// Generated by tools/export_embedded_tables.py from muon00.f block-data tables.
|
||||
// Do not edit this file by hand; regenerate it when the bundled coefficient source changes.
|
||||
#include "embedded_tables.hpp"
|
||||
|
||||
namespace mocca::detail {
|
||||
|
||||
NumericTables bundled_numeric_tables() {
|
||||
return NumericTables{
|
||||
{"COEDP", std::vector<double>{
|
||||
21.33333,
|
||||
42.66667,
|
||||
3.555556,
|
||||
113.7778,
|
||||
7.111111,
|
||||
56.88889,
|
||||
1024,
|
||||
22.75556,
|
||||
1228.8
|
||||
}},
|
||||
{"COEQ", std::vector<double>{
|
||||
0.06666667,
|
||||
0.08888889,
|
||||
0.0006944444,
|
||||
0.01,
|
||||
0.0009375,
|
||||
4.064421e-05,
|
||||
0.00351166,
|
||||
6.503074e-05,
|
||||
0.02107,
|
||||
9.290105e-05,
|
||||
2.064468e-06
|
||||
}},
|
||||
{"COEO", std::vector<double>{
|
||||
0.001693122,
|
||||
0.01015873,
|
||||
1.984127e-05,
|
||||
0.000212585,
|
||||
3.985969e-07,
|
||||
5.734633e-08,
|
||||
1.47462e-05,
|
||||
5.461556e-09,
|
||||
2.654316e-05,
|
||||
7.646177e-07,
|
||||
1.365389e-06
|
||||
}},
|
||||
{"COED", std::vector<double>{
|
||||
1.333333,
|
||||
21.33333,
|
||||
10.66667,
|
||||
7.111111
|
||||
}},
|
||||
{"EXPMON", std::vector<double>{
|
||||
4.531799,
|
||||
4.706453,
|
||||
4.736786,
|
||||
3.471234,
|
||||
3.150354,
|
||||
3.052991,
|
||||
4.703517,
|
||||
3.45911,
|
||||
3.249048,
|
||||
3.150354,
|
||||
2.977822,
|
||||
2.917974,
|
||||
3.312419,
|
||||
2.695684,
|
||||
2.533755,
|
||||
4.914818,
|
||||
2.970119,
|
||||
2.700216,
|
||||
6.548446,
|
||||
3.199174,
|
||||
2.830713,
|
||||
2.695684,
|
||||
2.41503,
|
||||
2.323143,
|
||||
2.974074,
|
||||
2.526711,
|
||||
2.40295,
|
||||
2.41503,
|
||||
2.249824,
|
||||
2.188693
|
||||
}},
|
||||
{"EXPDIP", std::vector<double>{
|
||||
3.766185,
|
||||
4.397381,
|
||||
4.511374,
|
||||
2.429543,
|
||||
2.72324,
|
||||
2.747787,
|
||||
2.976262,
|
||||
2.927947,
|
||||
2.886848,
|
||||
4.07693,
|
||||
3.354237,
|
||||
3.192531,
|
||||
2.72324,
|
||||
2.747093,
|
||||
2.736171,
|
||||
2.064114,
|
||||
2.209579,
|
||||
2.187568,
|
||||
2.546045,
|
||||
2.374398,
|
||||
2.298389,
|
||||
2.876576,
|
||||
2.498902,
|
||||
2.38535,
|
||||
3.993301,
|
||||
2.876576,
|
||||
2.652421,
|
||||
5.778643,
|
||||
3.134388,
|
||||
2.797892,
|
||||
2.209579,
|
||||
2.154546,
|
||||
2.118914,
|
||||
2.374398,
|
||||
2.234588,
|
||||
2.178575,
|
||||
2.876576,
|
||||
2.498902,
|
||||
2.38535,
|
||||
2.154546,
|
||||
2.084353,
|
||||
2.051376
|
||||
}},
|
||||
{"EXPQUA", std::vector<double>{
|
||||
3.531162,
|
||||
4.240205,
|
||||
4.388131,
|
||||
2.543817,
|
||||
2.683365,
|
||||
2.694792,
|
||||
2.131239,
|
||||
2.520683,
|
||||
2.5795,
|
||||
2.566813,
|
||||
2.816281,
|
||||
2.81955,
|
||||
2.520683,
|
||||
2.607425,
|
||||
2.618563,
|
||||
1.726067,
|
||||
1.977932,
|
||||
1.998559,
|
||||
2.061514,
|
||||
2.104925,
|
||||
2.087356,
|
||||
2.283507,
|
||||
2.199915,
|
||||
2.155173,
|
||||
2.183688,
|
||||
2.283507,
|
||||
2.24505,
|
||||
2.663082,
|
||||
2.438142,
|
||||
2.347529,
|
||||
1.977932,
|
||||
1.998217,
|
||||
1.987446,
|
||||
2.104925,
|
||||
2.062287,
|
||||
2.036313,
|
||||
4.34099,
|
||||
2.986053,
|
||||
2.726274,
|
||||
2.283507,
|
||||
2.199915,
|
||||
2.155173,
|
||||
1.998217,
|
||||
1.972396,
|
||||
1.956081
|
||||
}},
|
||||
{"EXPOCT", std::vector<double>{
|
||||
3.429305,
|
||||
4.153371,
|
||||
4.322206,
|
||||
1.982441,
|
||||
2.395708,
|
||||
2.470619,
|
||||
2.341001,
|
||||
2.538774,
|
||||
2.572215,
|
||||
2.195239,
|
||||
2.571516,
|
||||
2.623072,
|
||||
2.395708,
|
||||
2.51249,
|
||||
2.537093,
|
||||
1.558161,
|
||||
1.837921,
|
||||
1.876801,
|
||||
1.841217,
|
||||
1.946305,
|
||||
1.953014,
|
||||
2.017891,
|
||||
2.0263,
|
||||
2.011369,
|
||||
1.778407,
|
||||
2.017891,
|
||||
2.032372,
|
||||
2.117493,
|
||||
2.140989,
|
||||
2.117166,
|
||||
1.837921,
|
||||
1.89234,
|
||||
1.895838,
|
||||
1.946305,
|
||||
1.948185,
|
||||
1.938261,
|
||||
2.252893,
|
||||
2.331797,
|
||||
2.283271,
|
||||
2.017891,
|
||||
2.0263,
|
||||
2.011369,
|
||||
1.89234,
|
||||
1.921936,
|
||||
1.898511
|
||||
}},
|
||||
{"COEMON", std::vector<double>{
|
||||
0.9278462,
|
||||
-0.04686876,
|
||||
0.00260669,
|
||||
0.3258145,
|
||||
-0.01650726,
|
||||
0.0009192955,
|
||||
-0.08728775,
|
||||
0.005557533,
|
||||
-0.0003299306,
|
||||
0.01650726,
|
||||
-0.000788986,
|
||||
1.644805e-05,
|
||||
0.1837462,
|
||||
-0.009081039000000001,
|
||||
0.0005039261,
|
||||
-0.07674657,
|
||||
0.004090333,
|
||||
-0.000241485,
|
||||
0.006976183,
|
||||
-0.0003308179,
|
||||
2.022719e-05,
|
||||
0.009886183,
|
||||
-0.0004700158,
|
||||
9.786634e-06,
|
||||
-0.001115213,
|
||||
5.895603e-05,
|
||||
-1.271168e-06,
|
||||
4.52273e-05,
|
||||
-1.882362e-06,
|
||||
2.089122e-08
|
||||
}},
|
||||
{"COEDIP", std::vector<double>{
|
||||
0.09864278,
|
||||
-0.003552196,
|
||||
6.908681e-05,
|
||||
0.02415974,
|
||||
-0.0008812568999999999,
|
||||
1.721286e-05,
|
||||
-0.006819236,
|
||||
0.0003101739,
|
||||
-6.390954e-06,
|
||||
0.07532858000000001,
|
||||
-0.004445121,
|
||||
0.0002592835,
|
||||
0.0002203142,
|
||||
-8.210685e-06,
|
||||
8.801341e-08,
|
||||
0.0323546,
|
||||
-0.001181545,
|
||||
2.3038e-05,
|
||||
-0.01225112,
|
||||
0.0005542601,
|
||||
-1.140757e-05,
|
||||
0.0008892663,
|
||||
-4.580119e-05,
|
||||
9.800079e-07,
|
||||
0.01211143,
|
||||
-0.0006669497,
|
||||
3.872454e-05,
|
||||
-0.001512719,
|
||||
7.933719e-05,
|
||||
-4.808937e-06,
|
||||
4.376093e-05,
|
||||
-1.627056e-06,
|
||||
1.743224e-08,
|
||||
-5.132038e-06,
|
||||
2.09574e-07,
|
||||
-2.313305e-09,
|
||||
0.0002223166,
|
||||
-1.14503e-05,
|
||||
2.45002e-07,
|
||||
9.039198e-08,
|
||||
-3.079553e-09,
|
||||
2.088144e-11
|
||||
}},
|
||||
{"COEQUA", std::vector<double>{
|
||||
0.1411675,
|
||||
-0.003944496,
|
||||
3.94504e-05,
|
||||
0.3270497,
|
||||
-0.01133197,
|
||||
0.0001194093,
|
||||
-0.2776085,
|
||||
0.007846003000000001,
|
||||
-7.866167e-05,
|
||||
0.4601989,
|
||||
-0.01831626,
|
||||
0.000367533,
|
||||
0.003487113,
|
||||
-0.0001060614,
|
||||
6.944398e-07,
|
||||
1.25312,
|
||||
-0.03541941,
|
||||
0.0003549579,
|
||||
-0.4926037,
|
||||
0.01705249,
|
||||
-0.0001796112,
|
||||
0.03681114,
|
||||
-0.001438755,
|
||||
1.569441e-05,
|
||||
0.462617,
|
||||
-0.01840557,
|
||||
0.000369003,
|
||||
-0.04608811,
|
||||
0.00222026,
|
||||
-4.671028e-05,
|
||||
0.00787098,
|
||||
-0.0002392062,
|
||||
1.565542e-06,
|
||||
-0.0009473607,
|
||||
3.134314e-05,
|
||||
-2.107953e-07,
|
||||
0.04492433,
|
||||
-0.002608238,
|
||||
0.0001552426,
|
||||
0.009202785999999999,
|
||||
-0.0003596887,
|
||||
3.923602e-06,
|
||||
5.980154e-05,
|
||||
-1.722622e-06,
|
||||
7.889632e-09
|
||||
}},
|
||||
{"COEOCT", std::vector<double>{
|
||||
0.01835095,
|
||||
-0.0004182848,
|
||||
2.558331e-06,
|
||||
0.1444508,
|
||||
-0.003328672,
|
||||
2.037957e-05,
|
||||
-0.04367688,
|
||||
0.001223204,
|
||||
-7.847840000000001e-06,
|
||||
0.3021241,
|
||||
-0.008987655000000001,
|
||||
9.179068999999999e-05,
|
||||
0.0044383,
|
||||
-0.000114052,
|
||||
5.042711e-07,
|
||||
1.467168,
|
||||
-0.03379181,
|
||||
0.0002068045,
|
||||
-0.592403,
|
||||
0.01655453,
|
||||
-0.0001061699,
|
||||
0.04508567,
|
||||
-0.001416945,
|
||||
9.394653e-06,
|
||||
0.6820167,
|
||||
-0.02028855,
|
||||
0.000207118,
|
||||
-0.0691095,
|
||||
0.002487891,
|
||||
-2.660315e-05,
|
||||
0.0225287,
|
||||
-0.000578585,
|
||||
2.556806e-06,
|
||||
-0.002759089,
|
||||
7.677244000000001e-05,
|
||||
-3.479514e-07,
|
||||
0.3778402,
|
||||
-0.01582449,
|
||||
0.0003229544,
|
||||
0.004508567,
|
||||
-0.0001416945,
|
||||
9.394653e-07,
|
||||
1.285744e-06,
|
||||
-3.25799e-08,
|
||||
1.067759e-10
|
||||
}},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace mocca::detail
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "physics_engine.hpp"
|
||||
|
||||
namespace mocca::detail {
|
||||
|
||||
// Bump this identifier whenever the embedded coefficient tables are regenerated
|
||||
// from a materially different published source.
|
||||
inline constexpr std::string_view kBundledCoefficientTableId = "aama_v1_0_block_data_v1";
|
||||
|
||||
[[nodiscard]] NumericTables bundled_numeric_tables();
|
||||
|
||||
} // namespace mocca::detail
|
||||
+515
@@ -0,0 +1,515 @@
|
||||
#include "mocca/json.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
namespace mocca {
|
||||
|
||||
JsonValue::JsonValue(std::nullptr_t) : data_(nullptr) {}
|
||||
JsonValue::JsonValue(bool value) : data_(value) {}
|
||||
JsonValue::JsonValue(double value) : data_(value) {}
|
||||
JsonValue::JsonValue(int value) : data_(static_cast<double>(value)) {}
|
||||
JsonValue::JsonValue(std::string value) : data_(std::move(value)) {}
|
||||
JsonValue::JsonValue(const char* value) {
|
||||
if (value == nullptr) {
|
||||
throw std::runtime_error("JSON string pointer is null");
|
||||
}
|
||||
data_ = std::string(value);
|
||||
}
|
||||
JsonValue::JsonValue(Array value) : data_(std::move(value)) {}
|
||||
JsonValue::JsonValue(Object value) : data_(std::move(value)) {}
|
||||
|
||||
bool JsonValue::is_null() const {
|
||||
return std::holds_alternative<std::nullptr_t>(data_);
|
||||
}
|
||||
|
||||
bool JsonValue::is_bool() const {
|
||||
return std::holds_alternative<bool>(data_);
|
||||
}
|
||||
|
||||
bool JsonValue::is_number() const {
|
||||
return std::holds_alternative<double>(data_);
|
||||
}
|
||||
|
||||
bool JsonValue::is_string() const {
|
||||
return std::holds_alternative<std::string>(data_);
|
||||
}
|
||||
|
||||
bool JsonValue::is_array() const {
|
||||
return std::holds_alternative<Array>(data_);
|
||||
}
|
||||
|
||||
bool JsonValue::is_object() const {
|
||||
return std::holds_alternative<Object>(data_);
|
||||
}
|
||||
|
||||
bool JsonValue::as_bool() const {
|
||||
if (!is_bool()) {
|
||||
throw std::runtime_error("JSON value is not a bool");
|
||||
}
|
||||
return std::get<bool>(data_);
|
||||
}
|
||||
|
||||
double JsonValue::as_number() const {
|
||||
if (!is_number()) {
|
||||
throw std::runtime_error("JSON value is not a number");
|
||||
}
|
||||
return std::get<double>(data_);
|
||||
}
|
||||
|
||||
const std::string& JsonValue::as_string() const {
|
||||
if (!is_string()) {
|
||||
throw std::runtime_error("JSON value is not a string");
|
||||
}
|
||||
return std::get<std::string>(data_);
|
||||
}
|
||||
|
||||
const JsonValue::Array& JsonValue::as_array() const {
|
||||
if (!is_array()) {
|
||||
throw std::runtime_error("JSON value is not an array");
|
||||
}
|
||||
return std::get<Array>(data_);
|
||||
}
|
||||
|
||||
const JsonValue::Object& JsonValue::as_object() const {
|
||||
if (!is_object()) {
|
||||
throw std::runtime_error("JSON value is not an object");
|
||||
}
|
||||
return std::get<Object>(data_);
|
||||
}
|
||||
|
||||
JsonValue::Array& JsonValue::as_array() {
|
||||
if (!is_array()) {
|
||||
throw std::runtime_error("JSON value is not an array");
|
||||
}
|
||||
return std::get<Array>(data_);
|
||||
}
|
||||
|
||||
JsonValue::Object& JsonValue::as_object() {
|
||||
if (!is_object()) {
|
||||
throw std::runtime_error("JSON value is not an object");
|
||||
}
|
||||
return std::get<Object>(data_);
|
||||
}
|
||||
|
||||
bool JsonValue::contains(const std::string& key) const {
|
||||
if (!is_object()) {
|
||||
return false;
|
||||
}
|
||||
return as_object().contains(key);
|
||||
}
|
||||
|
||||
const JsonValue& JsonValue::at(const std::string& key) const {
|
||||
return as_object().at(key);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(std::string_view text) : text_(text) {}
|
||||
|
||||
JsonValue parse() {
|
||||
skip_ws();
|
||||
JsonValue value = parse_value();
|
||||
skip_ws();
|
||||
if (pos_ != text_.size()) {
|
||||
throw error("Unexpected trailing JSON content");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::runtime_error error(const std::string& message) const {
|
||||
std::ostringstream out;
|
||||
out << message << " at byte " << pos_;
|
||||
return std::runtime_error(out.str());
|
||||
}
|
||||
|
||||
void skip_ws() {
|
||||
while (pos_ < text_.size() && std::isspace(static_cast<unsigned char>(text_[pos_])) != 0) {
|
||||
++pos_;
|
||||
}
|
||||
}
|
||||
|
||||
char peek() const {
|
||||
if (pos_ >= text_.size()) {
|
||||
throw error("Unexpected end of JSON");
|
||||
}
|
||||
return text_[pos_];
|
||||
}
|
||||
|
||||
char consume() {
|
||||
const char ch = peek();
|
||||
++pos_;
|
||||
return ch;
|
||||
}
|
||||
|
||||
void expect(char expected) {
|
||||
const char actual = consume();
|
||||
if (actual != expected) {
|
||||
std::ostringstream out;
|
||||
out << "Expected '" << expected << "' but found '" << actual << "'";
|
||||
throw error(out.str());
|
||||
}
|
||||
}
|
||||
|
||||
bool consume_if(char ch) {
|
||||
if (pos_ < text_.size() && text_[pos_] == ch) {
|
||||
++pos_;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonValue parse_value() {
|
||||
skip_ws();
|
||||
switch (peek()) {
|
||||
case 'n':
|
||||
parse_keyword("null");
|
||||
return JsonValue(nullptr);
|
||||
case 't':
|
||||
parse_keyword("true");
|
||||
return JsonValue(true);
|
||||
case 'f':
|
||||
parse_keyword("false");
|
||||
return JsonValue(false);
|
||||
case '"':
|
||||
return JsonValue(parse_string());
|
||||
case '[':
|
||||
return JsonValue(parse_array());
|
||||
case '{':
|
||||
return JsonValue(parse_object());
|
||||
default:
|
||||
return JsonValue(parse_number());
|
||||
}
|
||||
}
|
||||
|
||||
void parse_keyword(std::string_view keyword) {
|
||||
for (char ch : keyword) {
|
||||
if (consume() != ch) {
|
||||
throw error("Invalid JSON keyword");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string parse_string() {
|
||||
expect('"');
|
||||
std::string value;
|
||||
while (true) {
|
||||
if (pos_ >= text_.size()) {
|
||||
throw error("Unterminated JSON string");
|
||||
}
|
||||
const char ch = consume();
|
||||
if (ch == '"') {
|
||||
return value;
|
||||
}
|
||||
if (ch != '\\') {
|
||||
if (static_cast<unsigned char>(ch) < 0x20) {
|
||||
throw error("Unescaped control character in JSON string");
|
||||
}
|
||||
value.push_back(ch);
|
||||
continue;
|
||||
}
|
||||
if (pos_ >= text_.size()) {
|
||||
throw error("Unterminated JSON escape");
|
||||
}
|
||||
const char escape = consume();
|
||||
switch (escape) {
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
value.push_back(escape);
|
||||
break;
|
||||
case 'b':
|
||||
value.push_back('\b');
|
||||
break;
|
||||
case 'f':
|
||||
value.push_back('\f');
|
||||
break;
|
||||
case 'n':
|
||||
value.push_back('\n');
|
||||
break;
|
||||
case 'r':
|
||||
value.push_back('\r');
|
||||
break;
|
||||
case 't':
|
||||
value.push_back('\t');
|
||||
break;
|
||||
case 'u': {
|
||||
std::uint32_t codepoint = parse_hex_codepoint();
|
||||
if (codepoint >= 0xD800 && codepoint <= 0xDBFF) {
|
||||
expect('\\');
|
||||
expect('u');
|
||||
const std::uint32_t low = parse_hex_codepoint();
|
||||
if (low < 0xDC00 || low > 0xDFFF) {
|
||||
throw error("Invalid low surrogate in JSON string");
|
||||
}
|
||||
codepoint = 0x10000 + ((codepoint - 0xD800) << 10) + (low - 0xDC00);
|
||||
} else if (codepoint >= 0xDC00 && codepoint <= 0xDFFF) {
|
||||
throw error("Unexpected low surrogate in JSON string");
|
||||
}
|
||||
append_utf8(value, codepoint);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw error("Invalid JSON escape");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int hex_digit(char ch) const {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
return ch - '0';
|
||||
}
|
||||
if (ch >= 'a' && ch <= 'f') {
|
||||
return 10 + ch - 'a';
|
||||
}
|
||||
if (ch >= 'A' && ch <= 'F') {
|
||||
return 10 + ch - 'A';
|
||||
}
|
||||
throw error("Invalid hex digit in JSON unicode escape");
|
||||
}
|
||||
|
||||
std::uint32_t parse_hex_codepoint() {
|
||||
std::uint32_t value = 0;
|
||||
for (int index = 0; index < 4; ++index) {
|
||||
value = (value << 4) | static_cast<std::uint32_t>(hex_digit(consume()));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void append_utf8(std::string& out, std::uint32_t codepoint) const {
|
||||
if (codepoint <= 0x7F) {
|
||||
out.push_back(static_cast<char>(codepoint));
|
||||
return;
|
||||
}
|
||||
if (codepoint <= 0x7FF) {
|
||||
out.push_back(static_cast<char>(0xC0 | (codepoint >> 6)));
|
||||
out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||
return;
|
||||
}
|
||||
if (codepoint <= 0xFFFF) {
|
||||
out.push_back(static_cast<char>(0xE0 | (codepoint >> 12)));
|
||||
out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||
out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||
return;
|
||||
}
|
||||
if (codepoint <= 0x10FFFF) {
|
||||
out.push_back(static_cast<char>(0xF0 | (codepoint >> 18)));
|
||||
out.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
|
||||
out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||
out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||
return;
|
||||
}
|
||||
throw error("Unicode codepoint is outside the valid UTF-8 range");
|
||||
}
|
||||
|
||||
double parse_number() {
|
||||
const std::size_t start = pos_;
|
||||
consume_if('-');
|
||||
if (consume_if('0')) {
|
||||
if (pos_ < text_.size() && std::isdigit(static_cast<unsigned char>(text_[pos_])) != 0) {
|
||||
throw error("Leading zeros are not permitted in JSON numbers");
|
||||
}
|
||||
} else {
|
||||
if (!std::isdigit(static_cast<unsigned char>(peek()))) {
|
||||
throw error("Invalid JSON number");
|
||||
}
|
||||
while (pos_ < text_.size() && std::isdigit(static_cast<unsigned char>(text_[pos_])) != 0) {
|
||||
++pos_;
|
||||
}
|
||||
}
|
||||
if (consume_if('.')) {
|
||||
if (pos_ >= text_.size() || std::isdigit(static_cast<unsigned char>(text_[pos_])) == 0) {
|
||||
throw error("Invalid JSON fractional part");
|
||||
}
|
||||
while (pos_ < text_.size() && std::isdigit(static_cast<unsigned char>(text_[pos_])) != 0) {
|
||||
++pos_;
|
||||
}
|
||||
}
|
||||
if (consume_if('e') || consume_if('E')) {
|
||||
consume_if('+') || consume_if('-');
|
||||
if (pos_ >= text_.size() || std::isdigit(static_cast<unsigned char>(text_[pos_])) == 0) {
|
||||
throw error("Invalid JSON exponent");
|
||||
}
|
||||
while (pos_ < text_.size() && std::isdigit(static_cast<unsigned char>(text_[pos_])) != 0) {
|
||||
++pos_;
|
||||
}
|
||||
}
|
||||
const std::string token(text_.substr(start, pos_ - start));
|
||||
return std::stod(token);
|
||||
}
|
||||
|
||||
JsonValue::Array parse_array() {
|
||||
expect('[');
|
||||
skip_ws();
|
||||
JsonValue::Array values;
|
||||
if (consume_if(']')) {
|
||||
return values;
|
||||
}
|
||||
while (true) {
|
||||
values.push_back(parse_value());
|
||||
skip_ws();
|
||||
if (consume_if(']')) {
|
||||
return values;
|
||||
}
|
||||
expect(',');
|
||||
skip_ws();
|
||||
}
|
||||
}
|
||||
|
||||
JsonValue::Object parse_object() {
|
||||
expect('{');
|
||||
skip_ws();
|
||||
JsonValue::Object values;
|
||||
if (consume_if('}')) {
|
||||
return values;
|
||||
}
|
||||
while (true) {
|
||||
skip_ws();
|
||||
if (peek() != '"') {
|
||||
throw error("Expected object key");
|
||||
}
|
||||
std::string key = parse_string();
|
||||
skip_ws();
|
||||
expect(':');
|
||||
skip_ws();
|
||||
JsonValue parsed_value = parse_value();
|
||||
const auto [it, inserted] = values.emplace(key, std::move(parsed_value));
|
||||
if (!inserted) {
|
||||
throw error("Duplicate object key \"" + key + "\"");
|
||||
}
|
||||
skip_ws();
|
||||
if (consume_if('}')) {
|
||||
return values;
|
||||
}
|
||||
expect(',');
|
||||
skip_ws();
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view text_;
|
||||
std::size_t pos_{0};
|
||||
};
|
||||
|
||||
std::string indent_string(int level, int indent) {
|
||||
return std::string(static_cast<std::size_t>(level * indent), ' ');
|
||||
}
|
||||
|
||||
std::string escape_string(const std::string& input) {
|
||||
std::ostringstream out;
|
||||
out << '"';
|
||||
for (unsigned char ch : input) {
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
out << "\\\\";
|
||||
break;
|
||||
case '"':
|
||||
out << "\\\"";
|
||||
break;
|
||||
case '\n':
|
||||
out << "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
out << "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
out << "\\t";
|
||||
break;
|
||||
default:
|
||||
if (ch < 0x20) {
|
||||
out << "\\u" << std::uppercase << std::hex << std::setw(4) << std::setfill('0')
|
||||
<< static_cast<int>(ch)
|
||||
<< std::nouppercase << std::dec << std::setfill(' ');
|
||||
} else {
|
||||
out << static_cast<char>(ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
out << '"';
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string number_string(double value) {
|
||||
if (!std::isfinite(value)) {
|
||||
throw std::runtime_error("Cannot serialize non-finite JSON number");
|
||||
}
|
||||
std::ostringstream out;
|
||||
out << std::setprecision(std::numeric_limits<double>::max_digits10) << value;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
void serialize_impl(const JsonValue& value, int indent, int level, std::ostringstream& out) {
|
||||
if (value.is_null()) {
|
||||
out << "null";
|
||||
return;
|
||||
}
|
||||
if (value.is_bool()) {
|
||||
out << (value.as_bool() ? "true" : "false");
|
||||
return;
|
||||
}
|
||||
if (value.is_number()) {
|
||||
out << number_string(value.as_number());
|
||||
return;
|
||||
}
|
||||
if (value.is_string()) {
|
||||
out << escape_string(value.as_string());
|
||||
return;
|
||||
}
|
||||
if (value.is_array()) {
|
||||
const auto& array = value.as_array();
|
||||
if (array.empty()) {
|
||||
out << "[]";
|
||||
return;
|
||||
}
|
||||
out << "[\n";
|
||||
for (std::size_t i = 0; i < array.size(); ++i) {
|
||||
out << indent_string(level + 1, indent);
|
||||
serialize_impl(array[i], indent, level + 1, out);
|
||||
if (i + 1 != array.size()) {
|
||||
out << ',';
|
||||
}
|
||||
out << '\n';
|
||||
}
|
||||
out << indent_string(level, indent) << ']';
|
||||
return;
|
||||
}
|
||||
const auto& object = value.as_object();
|
||||
if (object.empty()) {
|
||||
out << "{}";
|
||||
return;
|
||||
}
|
||||
out << "{\n";
|
||||
std::size_t index = 0;
|
||||
for (const auto& [key, child] : object) {
|
||||
out << indent_string(level + 1, indent) << escape_string(key) << ": ";
|
||||
serialize_impl(child, indent, level + 1, out);
|
||||
if (index + 1 != object.size()) {
|
||||
out << ',';
|
||||
}
|
||||
out << '\n';
|
||||
++index;
|
||||
}
|
||||
out << indent_string(level, indent) << '}';
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
JsonValue parse_json(std::string_view text) {
|
||||
return Parser(text).parse();
|
||||
}
|
||||
|
||||
std::string to_json_string(const JsonValue& value, int indent) {
|
||||
std::ostringstream out;
|
||||
serialize_impl(value, indent, 0, out);
|
||||
out << '\n';
|
||||
return out.str();
|
||||
}
|
||||
|
||||
} // namespace mocca
|
||||
+518
@@ -0,0 +1,518 @@
|
||||
#include "mocca/kernel.hpp"
|
||||
|
||||
#include "config_bridge.hpp"
|
||||
#include "embedded_tables.hpp"
|
||||
#include "line_codec.hpp"
|
||||
#include "physics_engine.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace mocca {
|
||||
|
||||
class ModernCascadeKernel::Impl {
|
||||
public:
|
||||
explicit Impl(const SimulationConfig& config)
|
||||
: config_(config),
|
||||
tables_(detail::bundled_numeric_tables()),
|
||||
primitives_(tables_, config_.numerics.matrix_element_precision_digits) {}
|
||||
|
||||
[[nodiscard]] SimulationResult run() const {
|
||||
detail::KernelState cfg = detail::build_kernel_config(tables_, config_);
|
||||
primitives_.prepare_case(cfg);
|
||||
auto [lyman_sum, states] = propagate_cascade(cfg);
|
||||
auto lines = detail::collect_lines(cfg);
|
||||
return SimulationResult{
|
||||
lyman_sum,
|
||||
static_cast<int>(lines.size()),
|
||||
std::move(lines),
|
||||
std::move(states),
|
||||
cfg.warnings,
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] static std::array<double, 5> popj(int l1, int l2, int ll) {
|
||||
std::array<double, 5> p{};
|
||||
const int li = ll + 1;
|
||||
const int l = std::max(l1, l2);
|
||||
if (li == 1) {
|
||||
p[1] = static_cast<double>(l + 1) / static_cast<double>(2 * l + 1);
|
||||
p[2] = 1.0 - p[1];
|
||||
return p;
|
||||
}
|
||||
if (li == 2) {
|
||||
p[1] = static_cast<double>((l + 1) * (2 * l - 1)) / static_cast<double>(4 * l * l - 1);
|
||||
p[2] = 1.0 / static_cast<double>(4 * l * l - 1);
|
||||
p[3] = 1.0 - (p[1] + p[2]);
|
||||
return p;
|
||||
}
|
||||
if (li == 3) {
|
||||
if (l1 != l2) {
|
||||
p[1] = static_cast<double>(l + 1) / static_cast<double>(2 * l + 1);
|
||||
p[2] = 2.0 / static_cast<double>((2 * l - 3) * (2 * l + 1));
|
||||
p[3] = 1.0 - (p[1] + p[2]);
|
||||
return p;
|
||||
}
|
||||
const double d = static_cast<double>((2 * l + 1) * (2 * l + 1));
|
||||
p[1] = static_cast<double>((l + 2) * (2 * l - 1)) / d;
|
||||
p[2] = static_cast<double>((l - 1) * (2 * l + 3)) / d;
|
||||
p[3] = 3.0 / d;
|
||||
p[4] = p[3];
|
||||
return p;
|
||||
}
|
||||
if (std::abs(l1 - l2) != 1) {
|
||||
p[1] = static_cast<double>(l + 1) / static_cast<double>(2 * l + 1);
|
||||
p[2] = 3.0 / static_cast<double>((2 * l - 5) * (2 * l + 1));
|
||||
p[3] = 1.0 - (p[1] + p[2]);
|
||||
return p;
|
||||
}
|
||||
const double d = static_cast<double>(4 * l * l - 1);
|
||||
p[1] = static_cast<double>((2 * l - 3) * (l + 2)) / d;
|
||||
p[2] = 5.0 / d;
|
||||
p[3] = 6.0 / d;
|
||||
p[4] = 1.0 - (p[1] + p[2] + p[3]);
|
||||
return p;
|
||||
}
|
||||
|
||||
[[nodiscard]] static double beta(int l1, int j1, int l2, int j2, int l) {
|
||||
const double a1 = 0.25 * static_cast<double>(j1 * (j1 + 2));
|
||||
const double a2 = 0.25 * static_cast<double>(j2 * (j2 + 2));
|
||||
return (
|
||||
(a2 - static_cast<double>(l2 * (l2 + 1)) + 0.750) /
|
||||
(a1 - static_cast<double>(l1 * (l1 + 1)) + 0.750)) *
|
||||
((a1 + a2 - static_cast<double>(l * (l + 1))) / (2.0 * a2));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<double, std::vector<StateSummary>> propagate_cascade(
|
||||
detail::KernelState& cfg) const {
|
||||
// The kernel keeps the original one-based workspace layout to preserve
|
||||
// the validated cascade-update order while expressing it in explicit
|
||||
// C++ containers instead of the monolithic runner entry point.
|
||||
std::vector<double> popt(4, 0.0);
|
||||
const std::vector<double> pop1 = cfg.pop;
|
||||
std::vector<double> pop2(7, 0.0);
|
||||
popt[1] = 2.0 * cfg.pop[1];
|
||||
popt[2] = 2.0 * cfg.pop[2] + 6.0 * cfg.pop[3];
|
||||
popt[3] = 2.0 * cfg.pop[4] + 6.0 * cfg.pop[5] + 10.0 * cfg.pop[6];
|
||||
pop2[1] = pop1[1];
|
||||
pop2[2] = (popt[2] <= 1.0e-20) ? 1.0 : 1.0 / popt[2];
|
||||
pop2[3] = (popt[2] <= 1.0e-20) ? 1.0 : 1.0 / popt[2];
|
||||
pop2[4] = (popt[3] <= 1.0e-20) ? 1.0 : 1.0 / popt[3];
|
||||
pop2[5] = (popt[3] <= 1.0e-20) ? 1.0 : 1.0 / popt[3];
|
||||
pop2[6] = (popt[3] <= 1.0e-20) ? 1.0 : 1.0 / popt[3];
|
||||
|
||||
const int ms = 3;
|
||||
const double rk = cfg.widthk / cfg.hbar;
|
||||
const int nu = cfg.nmax * (cfg.nmax + 1) / 2;
|
||||
std::vector<double> pnl(nu + 1, 0.0);
|
||||
std::vector<double> polpos(nu + 1, 0.0);
|
||||
std::vector<double> polneg(nu + 1, 0.0);
|
||||
std::vector<double> width(nu + 1, 0.0);
|
||||
std::vector<double> convc(nu + 1, 0.0);
|
||||
std::vector<double> sporb(nu + 1, 0.0);
|
||||
std::vector<double> pc0(nu + 1, 0.0);
|
||||
std::vector<double> pc1(nu + 1, 0.0);
|
||||
std::vector<double> pc2(nu + 1, 0.0);
|
||||
std::vector<std::vector<double>> pc(ms + 1, std::vector<double>(nu + 1, 0.0));
|
||||
const int mu = cfg.nmax * (cfg.nmax - 1) / 2;
|
||||
|
||||
for (int j = 1; j <= nu; ++j) {
|
||||
if (cfg.ip8 != 0) {
|
||||
pnl[j] = cfg.pln[j];
|
||||
}
|
||||
}
|
||||
for (int j = 1; j <= cfg.nmax; ++j) {
|
||||
const int jj = mu + j;
|
||||
if (cfg.ip8 == 0) {
|
||||
pnl[jj] = cfg.pl[j];
|
||||
}
|
||||
pc1[jj] = 2.0 - 2.0 * cfg.pop[1];
|
||||
pc2[jj] = 2.0 * cfg.pop[1] - 1.0;
|
||||
if (cfg.pop[1] <= 0.5) {
|
||||
pc0[jj] = 1.0 - 2.0 * cfg.pop[1];
|
||||
pc1[jj] = 2.0 * cfg.pop[1];
|
||||
pc2[jj] = 0.0;
|
||||
}
|
||||
if (pnl[jj] > 1.0e-20) {
|
||||
polpos[jj] = (1.0 + 2.0 / static_cast<double>(2 * j - 1)) / 3.0;
|
||||
}
|
||||
if (pnl[jj] > 1.0e-20 && j != 1) {
|
||||
polneg[jj] = (1.0 - 2.0 / static_cast<double>(2 * j - 1)) / 3.0;
|
||||
}
|
||||
for (int shell = 1; shell <= ms; ++shell) {
|
||||
pc[shell][jj] = popt[shell];
|
||||
}
|
||||
}
|
||||
|
||||
double rlyman = 0.0;
|
||||
std::vector<double> zt(131, 0.0);
|
||||
std::vector<std::vector<double>> zt_shell(4, std::vector<double>(131, 0.0));
|
||||
std::vector<double> zr(131, 0.0);
|
||||
std::vector<double> za(131, 0.0);
|
||||
std::vector<double> za0(131, 0.0);
|
||||
std::vector<double> za1(131, 0.0);
|
||||
std::vector<double> za2(131, 0.0);
|
||||
for (int i1 = 1; i1 <= cfg.nmax; ++i1) {
|
||||
const int n1 = cfg.nmax + 1 - i1;
|
||||
for (int i2 = 1; i2 <= n1; ++i2) {
|
||||
const int l1 = i2 - 1;
|
||||
double rategt = 0.0;
|
||||
double rate0 = 2.0 * rk;
|
||||
double rate1 = rk;
|
||||
double rateauger = 0.0;
|
||||
double raterd = 0.0;
|
||||
const int k1 = detail::state_index(n1, l1);
|
||||
if (i1 > 1) {
|
||||
if (pnl[k1] < 1.0e-20) {
|
||||
pc[2][k1] = popt[2];
|
||||
pc[3][k1] = popt[3];
|
||||
pc0[k1] = 0.0;
|
||||
pc1[k1] = 2.0 - 2.0 * pop2[1];
|
||||
pc2[k1] = 2.0 * pop2[1] - 1.0;
|
||||
if (pop2[1] < 0.5) {
|
||||
pc0[k1] = 1.0 - 2.0 * pop2[1];
|
||||
pc1[k1] = 2.0 * pop2[1];
|
||||
pc2[k1] = 0.0;
|
||||
}
|
||||
pc[1][k1] = pc1[k1] + 2.0 * pc2[k1];
|
||||
} else {
|
||||
const double xnorm = 1.0 / pnl[k1];
|
||||
pc0[k1] *= xnorm;
|
||||
pc1[k1] *= xnorm;
|
||||
pc2[k1] *= xnorm;
|
||||
for (int shell = 2; shell <= ms; ++shell) {
|
||||
pc[shell][k1] = std::max(pc[shell][k1] * xnorm, 0.0);
|
||||
}
|
||||
pc[1][k1] = pc1[k1] + 2.0 * pc2[k1];
|
||||
pc[1][k1] = std::min(std::max(pc[1][k1], 0.0), 2.0);
|
||||
for (int shell = 1; shell <= ms; ++shell) {
|
||||
if (cfg.ipc[shell] != 0) {
|
||||
pc[shell][k1] = popt[shell];
|
||||
}
|
||||
}
|
||||
if (cfg.ipc[1] != 0) {
|
||||
pc2[k1] = 2.0 * pop1[1] - 1.0;
|
||||
pc1[k1] = 2.0 - 2.0 * pop1[1];
|
||||
pc0[k1] = 0.0;
|
||||
if (pop1[1] < 0.5) {
|
||||
pc2[k1] = 0.0;
|
||||
pc1[k1] = 2.0 * pop1[1];
|
||||
pc0[k1] = 1.0 - 2.0 * pop1[1];
|
||||
}
|
||||
}
|
||||
polpos[k1] = polpos[k1] * xnorm * static_cast<double>(2 * l1 + 1) /
|
||||
static_cast<double>(l1 + 1);
|
||||
if (cfg.npol[n1] - l1 == 0 || cfg.npol[n1] - l1 == 1) {
|
||||
polpos[k1] = (1.0 + 2.0 / static_cast<double>(2 * l1 + 1)) / 3.0;
|
||||
}
|
||||
if (l1 > 0) {
|
||||
polneg[k1] = polneg[k1] * xnorm * static_cast<double>(2 * l1 + 1) /
|
||||
static_cast<double>(l1);
|
||||
if (cfg.npol[n1] - l1 == 0 || cfg.npol[n1] - l1 == 1) {
|
||||
polneg[k1] = (1.0 - 2.0 / static_cast<double>(2 * l1 + 1)) / 3.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (n1 == 1) {
|
||||
continue;
|
||||
}
|
||||
double pc012 = pc0[k1] + pc1[k1] + pc2[k1];
|
||||
if (std::abs(pc012) < 1.0e-20) {
|
||||
pc012 = 1.0;
|
||||
}
|
||||
pc0[k1] /= pc012;
|
||||
pc1[k1] /= pc012;
|
||||
pc2[k1] /= pc012;
|
||||
|
||||
cfg.pop[1] = 0.5 * pc[1][k1];
|
||||
cfg.pop[2] = pop2[2] * pc[2][k1];
|
||||
cfg.pop[3] = pop2[3] * pc[2][k1];
|
||||
cfg.pop[4] = pop2[4] * pc[3][k1];
|
||||
cfg.pop[5] = pop2[5] * pc[3][k1];
|
||||
cfg.pop[6] = pop2[6] * pc[3][k1];
|
||||
std::fill(zt.begin(), zt.end(), 0.0);
|
||||
for (auto& shell : zt_shell) {
|
||||
std::fill(shell.begin(), shell.end(), 0.0);
|
||||
}
|
||||
std::fill(zr.begin(), zr.end(), 0.0);
|
||||
std::fill(za.begin(), za.end(), 0.0);
|
||||
std::fill(za0.begin(), za0.end(), 0.0);
|
||||
std::fill(za1.begin(), za1.end(), 0.0);
|
||||
std::fill(za2.begin(), za2.end(), 0.0);
|
||||
|
||||
int k = 0;
|
||||
for (int i3 = 1; i3 <= 7; ++i3) {
|
||||
const int l2 = l1 - 4 + i3;
|
||||
if (l2 < 0) {
|
||||
continue;
|
||||
}
|
||||
for (int i4 = 1; i4 <= n1; ++i4) {
|
||||
const int n2 = n1 - i4 + 1;
|
||||
if (n2 <= l2) {
|
||||
continue;
|
||||
}
|
||||
if (n2 == n1 && (n2 != 2 || l1 != 0 || l2 != 1)) {
|
||||
continue;
|
||||
}
|
||||
if (n1 == n2 && cfg.espm <= 1.0e-20) {
|
||||
continue;
|
||||
}
|
||||
k += 1;
|
||||
if (n1 == n2) {
|
||||
cfg.ijk = 1;
|
||||
cfg.energ = cfg.espm;
|
||||
} else {
|
||||
cfg.ijk = 0;
|
||||
}
|
||||
const double popq = cfg.pop[1];
|
||||
cfg.pop[1] = 1.0;
|
||||
zt[k] = primitives_.transition_rate(cfg, n1, l1, n2, l2) + 1.0e-10;
|
||||
rategt += zt[k];
|
||||
if (cfg.pop[1] < 1.0e-20) {
|
||||
cfg.pop[1] = 1.0;
|
||||
}
|
||||
cfg.rsa[1] = cfg.rsa[1] / cfg.pop[1];
|
||||
cfg.pop[1] = popq;
|
||||
zr[k] = cfg.rad;
|
||||
raterd += cfg.rad;
|
||||
za[k] = cfg.rsa[1];
|
||||
// `rad_to_auger` used to reconstruct the effective Auger
|
||||
// width from `effective_total - radiative`, which loses
|
||||
// several digits when the state is almost purely
|
||||
// radiative. Accumulating the Auger contribution directly
|
||||
// keeps the same physics while avoiding that cancellation.
|
||||
const double transition_auger = cfg.rau + 1.0e-10;
|
||||
rateauger +=
|
||||
pc2[k1] * transition_auger +
|
||||
pc1[k1] * (transition_auger - 0.5 * za[k]) +
|
||||
pc0[k1] * (transition_auger - za[k]);
|
||||
const double xr = zt[k] - cfg.rsa[1];
|
||||
double t0 = xr + 0.5 * cfg.rsa[1] + rk;
|
||||
double g0 = xr + 2.0 * rk + 1.0e-10;
|
||||
rate1 = rate1 + t0 - rk + 1.0e-10;
|
||||
rate0 = rate0 + xr + 1.0e-10;
|
||||
double xm = zt[k] - za[k] * (pc0[k1] + 0.5 * pc1[k1]);
|
||||
if (std::abs(xm) < 1.0e-10) {
|
||||
xm = 1.0;
|
||||
}
|
||||
if (std::abs(t0) < 1.0e-10) {
|
||||
t0 = 1.0;
|
||||
}
|
||||
if (std::abs(g0) < 1.0e-10) {
|
||||
g0 = 1.0;
|
||||
}
|
||||
if (std::abs(zt[k]) < 1.0e-10) {
|
||||
zt[k] = 1.0;
|
||||
}
|
||||
zt_shell[3][k] = pc[3][k1] - cfg.rsa[3] / xm;
|
||||
if (zt_shell[3][k] < 0.0) {
|
||||
zt_shell[3][k] = 0.0;
|
||||
}
|
||||
za2[k] = xr / zt[k] *
|
||||
(pc2[k1] + rk * pc1[k1] / t0 + 2.0 * rk * rk * pc0[k1] / t0 / g0);
|
||||
za1[k] = za[k] * pc2[k1] / zt[k] +
|
||||
(xr + rk * za[k] / zt[k]) / t0 *
|
||||
(pc1[k1] + 2.0 * rk * pc0[k1] / g0);
|
||||
za0[k] = pc1[k1] * za[k] / (2.0 * t0) +
|
||||
pc0[k1] / g0 * (xr + rk * za[k] / t0);
|
||||
const double ym = za[k] * (
|
||||
pc2[k1] / zt[k] +
|
||||
pc1[k1] * (0.5 + rk / zt[k]) / t0 +
|
||||
pc0[k1] * rk / t0 / g0 * (1.0 + 2.0 * rk / zt[k]));
|
||||
const double xl =
|
||||
rk * (pc1[k1] / t0 + 2.0 * pc0[k1] / t0 / g0 * (t0 + rk));
|
||||
zt_shell[1][k] = pc[1][k1] - ym + xl;
|
||||
zt_shell[2][k] = pc[2][k1] - cfg.rsa[2] / xm - xl;
|
||||
if (zt_shell[1][k] < 0.0) {
|
||||
zt_shell[1][k] = 0.0;
|
||||
}
|
||||
if (zt_shell[2][k] < 0.0) {
|
||||
zt_shell[2][k] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
width[k1] = (rategt * pc2[k1] + (rate1 - rk) * pc1[k1] +
|
||||
(rate0 - 2.0 * rk) * pc0[k1]) *
|
||||
cfg.hbar;
|
||||
if (width[k1] < 0.0) {
|
||||
width[k1] = 0.0;
|
||||
}
|
||||
if (std::abs(rate0) < 1.0e-10) {
|
||||
rate0 = 1.0;
|
||||
}
|
||||
if (std::abs(rate1) < 1.0e-10) {
|
||||
rate1 = 1.0;
|
||||
}
|
||||
if (std::abs(rategt) < 1.0e-10) {
|
||||
rategt = 1.0;
|
||||
}
|
||||
convc[k1] = raterd / (rateauger + 1.0e-10);
|
||||
if (convc[k1] < 0.0) {
|
||||
convc[k1] = 9.999e99;
|
||||
}
|
||||
if (l1 != 0) {
|
||||
sporb[k1] = 0.150 * std::pow(cfg.z, 4) /
|
||||
static_cast<double>(n1 * n1 * n1 * l1 * (l1 + 1));
|
||||
}
|
||||
|
||||
k = 0;
|
||||
for (int i3 = 1; i3 <= 7; ++i3) {
|
||||
const int l2 = l1 - 4 + i3;
|
||||
if (l2 < 0) {
|
||||
continue;
|
||||
}
|
||||
for (int i4 = 1; i4 <= n1; ++i4) {
|
||||
const int n2 = n1 - i4 + 1;
|
||||
if (n2 <= l2) {
|
||||
continue;
|
||||
}
|
||||
if (n2 == n1 && (n2 != 2 || l1 != 0 || l2 != 1)) {
|
||||
continue;
|
||||
}
|
||||
if (n2 == n1 && cfg.espm <= 1.0e-20) {
|
||||
continue;
|
||||
}
|
||||
k += 1;
|
||||
const int k2 = detail::state_index(n2, l2);
|
||||
const double bnorm = pnl[k1] * (
|
||||
pc2[k1] * zt[k] / rategt +
|
||||
pc1[k1] *
|
||||
(zt[k] - 0.5 * za[k] + rk * zt[k] / rategt) / rate1 +
|
||||
pc0[k1] *
|
||||
(zt[k] - za[k] + 2.0 * rk / rate1 *
|
||||
(zt[k] - 0.5 * za[k] + rk * zt[k] / rategt)) /
|
||||
rate0);
|
||||
for (int shell = 1; shell <= ms; ++shell) {
|
||||
pc[shell][k2] += zt_shell[shell][k] * bnorm;
|
||||
}
|
||||
pnl[k2] += bnorm;
|
||||
pc2[k2] += za2[k] * bnorm;
|
||||
pc1[k2] += za1[k] * bnorm;
|
||||
pc0[k2] += za0[k] * bnorm;
|
||||
|
||||
const double radint =
|
||||
pnl[k1] * zr[k] *
|
||||
(pc2[k1] / rategt +
|
||||
pc1[k1] * (1.0 + rk / rategt) / rate1 +
|
||||
pc0[k1] *
|
||||
(1.0 + 2.0 * rk / rate1 * (1.0 + rk / rategt)) /
|
||||
rate0);
|
||||
const int ll = std::abs(l1 - l2);
|
||||
const auto p = popj(l1, l2, ll);
|
||||
if (cfg.ipol == 0) {
|
||||
const int li = ll + 1;
|
||||
const int j1u = 2 * l1 + 1;
|
||||
const int j2u = 2 * l2 + 1;
|
||||
const int j1d = j1u - 2;
|
||||
const int j2d = j2u - 2;
|
||||
if (li <= 1) {
|
||||
polpos[k2] += bnorm * p[1] * polpos[k1];
|
||||
polneg[k2] += bnorm * p[2] * polneg[k1];
|
||||
} else if (l2 <= l1) {
|
||||
polpos[k2] +=
|
||||
bnorm *
|
||||
(p[1] * polpos[k1] * beta(l1, j1u, l2, j2u, ll) +
|
||||
p[2] * polneg[k1] * beta(l1, j1d, l2, j2u, ll));
|
||||
if (j1d != -1 && j2d != -1) {
|
||||
polneg[k2] +=
|
||||
bnorm * p[3] * polneg[k1] *
|
||||
beta(l1, j1d, l2, j2d, ll);
|
||||
}
|
||||
} else {
|
||||
polpos[k2] +=
|
||||
bnorm * p[1] * polpos[k1] *
|
||||
beta(l1, j1u, l2, j2u, ll);
|
||||
polneg[k2] +=
|
||||
bnorm *
|
||||
(p[2] * polpos[k1] * beta(l1, j1u, l2, j2d, ll) +
|
||||
p[3] * polneg[k1] * beta(l1, j1d, l2, j2d, ll));
|
||||
}
|
||||
}
|
||||
const int li = (ll != 0) ? ll : 2;
|
||||
detail::record_lines(cfg, n1, l1, n2, l2, li, radint);
|
||||
if (l1 == 1 && l2 == 0 && n2 == 1) {
|
||||
rlyman += radint;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<StateSummary> states;
|
||||
states.reserve(static_cast<std::size_t>(cfg.nmax * (cfg.nmax + 1) / 2));
|
||||
pc[1][1] = pc1[1] + 2.0 * pc2[1];
|
||||
for (int m1 = 1; m1 <= cfg.nmax; ++m1) {
|
||||
const int n1 = cfg.nmax + 1 - m1;
|
||||
for (int ll1 = 1; ll1 <= n1; ++ll1) {
|
||||
const int l1 = ll1 - 1;
|
||||
const int k1 = detail::state_index(n1, l1);
|
||||
std::optional<double> polar_down;
|
||||
if (l1 > 0) {
|
||||
polar_down =
|
||||
-polneg[k1] * (static_cast<double>(l1) + 0.5) /
|
||||
(static_cast<double>(l1) - 0.5);
|
||||
}
|
||||
std::optional<double> polar_up =
|
||||
(cfg.ipol == 1) ? std::optional<double>{} : std::optional<double>{polpos[k1]};
|
||||
std::optional<double> width_ev =
|
||||
(n1 == 1 || (l1 == 0 && pnl[k1] > 1.0e-20))
|
||||
? std::optional<double>{}
|
||||
: std::optional<double>{width[k1]};
|
||||
std::optional<double> rad_to_auger =
|
||||
(n1 == 1 || (l1 == 0 && pnl[k1] > 1.0e-20))
|
||||
? std::optional<double>{}
|
||||
: std::optional<double>{convc[k1]};
|
||||
std::optional<double> spin_orbit_ev =
|
||||
(cfg.ipol == 1 || n1 == 1 || (l1 == 0 && pnl[k1] > 1.0e-20))
|
||||
? std::optional<double>{}
|
||||
: std::optional<double>{sporb[k1]};
|
||||
states.push_back(StateSummary{
|
||||
n1,
|
||||
l1,
|
||||
pnl[k1],
|
||||
polar_up,
|
||||
polar_down,
|
||||
width_ev,
|
||||
rad_to_auger,
|
||||
spin_orbit_ev,
|
||||
pc[1][k1],
|
||||
pc[2][k1],
|
||||
pc[3][k1],
|
||||
});
|
||||
}
|
||||
}
|
||||
for (int i = 1; i <= 6; ++i) {
|
||||
cfg.pop[i] = pop1[i];
|
||||
}
|
||||
return {rlyman, std::move(states)};
|
||||
}
|
||||
|
||||
SimulationConfig config_;
|
||||
detail::NumericTables tables_;
|
||||
detail::PhysicsEngine primitives_;
|
||||
};
|
||||
|
||||
ModernCascadeKernel::ModernCascadeKernel(const SimulationConfig& config)
|
||||
: impl_(std::make_unique<Impl>(config)) {}
|
||||
|
||||
ModernCascadeKernel::~ModernCascadeKernel() = default;
|
||||
|
||||
ModernCascadeKernel::ModernCascadeKernel(ModernCascadeKernel&&) noexcept = default;
|
||||
|
||||
ModernCascadeKernel& ModernCascadeKernel::operator=(ModernCascadeKernel&&) noexcept = default;
|
||||
|
||||
SimulationResult ModernCascadeKernel::run() const {
|
||||
return impl_->run();
|
||||
}
|
||||
|
||||
SimulationResult run_modern_kernel(const SimulationConfig& config) {
|
||||
return ModernCascadeKernel(config).run();
|
||||
}
|
||||
|
||||
} // namespace mocca
|
||||
@@ -0,0 +1,440 @@
|
||||
#include "line_codec.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
|
||||
namespace mocca::detail {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kN1Shift = 0;
|
||||
constexpr int kL1Shift = 5;
|
||||
constexpr int kJ1Shift = 10;
|
||||
constexpr int kN2Shift = 16;
|
||||
constexpr int kL2Shift = 21;
|
||||
constexpr int kJ2Shift = 26;
|
||||
constexpr int kMultipoleShift = 32;
|
||||
|
||||
constexpr std::int64_t kFiveBitMask = 0x1FLL;
|
||||
constexpr std::int64_t kSixBitMask = 0x3FLL;
|
||||
constexpr std::int64_t kThreeBitMask = 0x7LL;
|
||||
|
||||
int lower_twice_j(int l) {
|
||||
return l == 0 ? 1 : 2 * l - 1;
|
||||
}
|
||||
|
||||
int upper_twice_j(int l) {
|
||||
return l == 0 ? 1 : 2 * l + 1;
|
||||
}
|
||||
|
||||
std::int64_t pack_line_id(
|
||||
int n1,
|
||||
int l1,
|
||||
int j1_twice,
|
||||
int n2,
|
||||
int l2,
|
||||
int j2_twice,
|
||||
int multipole) {
|
||||
const auto fits_mask = [](int value, std::int64_t mask) {
|
||||
return value >= 0 && static_cast<std::int64_t>(value) <= mask;
|
||||
};
|
||||
if (!fits_mask(n1, kFiveBitMask) || !fits_mask(l1, kFiveBitMask) ||
|
||||
!fits_mask(j1_twice, kSixBitMask) || !fits_mask(n2, kFiveBitMask) ||
|
||||
!fits_mask(l2, kFiveBitMask) || !fits_mask(j2_twice, kSixBitMask) ||
|
||||
!fits_mask(multipole, kThreeBitMask)) {
|
||||
throw std::runtime_error("Line descriptor exceeds the supported packed range");
|
||||
}
|
||||
return (static_cast<std::int64_t>(n1) << kN1Shift) |
|
||||
(static_cast<std::int64_t>(l1) << kL1Shift) |
|
||||
(static_cast<std::int64_t>(j1_twice) << kJ1Shift) |
|
||||
(static_cast<std::int64_t>(n2) << kN2Shift) |
|
||||
(static_cast<std::int64_t>(l2) << kL2Shift) |
|
||||
(static_cast<std::int64_t>(j2_twice) << kJ2Shift) |
|
||||
(static_cast<std::int64_t>(multipole) << kMultipoleShift);
|
||||
}
|
||||
|
||||
void store_line(
|
||||
KernelState& cfg,
|
||||
int index,
|
||||
double energy,
|
||||
int n1,
|
||||
int l1,
|
||||
int j1_twice,
|
||||
int n2,
|
||||
int l2,
|
||||
int j2_twice,
|
||||
int multipole,
|
||||
double intensity) {
|
||||
cfg.line_e[index] = energy;
|
||||
cfg.line_ia[index] = pack_line_id(n1, l1, j1_twice, n2, l2, j2_twice, multipole);
|
||||
cfg.line_ai[index] = intensity;
|
||||
}
|
||||
|
||||
std::string multipole_name(int multipole) {
|
||||
switch (multipole) {
|
||||
case 1:
|
||||
return "DIP";
|
||||
case 2:
|
||||
return "QUA";
|
||||
case 3:
|
||||
return "OCT";
|
||||
default:
|
||||
throw std::runtime_error("Invalid packed multipole id");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void record_lines(
|
||||
KernelState& cfg,
|
||||
int n1,
|
||||
int l1,
|
||||
int n2,
|
||||
int l2,
|
||||
int multipole,
|
||||
double intensity) {
|
||||
if (intensity <= cfg.climit) {
|
||||
return;
|
||||
}
|
||||
if (cfg.m > 995) {
|
||||
const std::string warning =
|
||||
"*** ATTENTION *** OVERFLOWING CAPABILITY OF SORTING *** PLEASE RESTRICT CRITERIA FOR LINES TO PASS ***";
|
||||
if (std::find(cfg.warnings.begin(), cfg.warnings.end(), warning) == cfg.warnings.end()) {
|
||||
cfg.warnings.push_back(warning);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
double e12 = 0.0;
|
||||
double e22 = 0.0;
|
||||
double e11 = cfg.energy[n1][2 * l1 + 1];
|
||||
if (l1 != 0) {
|
||||
e12 = cfg.energy[n1][2 * l1];
|
||||
}
|
||||
double e21 = cfg.energy[n2][2 * l2 + 1];
|
||||
if (l2 != 0) {
|
||||
e22 = cfg.energy[n2][2 * l2];
|
||||
}
|
||||
if (n1 != n2 && (e21 - e11 <= cfg.elow || e21 - e11 >= cfg.ehigh)) {
|
||||
return;
|
||||
}
|
||||
if (n1 == n2) {
|
||||
e11 = 0.0;
|
||||
const double ez = 0.5 * (cfg.energy[2][2] - cfg.energy[2][3]);
|
||||
e21 = cfg.esp * 1.0e-6 - ez;
|
||||
e22 = e21 + 2.0 * ez;
|
||||
}
|
||||
|
||||
if (multipole == 1) {
|
||||
const int ll = std::max(l1, l2);
|
||||
const double an = intensity / static_cast<double>(4 * ll * ll - 1);
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m,
|
||||
e21 - e11,
|
||||
n1,
|
||||
l1,
|
||||
upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
upper_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((ll + 1) * (2 * ll - 1)));
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 1,
|
||||
l1 == ll ? e21 - e12 : e22 - e11,
|
||||
n1,
|
||||
l1,
|
||||
l1 == ll ? lower_twice_j(l1) : upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
l1 == ll ? upper_twice_j(l2) : lower_twice_j(l2),
|
||||
multipole,
|
||||
an);
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 2,
|
||||
e22 - e12,
|
||||
n1,
|
||||
l1,
|
||||
lower_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
lower_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((ll - 1) * (2 * ll + 1)));
|
||||
cfg.m += 3;
|
||||
if (ll == 1) {
|
||||
cfg.m -= 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (multipole == 2) {
|
||||
if (l1 != l2) {
|
||||
const int ll = std::max(l1, l2);
|
||||
const double an = intensity / static_cast<double>((2 * ll - 3) * (2 * ll + 1));
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m,
|
||||
e21 - e11,
|
||||
n1,
|
||||
l1,
|
||||
upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
upper_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((ll + 1) * (2 * ll - 3)));
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 1,
|
||||
l1 == ll ? e21 - e12 : e22 - e11,
|
||||
n1,
|
||||
l1,
|
||||
l1 == ll ? lower_twice_j(l1) : upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
l1 == ll ? upper_twice_j(l2) : lower_twice_j(l2),
|
||||
multipole,
|
||||
2.0 * an);
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 2,
|
||||
e22 - e12,
|
||||
n1,
|
||||
l1,
|
||||
lower_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
lower_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((ll - 2) * (2 * ll + 1)));
|
||||
cfg.m += 3;
|
||||
if (ll <= 2) {
|
||||
cfg.m -= 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (l1 == 0) {
|
||||
return;
|
||||
}
|
||||
const double an = intensity / static_cast<double>((2 * l1 + 1) * (2 * l1 + 1));
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m,
|
||||
e21 - e11,
|
||||
n1,
|
||||
l1,
|
||||
upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
upper_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((l1 + 2) * (2 * l1 - 1)));
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 1,
|
||||
e22 - e11,
|
||||
n1,
|
||||
l1,
|
||||
upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
lower_twice_j(l2),
|
||||
multipole,
|
||||
3.0 * an);
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 2,
|
||||
e21 - e12,
|
||||
n1,
|
||||
l1,
|
||||
lower_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
upper_twice_j(l2),
|
||||
multipole,
|
||||
3.0 * an);
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 3,
|
||||
e22 - e12,
|
||||
n1,
|
||||
l1,
|
||||
lower_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
lower_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((l1 - 1) * (2 * l1 + 3)));
|
||||
cfg.m += 4;
|
||||
if (l1 == 1) {
|
||||
cfg.m -= 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::abs(l1 - l2) != 1) {
|
||||
const int ll = std::max(l1, l2);
|
||||
const double an = intensity / static_cast<double>((2 * ll - 5) * (2 * ll + 1));
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m,
|
||||
e21 - e11,
|
||||
n1,
|
||||
l1,
|
||||
upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
upper_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((2 * ll - 5) * (ll + 1)));
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 1,
|
||||
l1 == ll ? e21 - e12 : e22 - e11,
|
||||
n1,
|
||||
l1,
|
||||
l1 == ll ? lower_twice_j(l1) : upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
l1 == ll ? upper_twice_j(l2) : lower_twice_j(l2),
|
||||
multipole,
|
||||
3.0 * an);
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 2,
|
||||
e22 - e12,
|
||||
n1,
|
||||
l1,
|
||||
lower_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
lower_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((2 * ll + 1) * (ll - 3)));
|
||||
cfg.m += 3;
|
||||
if (ll <= 3) {
|
||||
cfg.m -= 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const int ll = std::max(l1, l2);
|
||||
const double an = intensity / static_cast<double>(4 * ll * ll - 1);
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m,
|
||||
e21 - e11,
|
||||
n1,
|
||||
l1,
|
||||
upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
upper_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((2 * ll - 3) * (ll + 2)));
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 1,
|
||||
e22 - e11,
|
||||
n1,
|
||||
l1,
|
||||
upper_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
lower_twice_j(l2),
|
||||
multipole,
|
||||
5.0 * an);
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 2,
|
||||
e21 - e12,
|
||||
n1,
|
||||
l1,
|
||||
lower_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
upper_twice_j(l2),
|
||||
multipole,
|
||||
6.0 * an);
|
||||
store_line(
|
||||
cfg,
|
||||
cfg.m + 3,
|
||||
e22 - e12,
|
||||
n1,
|
||||
l1,
|
||||
lower_twice_j(l1),
|
||||
n2,
|
||||
l2,
|
||||
lower_twice_j(l2),
|
||||
multipole,
|
||||
an * static_cast<double>((2 * ll + 3) * (ll - 2)));
|
||||
cfg.m += 4;
|
||||
if (ll <= 2) {
|
||||
cfg.m -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TransitionLine> collect_lines(const KernelState& cfg) {
|
||||
std::vector<TransitionLine> lines;
|
||||
lines.reserve(cfg.m > 1 ? static_cast<std::size_t>(cfg.m - 1) : 0);
|
||||
for (int i = 1; i < cfg.m; ++i) {
|
||||
if (!std::isfinite(cfg.line_ai[i]) || !std::isfinite(cfg.line_e[i])) {
|
||||
continue;
|
||||
}
|
||||
if (cfg.line_ai[i] <= cfg.climit) {
|
||||
continue;
|
||||
}
|
||||
const std::int64_t ia0 = cfg.line_ia[i];
|
||||
const int n1 = static_cast<int>((ia0 >> kN1Shift) & kFiveBitMask);
|
||||
const int l1 = static_cast<int>((ia0 >> kL1Shift) & kFiveBitMask);
|
||||
const int j1 = static_cast<int>((ia0 >> kJ1Shift) & kSixBitMask);
|
||||
const int n2 = static_cast<int>((ia0 >> kN2Shift) & kFiveBitMask);
|
||||
const int l2 = static_cast<int>((ia0 >> kL2Shift) & kFiveBitMask);
|
||||
const int j2 = static_cast<int>((ia0 >> kJ2Shift) & kSixBitMask);
|
||||
const int multipole_id = static_cast<int>((ia0 >> kMultipoleShift) & kThreeBitMask);
|
||||
lines.push_back(TransitionLine{
|
||||
n1,
|
||||
l1,
|
||||
j1,
|
||||
n2,
|
||||
l2,
|
||||
j2,
|
||||
multipole_name(multipole_id),
|
||||
1000.0 * cfg.line_e[i],
|
||||
cfg.line_ai[i],
|
||||
});
|
||||
}
|
||||
|
||||
std::sort(lines.begin(), lines.end(), [](const TransitionLine& lhs, const TransitionLine& rhs) {
|
||||
return std::tie(
|
||||
lhs.energy_kev,
|
||||
lhs.n1,
|
||||
lhs.l1,
|
||||
lhs.j1_twice,
|
||||
lhs.n2,
|
||||
lhs.l2,
|
||||
lhs.j2_twice,
|
||||
lhs.multipole,
|
||||
lhs.intensity) <
|
||||
std::tie(
|
||||
rhs.energy_kev,
|
||||
rhs.n1,
|
||||
rhs.l1,
|
||||
rhs.j1_twice,
|
||||
rhs.n2,
|
||||
rhs.l2,
|
||||
rhs.j2_twice,
|
||||
rhs.multipole,
|
||||
rhs.intensity);
|
||||
});
|
||||
return lines;
|
||||
}
|
||||
|
||||
} // namespace mocca::detail
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "mocca/api.hpp"
|
||||
#include "physics_engine.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace mocca::detail {
|
||||
|
||||
void record_lines(
|
||||
KernelState& cfg,
|
||||
int n1,
|
||||
int l1,
|
||||
int n2,
|
||||
int l2,
|
||||
int multipole,
|
||||
double intensity);
|
||||
|
||||
[[nodiscard]] std::vector<TransitionLine> collect_lines(const KernelState& cfg);
|
||||
|
||||
} // namespace mocca::detail
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,228 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "wide_float.hpp"
|
||||
|
||||
namespace mocca::detail {
|
||||
|
||||
using NumericTables = std::unordered_map<std::string, std::vector<double>>;
|
||||
|
||||
struct KernelState {
|
||||
explicit KernelState(const NumericTables& numeric_tables);
|
||||
|
||||
std::vector<std::string> warnings;
|
||||
|
||||
int ijk{0};
|
||||
double energ{0.0};
|
||||
double econs{5.505355e-03};
|
||||
double econst{0.0};
|
||||
double d2p1sm{0.0};
|
||||
double d2p1s{0.0};
|
||||
|
||||
std::vector<double> bem;
|
||||
std::vector<double> zsa;
|
||||
std::vector<double> be;
|
||||
|
||||
int k0{3};
|
||||
int k1{4};
|
||||
int k2{4};
|
||||
int k3{4};
|
||||
|
||||
std::vector<int> nn0;
|
||||
std::vector<int> nn1;
|
||||
std::vector<int> nn2;
|
||||
std::vector<int> nn3;
|
||||
|
||||
std::vector<double> r0;
|
||||
std::vector<double> r1;
|
||||
std::vector<double> r2;
|
||||
std::vector<double> r3;
|
||||
|
||||
std::vector<int> ip1;
|
||||
std::vector<int> ip2;
|
||||
std::vector<int> ip3;
|
||||
std::vector<int> iq1;
|
||||
std::vector<int> iq2;
|
||||
std::vector<int> iq3;
|
||||
|
||||
std::vector<double> f;
|
||||
double fd{15.0};
|
||||
|
||||
double pi{3.1415926535};
|
||||
double picoef{1.298778e17};
|
||||
double amassm{206.7686};
|
||||
std::vector<int> ne;
|
||||
std::vector<int> jk;
|
||||
double coeff{4.134139e16};
|
||||
double alfa{7.297353e-03};
|
||||
double amasse{511003.4};
|
||||
|
||||
double z{0.0};
|
||||
double zsk{0.0};
|
||||
double zsl{0.0};
|
||||
double zsm{0.0};
|
||||
double zskz{0.0};
|
||||
double zslz{0.0};
|
||||
double zsmz{0.0};
|
||||
|
||||
std::vector<double> amzz;
|
||||
std::vector<double> expmon;
|
||||
std::vector<double> expdip;
|
||||
std::vector<double> expqua;
|
||||
std::vector<double> expoct;
|
||||
std::vector<double> coemon;
|
||||
std::vector<double> coedip;
|
||||
std::vector<double> coequa;
|
||||
std::vector<double> coeoct;
|
||||
|
||||
std::vector<int> ifm;
|
||||
std::vector<int> ifd;
|
||||
std::vector<int> ifq;
|
||||
std::vector<int> ifo;
|
||||
|
||||
double angd{0.0};
|
||||
double angq{0.0};
|
||||
double ango{0.0};
|
||||
double aid{0.0};
|
||||
double aiq{0.0};
|
||||
double aio{0.0};
|
||||
double aidsq{0.0};
|
||||
double aiqsq{0.0};
|
||||
double aiosq{0.0};
|
||||
std::vector<double> coedp;
|
||||
std::vector<double> coeq;
|
||||
std::vector<double> coeo;
|
||||
std::vector<double> coed;
|
||||
|
||||
std::vector<int> ll;
|
||||
|
||||
std::vector<int> m1;
|
||||
std::vector<int> m2;
|
||||
std::vector<int> m3;
|
||||
std::vector<double> yc;
|
||||
int idb{0};
|
||||
|
||||
int irr{0};
|
||||
std::vector<double> rr;
|
||||
double rau{0.0};
|
||||
double rad{0.0};
|
||||
std::vector<double> ra;
|
||||
std::vector<double> rd;
|
||||
std::vector<double> rsa;
|
||||
|
||||
std::vector<double> pop;
|
||||
std::vector<int> jtm;
|
||||
std::vector<int> jtd;
|
||||
std::vector<int> jtq;
|
||||
std::vector<int> jto;
|
||||
|
||||
std::vector<int> jm;
|
||||
std::vector<int> jd;
|
||||
std::vector<int> jq;
|
||||
std::vector<int> jo;
|
||||
int iyc{0};
|
||||
std::vector<int> ij;
|
||||
std::vector<double> yj;
|
||||
std::vector<int> jj1;
|
||||
|
||||
double ehigh{20.0};
|
||||
double elow{0.040};
|
||||
double climit{1.0e-06};
|
||||
double eres{0.000300};
|
||||
double esp{0.0};
|
||||
double espm{0.0};
|
||||
|
||||
int m{1};
|
||||
std::vector<double> line_e;
|
||||
std::vector<double> line_ai;
|
||||
std::vector<std::int64_t> line_ia;
|
||||
std::vector<std::vector<double>> energy;
|
||||
|
||||
double dza{0.0};
|
||||
double dza2{0.0};
|
||||
double dredm{0.0};
|
||||
|
||||
int icc{0};
|
||||
std::vector<double> cd;
|
||||
double ea{99.0};
|
||||
double eb{99.0};
|
||||
int idr{0};
|
||||
|
||||
double zmk{2.0};
|
||||
double zml{4.0};
|
||||
double zmm{9.0};
|
||||
double zmkm{4.0};
|
||||
double zmlm{8.0};
|
||||
double zmmm{18.0};
|
||||
int ivers{0};
|
||||
|
||||
std::vector<double> pl;
|
||||
std::vector<int> npol;
|
||||
int ipol{0};
|
||||
double cl1{0.0};
|
||||
double cl2{0.0};
|
||||
int ide{10000};
|
||||
std::vector<double> pln;
|
||||
int ip8{0};
|
||||
|
||||
double a{140.0};
|
||||
double cfm{0.0};
|
||||
double tfm{2.3001};
|
||||
double step{0.0};
|
||||
double rmatch{0.0};
|
||||
double widthk{0.0};
|
||||
std::vector<int> ipc;
|
||||
|
||||
int nopt{0};
|
||||
int nmax{15};
|
||||
double alexp{0.0};
|
||||
|
||||
double amassa{0.0};
|
||||
double amassn{931.48};
|
||||
double hbar{6.582173e-16};
|
||||
};
|
||||
|
||||
[[nodiscard]] NumericTables load_numeric_tables(const std::filesystem::path& fortran_path);
|
||||
[[nodiscard]] int state_index(int n, int l);
|
||||
|
||||
class PhysicsEngine {
|
||||
public:
|
||||
explicit PhysicsEngine(const NumericTables& numeric_tables, int precision_digits = 120);
|
||||
|
||||
void prepare_case(KernelState& cfg) const;
|
||||
[[nodiscard]] double transition_rate(KernelState& cfg, int n1, int l1, int n2, int l2) const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] const std::vector<WideFloat>& factor_table(double fd) const;
|
||||
[[nodiscard]] double powi(double base, int exponent) const;
|
||||
[[nodiscard]] double continuum_extpy(double y) const;
|
||||
[[nodiscard]] double continuum_expiy(double y) const;
|
||||
[[nodiscard]] double rmon_coeff(const KernelState& cfg, double y) const;
|
||||
[[nodiscard]] double point(const KernelState& cfg, int n, int j) const;
|
||||
[[nodiscard]] double matel(KernelState& cfg, int n1, int l1, int n2, int l2, int l, double a, int n) const;
|
||||
[[nodiscard]] double matelu(KernelState& cfg, int n1, int l1, int n2, int l2, int l) const;
|
||||
[[nodiscard]] double rdipu(KernelState& cfg, int n1, int l1, int n2, int l2, int n, double enem, double y, int mm) const;
|
||||
[[nodiscard]] double rquau(KernelState& cfg, int n1, int l1, int n2, int l2, int n, double enem, double y, int m) const;
|
||||
[[nodiscard]] double roctu(KernelState& cfg, int n1, int l1, int n2, int l2, int n, double enem, double y, int m) const;
|
||||
[[nodiscard]] double rmon(KernelState& cfg, int n1, int l1, int n2, int l2, int n, double y) const;
|
||||
[[nodiscard]] double rdip(KernelState& cfg, int n1, int l1, int n2, int l2, int n, int m, double y) const;
|
||||
[[nodiscard]] double rqua(KernelState& cfg, int n1, int l1, int n2, int l2, int n, int m, double y) const;
|
||||
[[nodiscard]] double roct(KernelState& cfg, int n1, int l1, int n2, int l2, int n, int m, double y) const;
|
||||
|
||||
void warn(KernelState& cfg, const std::string& message) const;
|
||||
void apply_input_energy_defaults(KernelState& cfg) const;
|
||||
void ffix(KernelState& cfg) const;
|
||||
|
||||
const NumericTables& tables_;
|
||||
double default_amassm_{206.7686};
|
||||
int precision_digits_;
|
||||
unsigned long precision_bits_;
|
||||
};
|
||||
|
||||
} // namespace mocca::detail
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
namespace mocca::detail {
|
||||
|
||||
class WideFloat {
|
||||
public:
|
||||
WideFloat() = default;
|
||||
WideFloat(int value) : value_(static_cast<long double>(value)) {}
|
||||
WideFloat(long value) : value_(static_cast<long double>(value)) {}
|
||||
WideFloat(double value) : value_(static_cast<long double>(value)) {}
|
||||
WideFloat(long double value) : value_(value) {}
|
||||
WideFloat(int value, unsigned long) : value_(static_cast<long double>(value)) {}
|
||||
WideFloat(long value, unsigned long) : value_(static_cast<long double>(value)) {}
|
||||
WideFloat(double value, unsigned long) : value_(static_cast<long double>(value)) {}
|
||||
WideFloat(long double value, unsigned long) : value_(value) {}
|
||||
WideFloat(const std::string& value, unsigned long) : value_(std::stold(value)) {}
|
||||
|
||||
[[nodiscard]] double get_d() const {
|
||||
return static_cast<double>(value_);
|
||||
}
|
||||
|
||||
[[nodiscard]] long double raw() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
WideFloat& operator+=(const WideFloat& other) {
|
||||
value_ += other.value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
WideFloat& operator-=(const WideFloat& other) {
|
||||
value_ -= other.value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
WideFloat& operator*=(const WideFloat& other) {
|
||||
value_ *= other.value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
WideFloat& operator/=(const WideFloat& other) {
|
||||
value_ /= other.value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend WideFloat operator+(WideFloat lhs, const WideFloat& rhs) {
|
||||
lhs += rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend WideFloat operator-(WideFloat lhs, const WideFloat& rhs) {
|
||||
lhs -= rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend WideFloat operator*(WideFloat lhs, const WideFloat& rhs) {
|
||||
lhs *= rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend WideFloat operator/(WideFloat lhs, const WideFloat& rhs) {
|
||||
lhs /= rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend bool operator<(const WideFloat& lhs, const WideFloat& rhs) {
|
||||
return lhs.value_ < rhs.value_;
|
||||
}
|
||||
|
||||
private:
|
||||
long double value_{0.0L};
|
||||
};
|
||||
|
||||
inline WideFloat abs(const WideFloat& value) {
|
||||
return std::fabsl(value.raw());
|
||||
}
|
||||
|
||||
inline WideFloat sqrt(const WideFloat& value) {
|
||||
return std::sqrt(value.raw());
|
||||
}
|
||||
|
||||
} // namespace mocca::detail
|
||||
@@ -0,0 +1,455 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def one_based(values: list[float] | list[int], size: int, fill: float | int = 0) -> list[float] | list[int]:
|
||||
padded = list(values)
|
||||
if len(padded) < size:
|
||||
padded.extend([fill] * (size - len(padded)))
|
||||
return [fill] + padded
|
||||
|
||||
|
||||
def new_energy_table() -> list[list[float]]:
|
||||
return [[0.0 for _ in range(41)] for _ in range(21)]
|
||||
|
||||
|
||||
def state_index(n: int, l: int) -> int:
|
||||
return n * (n - 1) // 2 + l + 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeckConfig:
|
||||
amassa: float = 0.0
|
||||
amassn: float = 931.48
|
||||
d2p1s: float = 0.0
|
||||
be: list[float] = field(default_factory=lambda: one_based([0.0, 0.0, 0.0], 3, 0.0))
|
||||
k0: int = 3
|
||||
k1: int = 4
|
||||
k2: int = 4
|
||||
k3: int = 4
|
||||
nn0: list[int] = field(default_factory=lambda: one_based([1, 2, 3], 3, 0))
|
||||
nn1: list[int] = field(default_factory=lambda: one_based([0, 1, 2, 3, 3, 3, 3], 7, 0))
|
||||
nn2: list[int] = field(default_factory=lambda: one_based([0, 1, 2, 3, 3, 3, 3], 7, 0))
|
||||
nn3: list[int] = field(default_factory=lambda: one_based([0, 1, 2, 3, 3, 3, 3], 7, 0))
|
||||
ip1: list[int] = field(default_factory=lambda: one_based([0, 1, 1, 1, 1, 1, 1], 7, 0))
|
||||
ip2: list[int] = field(default_factory=lambda: one_based([0, 1, 1, 1, 1, 1, 1], 7, 0))
|
||||
ip3: list[int] = field(default_factory=lambda: one_based([0, 1, 1, 1, 1, 1, 1], 7, 0))
|
||||
iq1: list[int] = field(default_factory=lambda: one_based([0] * 7, 7, 0))
|
||||
iq2: list[int] = field(default_factory=lambda: one_based([0] * 7, 7, 0))
|
||||
iq3: list[int] = field(default_factory=lambda: one_based([0] * 7, 7, 0))
|
||||
fd: float = 15.0
|
||||
amassm: float = 206.7686
|
||||
amasse: float = 511003.4
|
||||
z: float = 0.0
|
||||
zsk: float = 0.0
|
||||
zsl: float = 0.0
|
||||
zsm: float = 0.0
|
||||
m1: list[int] = field(default_factory=lambda: one_based([0, 0, 0, 0, 1, 2, 3], 7, 0))
|
||||
m2: list[int] = field(default_factory=lambda: one_based([0, 0, 0, 0, 1, 2, 3], 7, 0))
|
||||
m3: list[int] = field(default_factory=lambda: one_based([0, 0, 0, 0, 1, 2, 3], 7, 0))
|
||||
pop: list[float] = field(default_factory=lambda: one_based([1.0] * 6, 6, 0.0))
|
||||
ehigh: float = 20.0
|
||||
elow: float = 0.040
|
||||
climit: float = 1.0e-06
|
||||
eres: float = 0.000300
|
||||
ea: float = 99.0
|
||||
eb: float = 99.0
|
||||
a: float = 140.0
|
||||
cfm: float = 0.0
|
||||
tfm: float = 2.3001
|
||||
step: float = 0.0
|
||||
rmatch: float = 0.0
|
||||
widthk: float = 0.0
|
||||
nopt: int = 0
|
||||
nmax: int = 15
|
||||
alexp: float = 0.0
|
||||
energy: list[list[float]] = field(default_factory=new_energy_table)
|
||||
npol: list[int] = field(default_factory=lambda: one_based([-1] * 20, 20, 0))
|
||||
ipol: int = 0
|
||||
idb: int = 0
|
||||
yc: list[float] = field(default_factory=lambda: one_based([1.0, 1.0, 1.0, 1.0], 4, 0.0))
|
||||
ipc: list[int] = field(default_factory=lambda: one_based([0, 0, 0], 3, 0))
|
||||
cl1: float = 0.0
|
||||
cl2: float = 0.0
|
||||
esp: float = 0.0
|
||||
ide: int = 10000
|
||||
jtm: list[int] = field(default_factory=lambda: one_based([1] * 6, 6, 0))
|
||||
jtd: list[int] = field(default_factory=lambda: one_based([1] * 6, 6, 0))
|
||||
jtq: list[int] = field(default_factory=lambda: one_based([1] * 6, 6, 0))
|
||||
jto: list[int] = field(default_factory=lambda: one_based([1] * 6, 6, 0))
|
||||
pl: list[float] = field(default_factory=lambda: one_based([0.0] * 20, 20, 0.0))
|
||||
pln: list[float] = field(default_factory=lambda: one_based([0.0] * 210, 210, 0.0))
|
||||
ip8: int = 0
|
||||
warnings: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
NUMBER_PATTERN = re.compile(r"[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[EeDd][+-]?\d+)?")
|
||||
|
||||
|
||||
def parse_numbers(payload: str) -> list[float]:
|
||||
payload = payload.split(";", 1)[0]
|
||||
values = [token.replace("D", "E").replace("d", "E") for token in NUMBER_PATTERN.findall(payload)]
|
||||
return [float(value) for value in values]
|
||||
|
||||
|
||||
def parse_ints(payload: str) -> list[int]:
|
||||
return [int(round(value)) for value in parse_numbers(payload)]
|
||||
|
||||
|
||||
def apply_card(cfg: DeckConfig, code: str, payload: str) -> str | None:
|
||||
code = code.strip().upper()
|
||||
if not code:
|
||||
return None
|
||||
if code == "STO":
|
||||
return "stop"
|
||||
if code == "XEQ":
|
||||
return "execute"
|
||||
|
||||
if code == "AMA":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.amassa = values[0]
|
||||
elif code == "AMN":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.amassn = values[0]
|
||||
elif code == "D21":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.d2p1s = values[0]
|
||||
elif code == "BE":
|
||||
for index, value in enumerate(parse_numbers(payload)[:3], start=1):
|
||||
cfg.be[index] = value
|
||||
elif code == "K":
|
||||
values = parse_ints(payload)
|
||||
if len(values) >= 4:
|
||||
cfg.k0, cfg.k1, cfg.k2, cfg.k3 = values[:4]
|
||||
elif code == "NN0":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k0], start=1):
|
||||
cfg.nn0[index] = value
|
||||
elif code == "NN1":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k1], start=1):
|
||||
cfg.nn1[index] = value
|
||||
elif code == "NN2":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k2], start=1):
|
||||
cfg.nn2[index] = value
|
||||
elif code == "NN3":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k3], start=1):
|
||||
cfg.nn3[index] = value
|
||||
elif code == "IP1":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k1], start=1):
|
||||
cfg.ip1[index] = value
|
||||
elif code == "IP2":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k2], start=1):
|
||||
cfg.ip2[index] = value
|
||||
elif code == "IP3":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k3], start=1):
|
||||
cfg.ip3[index] = value
|
||||
elif code == "FD":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.fd = values[0]
|
||||
elif code == "AMM":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.amassm = values[0]
|
||||
elif code == "AME":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.amasse = values[0]
|
||||
elif code == "Z":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.z = values[0]
|
||||
elif code == "ZS":
|
||||
values = parse_numbers(payload)
|
||||
if len(values) >= 3:
|
||||
cfg.zsk, cfg.zsl, cfg.zsm = values[:3]
|
||||
elif code == "M1":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k1], start=1):
|
||||
cfg.m1[index] = value
|
||||
elif code == "M2":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k2], start=1):
|
||||
cfg.m2[index] = value
|
||||
elif code == "M3":
|
||||
for index, value in enumerate(parse_ints(payload)[: cfg.k3], start=1):
|
||||
cfg.m3[index] = value
|
||||
elif code == "POP":
|
||||
for index, value in enumerate(parse_numbers(payload)[:6], start=1):
|
||||
cfg.pop[index] = min(max(value, 0.0), 1.0)
|
||||
elif code == "EHI":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.ehigh = values[0]
|
||||
elif code == "ELO":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.elow = values[0]
|
||||
elif code == "CLM":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.climit = values[0]
|
||||
elif code == "ERS":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.eres = values[0]
|
||||
elif code == "EAB":
|
||||
values = parse_numbers(payload)
|
||||
if len(values) >= 2:
|
||||
cfg.ea, cfg.eb = values[:2]
|
||||
elif code == "A":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.a = values[0]
|
||||
elif code == "CT":
|
||||
values = parse_numbers(payload)
|
||||
if len(values) >= 2:
|
||||
cfg.cfm, cfg.tfm = values[:2]
|
||||
elif code == "STP":
|
||||
values = parse_numbers(payload)
|
||||
if len(values) >= 2:
|
||||
cfg.step, cfg.rmatch = values[:2]
|
||||
elif code == "KWD":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.widthk = values[0]
|
||||
elif code == "NOP":
|
||||
values = parse_ints(payload)
|
||||
if values:
|
||||
cfg.nopt = values[0]
|
||||
elif code == "NMX":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.nmax = int(round(values[0]))
|
||||
if len(values) >= 2 and cfg.nopt < 1:
|
||||
cfg.alexp = values[1]
|
||||
elif code == "PL":
|
||||
values = parse_numbers(payload)
|
||||
if len(values) >= 2:
|
||||
level = int(round(values[0]))
|
||||
if 0 <= level <= 19:
|
||||
cfg.pl[level + 1] = values[1]
|
||||
elif code == "DIR":
|
||||
values = parse_numbers(payload)
|
||||
if len(values) >= 4:
|
||||
nstate = int(round(values[0]))
|
||||
kappa = int(round(values[1]))
|
||||
vacuum = values[2]
|
||||
binding = values[3]
|
||||
if vacuum < 0.0:
|
||||
binding = binding + vacuum
|
||||
cfg.energy[nstate][kappa] = binding + vacuum
|
||||
elif code == "NPL":
|
||||
for index, value in enumerate(parse_ints(payload)[:20], start=1):
|
||||
cfg.npol[index] = value
|
||||
elif code == "IPL":
|
||||
values = parse_ints(payload)
|
||||
if values:
|
||||
cfg.ipol = values[0]
|
||||
elif code == "IDB":
|
||||
values = parse_ints(payload)
|
||||
if values:
|
||||
cfg.idb = values[0]
|
||||
elif code == "YC":
|
||||
for index, value in enumerate(parse_numbers(payload)[:4], start=1):
|
||||
cfg.yc[index] = value
|
||||
elif code == "IPC":
|
||||
for index, value in enumerate(parse_ints(payload)[:3], start=1):
|
||||
cfg.ipc[index] = value
|
||||
elif code == "CL":
|
||||
values = parse_numbers(payload)
|
||||
if len(values) >= 2:
|
||||
cfg.cl1, cfg.cl2 = values[:2]
|
||||
elif code == "ESP":
|
||||
values = parse_numbers(payload)
|
||||
if values:
|
||||
cfg.esp = values[0]
|
||||
elif code == "IDE":
|
||||
values = parse_ints(payload)
|
||||
if values:
|
||||
cfg.ide = values[0]
|
||||
elif code == "JTM":
|
||||
for index, value in enumerate(parse_ints(payload)[:6], start=1):
|
||||
cfg.jtm[index] = value
|
||||
elif code == "JTD":
|
||||
for index, value in enumerate(parse_ints(payload)[:6], start=1):
|
||||
cfg.jtd[index] = value
|
||||
elif code == "JTQ":
|
||||
for index, value in enumerate(parse_ints(payload)[:6], start=1):
|
||||
cfg.jtq[index] = value
|
||||
elif code == "JTO":
|
||||
for index, value in enumerate(parse_ints(payload)[:6], start=1):
|
||||
cfg.jto[index] = value
|
||||
elif code == "PLN":
|
||||
values = parse_numbers(payload)
|
||||
if len(values) >= 4:
|
||||
n8 = int(round(values[0]))
|
||||
ld8 = int(round(values[1]))
|
||||
lu8 = int(round(values[2]))
|
||||
a8 = values[3]
|
||||
entries = values[4:]
|
||||
count = lu8 - ld8 + 1
|
||||
total = 0.0
|
||||
for offset in range(count):
|
||||
if offset >= len(entries):
|
||||
break
|
||||
index = state_index(n8, ld8 + offset)
|
||||
cfg.pln[index] = entries[offset]
|
||||
total += entries[offset]
|
||||
scale = a8 if a8 > 0.0 else total
|
||||
normalizer = max(total, 1.0e-20)
|
||||
for offset in range(count):
|
||||
if offset >= len(entries):
|
||||
break
|
||||
index = state_index(n8, ld8 + offset)
|
||||
cfg.pln[index] = cfg.pln[index] * scale / normalizer
|
||||
elif code == "IP":
|
||||
values = parse_ints(payload)
|
||||
if values:
|
||||
cfg.ip8 = values[0]
|
||||
else:
|
||||
cfg.warnings.append(f"Ignored unsupported card {code}")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def parse_first_case(deck_text: str) -> DeckConfig:
|
||||
cfg = DeckConfig()
|
||||
for raw_line in deck_text.splitlines():
|
||||
if raw_line.strip() == "":
|
||||
continue
|
||||
code = raw_line[:3]
|
||||
payload = raw_line[10:] if len(raw_line) > 10 else ""
|
||||
action = apply_card(cfg, code, payload)
|
||||
if action == "execute":
|
||||
return cfg
|
||||
if action == "stop":
|
||||
break
|
||||
raise SystemExit("No XEQ card found in the input deck.")
|
||||
|
||||
|
||||
def capture_config(cfg: DeckConfig) -> dict[str, object]:
|
||||
if cfg.ip8 != 0:
|
||||
weights: list[dict[str, object]] = []
|
||||
for n in range(1, cfg.nmax + 1):
|
||||
for l in range(n):
|
||||
weight = cfg.pln[state_index(n, l)]
|
||||
if abs(weight) > 0.0:
|
||||
weights.append({"n": n, "l": l, "weight": weight})
|
||||
return {"mode": "explicit_nl", "n_max": cfg.nmax, "nl_weights": weights}
|
||||
if cfg.nopt == 0:
|
||||
return {"mode": "statistical_l", "n_max": cfg.nmax, "alpha": cfg.alexp}
|
||||
if cfg.nopt == 2:
|
||||
return {
|
||||
"mode": "quadratic_l",
|
||||
"n_max": cfg.nmax,
|
||||
"quadratic_coefficients": [cfg.cl1, cfg.cl2],
|
||||
}
|
||||
if cfg.nopt == 4:
|
||||
return {"mode": "legacy_empty", "n_max": cfg.nmax}
|
||||
return {
|
||||
"mode": "explicit_l",
|
||||
"n_max": cfg.nmax,
|
||||
"l_weights": [cfg.pl[index] for index in range(1, cfg.nmax + 1)],
|
||||
}
|
||||
|
||||
|
||||
def dirac_energies(cfg: DeckConfig) -> list[dict[str, object]]:
|
||||
entries: list[dict[str, object]] = []
|
||||
for n in range(1, min(cfg.nmax, len(cfg.energy) - 1) + 1):
|
||||
for kappa in range(1, len(cfg.energy[n])):
|
||||
value = cfg.energy[n][kappa]
|
||||
if value > 0.0:
|
||||
entries.append(
|
||||
{
|
||||
"n": n,
|
||||
"kappa": kappa,
|
||||
"vacuum_polarization_kev": 0.0,
|
||||
"binding_kev": value,
|
||||
}
|
||||
)
|
||||
return entries
|
||||
|
||||
|
||||
def convert(deck_path: Path, case_name: str | None) -> dict[str, object]:
|
||||
cfg = parse_first_case(deck_path.read_text())
|
||||
payload: dict[str, object] = {
|
||||
"schema_version": 1,
|
||||
"metadata": {"case_name": case_name or deck_path.stem},
|
||||
"atom": {
|
||||
"atomic_number": cfg.z,
|
||||
"effective_shell_charges": [cfg.zsk, cfg.zsl, cfg.zsm],
|
||||
"binding_energies_ev": [cfg.be[1], cfg.be[2], cfg.be[3]],
|
||||
"atomic_mass": cfg.a,
|
||||
"exact_mass_number": cfg.amassa if cfg.amassa > 0.0 else None,
|
||||
},
|
||||
"masses": {
|
||||
"muon_electron_masses": cfg.amassm,
|
||||
"electron_mass_ev": cfg.amasse,
|
||||
"nucleon_mass_mev": cfg.amassn,
|
||||
},
|
||||
"transitions": {
|
||||
"two_p_to_one_s_energy_ev": cfg.d2p1s if cfg.d2p1s > 0.0 else None,
|
||||
"two_s_to_two_p_split_ev": cfg.esp if cfg.esp > 0.0 else None,
|
||||
"dirac_energies": dirac_energies(cfg),
|
||||
},
|
||||
"capture": capture_config(cfg),
|
||||
"channels": {
|
||||
"case_counts": [cfg.k0, cfg.k1, cfg.k2, cfg.k3],
|
||||
"monopole_shells": [cfg.nn0[index] for index in range(1, cfg.k0 + 1)],
|
||||
"dipole_shells": [cfg.nn1[index] for index in range(1, cfg.k1 + 1)],
|
||||
"quadrupole_shells": [cfg.nn2[index] for index in range(1, cfg.k2 + 1)],
|
||||
"octupole_shells": [cfg.nn3[index] for index in range(1, cfg.k3 + 1)],
|
||||
"dipole_subshell_channels": [cfg.m1[index] for index in range(1, cfg.k1 + 1)],
|
||||
"quadrupole_subshell_channels": [cfg.m2[index] for index in range(1, cfg.k2 + 1)],
|
||||
"octupole_subshell_channels": [cfg.m3[index] for index in range(1, cfg.k3 + 1)],
|
||||
"dipole_penetration_codes": [cfg.ip1[index] for index in range(1, cfg.k1 + 1)],
|
||||
"quadrupole_penetration_codes": [cfg.ip2[index] for index in range(1, cfg.k2 + 1)],
|
||||
"octupole_penetration_codes": [cfg.ip3[index] for index in range(1, cfg.k3 + 1)],
|
||||
"dipole_penetration_avg_n_cutoffs": [cfg.iq1[index] for index in range(1, cfg.k1 + 1)],
|
||||
"quadrupole_penetration_avg_n_cutoffs": [cfg.iq2[index] for index in range(1, cfg.k2 + 1)],
|
||||
"octupole_penetration_avg_n_cutoffs": [cfg.iq3[index] for index in range(1, cfg.k3 + 1)],
|
||||
},
|
||||
"shell_model": {
|
||||
"subshell_populations": [cfg.pop[index] for index in range(1, 7)],
|
||||
"refill_codes": [cfg.ipc[index] for index in range(1, 4)],
|
||||
"penetration_cutoffs": [cfg.yc[index] for index in range(1, 5)],
|
||||
"width_k_ev": cfg.widthk,
|
||||
"track_polarization": cfg.ipol == 0,
|
||||
},
|
||||
"reporting": {
|
||||
"line_energy_min_mev": cfg.elow,
|
||||
"line_energy_max_mev": cfg.ehigh,
|
||||
"line_intensity_threshold": cfg.climit,
|
||||
"energy_resolution_mev": cfg.eres,
|
||||
},
|
||||
"model": {"factorial_divider": cfg.fd},
|
||||
"numerics": {"matrix_element_precision_digits": 120},
|
||||
}
|
||||
if cfg.warnings:
|
||||
payload["translator_warnings"] = cfg.warnings
|
||||
return payload
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Translate a legacy cascade card deck into the MOCCA JSON input format.")
|
||||
parser.add_argument("deck", type=Path, help="Legacy input deck")
|
||||
parser.add_argument("--output", type=Path, help="Output JSON path. Defaults to stdout.")
|
||||
parser.add_argument("--case-name", help="Optional case name override")
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = json.dumps(convert(args.deck, args.case_name), indent=2) + "\n"
|
||||
if args.output is None:
|
||||
print(payload, end="")
|
||||
else:
|
||||
args.output.write_text(payload)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user