beta-parameterization 3.0.0__tar.gz → 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/CHANGELOG.md +90 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/CMakeLists.txt +23 -12
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/PKG-INFO +1 -1
- beta_parameterization-4.0.0/README.md +191 -0
- beta_parameterization-4.0.0/include/beta_parameterization.h +243 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/include/beta_parameterization.hpp +102 -164
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/python/beta_parameterization/__init__.py +2 -2
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/python/beta_parameterization/_cdefs.py +14 -26
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/python/beta_parameterization/api.py +117 -47
- beta_parameterization-4.0.0/python/tests/test_api.py +318 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/src/beta_parameterization_c_api_mod.f08 +197 -165
- beta_parameterization-4.0.0/src/beta_parameterization_mod.f08 +996 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/src/beta_parameterization_workers_mod.f08 +7 -1
- beta_parameterization-4.0.0/tests/beta_param_boundary_test.f08 +256 -0
- beta_parameterization-4.0.0/tests/beta_param_equivalence_test.f08 +385 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/tests/beta_param_golden_test.f08 +5 -5
- beta_parameterization-4.0.0/tests/beta_param_lifecycle_test.f08 +115 -0
- beta_parameterization-4.0.0/tests/beta_param_outputs_test.f08 +187 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/tests/beta_param_property_test.f08 +5 -5
- beta_parameterization-4.0.0/tests/beta_param_resolve_test.f08 +81 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/tests/beta_param_standalone_test.f08 +39 -30
- beta_parameterization-4.0.0/tests/beta_param_statelessness_test.f08 +304 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/tests/beta_param_status_test.f08 +13 -1
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/tests/beta_pes_sweep.f08 +4 -4
- beta_parameterization-4.0.0/tests/c_api_smoke_test.cpp +309 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/tests/golden_capture.f08 +4 -5
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/tests/test_utils_mod.f08 +34 -1
- beta_parameterization-4.0.0/tests/thread_stress_test.cpp +242 -0
- beta_parameterization-3.0.0/include/beta_parameterization.h +0 -263
- beta_parameterization-3.0.0/python/tests/test_api.py +0 -153
- beta_parameterization-3.0.0/src/beta_parameterization_mod.f08 +0 -1419
- beta_parameterization-3.0.0/tests/beta_param_bitwise_test.f08 +0 -252
- beta_parameterization-3.0.0/tests/beta_param_boundary_test.f08 +0 -148
- beta_parameterization-3.0.0/tests/beta_param_interleave_test.f08 +0 -378
- beta_parameterization-3.0.0/tests/beta_param_lifecycle_test.f08 +0 -124
- beta_parameterization-3.0.0/tests/beta_param_minimality_test.f08 +0 -260
- beta_parameterization-3.0.0/tests/beta_param_outputs_test.f08 +0 -132
- beta_parameterization-3.0.0/tests/beta_param_resolve_test.f08 +0 -53
- beta_parameterization-3.0.0/tests/c_api_smoke_test.cpp +0 -213
- beta_parameterization-3.0.0/tests/thread_stress_test.cpp +0 -168
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/.gitignore +0 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/LICENSE +0 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/ci/build-wheel.sh +0 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/pyproject.toml +0 -0
- {beta_parameterization-3.0.0 → beta_parameterization-4.0.0}/python/beta_parameterization/_libloader.py +0 -0
|
@@ -3,6 +3,96 @@
|
|
|
3
3
|
All notable changes to this project are documented here. Versions follow
|
|
4
4
|
semantic versioning; the format follows [Keep a Changelog](https://keepachangelog.com).
|
|
5
5
|
|
|
6
|
+
## 4.0.0
|
|
7
|
+
|
|
8
|
+
Adoption of the two-tier shape parameterization contract on
|
|
9
|
+
`shape_core_mod` (fortran-foundations 3.0.0). The incremental tier is gone:
|
|
10
|
+
one-shot functions plus one read-only cache, built once and shared across
|
|
11
|
+
threads. Every public surface — Fortran, C, C++, Python — changes; nothing in
|
|
12
|
+
3.x compiles against 4.0.0 unchanged.
|
|
13
|
+
|
|
14
|
+
### Changed (breaking)
|
|
15
|
+
|
|
16
|
+
- **One cache type.** `tables_t` is renamed `cache_t`; the mutable 3.x
|
|
17
|
+
`cache_t`, `cache_init_shared_s`, the shared-tables mode and the `target`
|
|
18
|
+
requirement are removed. `cache_init_s(cache, max_params, thetas, status)`.
|
|
19
|
+
C: `beta_param_cache_create(max_params, thetas, n_thetas, status)`; the
|
|
20
|
+
`tables` handle and `beta_param_cache_create_shared` are removed. C++:
|
|
21
|
+
`Tables` is removed, `Cache(max_params, thetas)`. Python:
|
|
22
|
+
`Cache(max_params, thetas)`.
|
|
23
|
+
- **Caches are immutable and shareable.** Every compute takes the cache
|
|
24
|
+
read-only (`intent(in)`, `const` handle, `const` method) and is `pure` in
|
|
25
|
+
Fortran. Any number of threads may compute on one cache. The 3.0.0
|
|
26
|
+
thread-confinement rule is withdrawn.
|
|
27
|
+
- **Options are per call.** `conserve_volume` and `apply_com` move from cache
|
|
28
|
+
creation to each compute, after `params` and before the outputs. One cache
|
|
29
|
+
serves every combination. `cache_radius_grid_unchecked_s` takes `apply_com`
|
|
30
|
+
only: it never volume-scales.
|
|
31
|
+
- **`max_params` replaces `n_params`.** A cache accepts any vector of
|
|
32
|
+
`1 .. max_params` entries, up to 64 (the 8-parameter cap is gone). Missing
|
|
33
|
+
trailing parameters are zero.
|
|
34
|
+
- **Node sets are built from a cache**:
|
|
35
|
+
`node_set_build_s(node_set, cache, thetas, status)`; argument order of the
|
|
36
|
+
node compute is `(cache, params, node_set, conserve_volume, apply_com, radii,
|
|
37
|
+
dr_dthetas, status)`. A node set serves a cache when it was built from a
|
|
38
|
+
cache with at least that cache's `max_params`
|
|
39
|
+
(`BETA_PARAM_ERROR_NODE_SET_MISMATCH` otherwise) — judged on the two
|
|
40
|
+
objects, no longer on the vector length.
|
|
41
|
+
- **Status codes.** Code 6 (`SHAPE_ERROR_TABLES_NOT_INITIALIZED`) is retired
|
|
42
|
+
and never reused; an uninitialized cache passed to `node_set_build_s` now
|
|
43
|
+
returns 2. An empty one-shot vector returns 4 (was 5). `max_params > 64` at
|
|
44
|
+
init returns 1. C: a negative `n_thetas` returns 3 (was 5).
|
|
45
|
+
- **Getters.** `cache_n_params_f` becomes `cache_max_params_f`; C++
|
|
46
|
+
`Cache::n_params()` becomes `max_params()`; Python `Cache.n_params` becomes
|
|
47
|
+
`max_params`, and the `conserve_volume` / `apply_com` attributes are gone.
|
|
48
|
+
|
|
49
|
+
### Removed
|
|
50
|
+
|
|
51
|
+
- The recompute engine and everything built on it: `cache_recompute_count_f`,
|
|
52
|
+
the `BETA_PARAM_I_*` indices, the dependency map, the minimality test suite.
|
|
53
|
+
- `SHAPE_CACHE_MAX_PARAMS`, `SHAPE_STANDALONE_MAX_PARAMS`
|
|
54
|
+
(use `SHAPE_MAX_PARAMS`), `BETA_PARAM_CACHE_MAX_PARAMS`, Python
|
|
55
|
+
`CACHE_MAX_PARAMS`, `Status.tables_not_initialized`.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- **Trailing zeros are trimmed.** Both tiers reduce a vector to its last
|
|
60
|
+
nonzero entry before any arithmetic, so a short vector and its zero-padded
|
|
61
|
+
form return identical bits. Interior zeros are kept.
|
|
62
|
+
- **README.md** with the shape definition, both tiers in Fortran, C and
|
|
63
|
+
Python, build instructions and version pins.
|
|
64
|
+
- **CI on push and pull request** (`.github/workflows/tests.yml`): every suite
|
|
65
|
+
in Debug and Release, pytest against the built library, and the
|
|
66
|
+
manylinux2014 wheel build.
|
|
67
|
+
- Test suites for the contract's families: `equivalence` (one-shot ≡ cached,
|
|
68
|
+
short ≡ zero-padded), `statelessness`, `boundary`, and a concurrency suite
|
|
69
|
+
running eight threads on one shared cache.
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- **COM correction on a non-positive volume integral.** With `apply_com`, a
|
|
74
|
+
shape far outside the valid domain (for example `[0, -20]`) made the Newton
|
|
75
|
+
step take the cube root of a negative number: a floating-point trap in Debug
|
|
76
|
+
builds, and in Release a result that depended on a NaN comparison. The
|
|
77
|
+
iteration now reports non-convergence (103). Without `apply_com` such a
|
|
78
|
+
shape is still rejected by the validity gate (100).
|
|
79
|
+
- **C API stack overflow on a large wrong size.** Marshalling buffers were
|
|
80
|
+
caller-sized automatic arrays; under Release a large wrong size argument
|
|
81
|
+
overflowed the stack before the size was checked. They are heap-allocated
|
|
82
|
+
now: the call returns `BETA_PARAM_ERROR_INVALID_BUFFER_SIZE`.
|
|
83
|
+
- **Allocation failure in cache or node-set build** returns
|
|
84
|
+
`SHAPE_ERROR_INVALID_GRID` (3) instead of terminating the process.
|
|
85
|
+
|
|
86
|
+
### Build
|
|
87
|
+
|
|
88
|
+
- The library's own objects are compiled with `-fno-lto`. Under
|
|
89
|
+
`-flto -ffast-math` the summation kernels were inlined into each caller and
|
|
90
|
+
optimized per call site, so the same call could return different last bits
|
|
91
|
+
from different call sites. Without LTO every caller executes one machine-code
|
|
92
|
+
body per kernel, which is what makes the bitwise guarantees hold. Cost: about
|
|
93
|
+
30% on shape evaluation time. Consumers keep LTO for their own code.
|
|
94
|
+
- fortran-foundations pin 2.4.0 → 3.0.0.
|
|
95
|
+
|
|
6
96
|
## 3.0.0
|
|
7
97
|
|
|
8
98
|
Adoption of the three-tier shape parameterization contract against
|
|
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.20)
|
|
|
3
3
|
include(FetchContent)
|
|
4
4
|
|
|
5
5
|
project(beta-parameterization
|
|
6
|
-
VERSION
|
|
6
|
+
VERSION 4.0.0
|
|
7
7
|
LANGUAGES Fortran CXX
|
|
8
8
|
DESCRIPTION "Beta parameterization for nuclear physics applications"
|
|
9
9
|
)
|
|
@@ -14,19 +14,19 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
|
|
14
14
|
|
|
15
15
|
# --- Fetch GCC compiler options package ---
|
|
16
16
|
FetchContent_Declare(
|
|
17
|
-
|
|
18
|
-
GIT_REPOSITORY https://github.com/AleksanderAugustyn/
|
|
17
|
+
gcc-compiler-options
|
|
18
|
+
GIT_REPOSITORY https://github.com/AleksanderAugustyn/gcc-compiler-options.git
|
|
19
19
|
GIT_TAG 2.0.0
|
|
20
20
|
)
|
|
21
|
-
FetchContent_MakeAvailable(
|
|
21
|
+
FetchContent_MakeAvailable(gcc-compiler-options)
|
|
22
22
|
|
|
23
23
|
# --- Fetch foundational Fortran modules ---
|
|
24
24
|
FetchContent_Declare(
|
|
25
|
-
|
|
26
|
-
GIT_REPOSITORY https://github.com/AleksanderAugustyn/
|
|
27
|
-
GIT_TAG
|
|
25
|
+
fortran-foundations
|
|
26
|
+
GIT_REPOSITORY https://github.com/AleksanderAugustyn/fortran-foundations.git
|
|
27
|
+
GIT_TAG 3.0.0
|
|
28
28
|
)
|
|
29
|
-
FetchContent_MakeAvailable(
|
|
29
|
+
FetchContent_MakeAvailable(fortran-foundations)
|
|
30
30
|
|
|
31
31
|
# --- Preprocessor & build type ---
|
|
32
32
|
set(CMAKE_Fortran_PREPROCESS ON)
|
|
@@ -35,7 +35,7 @@ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|
|
35
35
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "RelWithDebInfo" "Release")
|
|
36
36
|
endif ()
|
|
37
37
|
|
|
38
|
-
# --- Compiler flags via
|
|
38
|
+
# --- Compiler flags via gcc-compiler-options ---
|
|
39
39
|
include(GCCCompilerOptions/FortranCompilerOptions)
|
|
40
40
|
create_fortran_library_interface(TARGET beta_parameterization_flags)
|
|
41
41
|
|
|
@@ -46,6 +46,16 @@ set(BETA_PARAM_SOURCES
|
|
|
46
46
|
src/beta_parameterization_c_api_mod.f08
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
+
# --- No LTO for the library's own objects ---
|
|
50
|
+
# Release builds use -flto=auto -ffast-math. With LTO the summation kernels are
|
|
51
|
+
# inlined into every caller and optimized per call site, so the same call can
|
|
52
|
+
# return different last bits from different call sites — which breaks the
|
|
53
|
+
# contract's bitwise guarantees (one-shot == cached, statelessness,
|
|
54
|
+
# concurrency). Compiled without LTO, each kernel is ONE machine-code body that
|
|
55
|
+
# every caller executes. Consumers keep LTO for their own code.
|
|
56
|
+
# Source-file property so it lands after the flags-interface options (last wins).
|
|
57
|
+
set_source_files_properties(${BETA_PARAM_SOURCES} PROPERTIES COMPILE_OPTIONS "-fno-lto")
|
|
58
|
+
|
|
49
59
|
# --- Static library target ---
|
|
50
60
|
add_library(beta_parameterization STATIC ${BETA_PARAM_SOURCES})
|
|
51
61
|
target_link_libraries(beta_parameterization
|
|
@@ -118,8 +128,8 @@ set_target_properties(beta_param_test_utils PROPERTIES
|
|
|
118
128
|
target_include_directories(beta_param_test_utils PUBLIC
|
|
119
129
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/mod_files_tests>)
|
|
120
130
|
|
|
121
|
-
set(BETA_PARAM_TEST_SUITES status lifecycle resolve outputs standalone
|
|
122
|
-
|
|
131
|
+
set(BETA_PARAM_TEST_SUITES status lifecycle resolve outputs standalone property golden
|
|
132
|
+
equivalence statelessness boundary)
|
|
123
133
|
foreach (suite IN LISTS BETA_PARAM_TEST_SUITES)
|
|
124
134
|
add_executable(beta_param_${suite}_test tests/beta_param_${suite}_test.f08)
|
|
125
135
|
target_link_libraries(beta_param_${suite}_test PRIVATE
|
|
@@ -159,7 +169,8 @@ add_executable(c_api_smoke_test tests/c_api_smoke_test.cpp)
|
|
|
159
169
|
target_link_libraries(c_api_smoke_test PRIVATE beta_parameterization_cxx)
|
|
160
170
|
add_test(NAME c_api_smoke COMMAND c_api_smoke_test)
|
|
161
171
|
|
|
162
|
-
#
|
|
172
|
+
# Contract family 3 (concurrency): many threads on one shared cache, via the
|
|
173
|
+
# C++ wrapper.
|
|
163
174
|
find_package(Threads REQUIRED)
|
|
164
175
|
add_executable(thread_stress_test tests/thread_stress_test.cpp)
|
|
165
176
|
target_link_libraries(thread_stress_test PRIVATE
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# beta-parameterization
|
|
2
|
+
|
|
3
|
+
Axially symmetric nuclear shapes in the spherical-harmonic (beta) expansion: a Fortran 2018 library with a C API, a C++20 wrapper and a Python wheel. It implements the two-tier shape parameterization contract shared with its sibling libraries.
|
|
4
|
+
|
|
5
|
+
## The shape
|
|
6
|
+
|
|
7
|
+
The surface radius, in units of the spherical radius R₀, is
|
|
8
|
+
|
|
9
|
+
R(θ) = c · [ 1 + Σ_{λ=1}^{n} β_λ · C_λ · P_λ(cos θ) ], C_λ = √((2λ+1) / 4π)
|
|
10
|
+
|
|
11
|
+
so that `C_λ · P_λ(cos θ)` is the spherical harmonic Y_λ0. `params(λ)` is β_λ0; position λ always means order λ. Up to 64 orders are supported.
|
|
12
|
+
|
|
13
|
+
Two per-call options change the shape:
|
|
14
|
+
|
|
15
|
+
- `apply_com` — β₁ is replaced by the value that puts the centre of mass at the origin (Newton iteration, tolerance 10⁻⁵ R₀).
|
|
16
|
+
- `conserve_volume` — the scale `c = (2 / ∫ R³ d cos θ)^(1/3)` restores the volume of the unit sphere. Without it, `c = 1`.
|
|
17
|
+
|
|
18
|
+
A shape is valid when R > 10⁻⁶ at both poles and on an internal 512-node Gauss-Legendre grid. An invalid shape returns a status code and zero-filled outputs.
|
|
19
|
+
|
|
20
|
+
## Two tiers
|
|
21
|
+
|
|
22
|
+
1. **One-shot.** One call computes one shape. All workspace is internal and discarded on return. For scripts and one-off plots.
|
|
23
|
+
2. **Read-only cache.** Build a cache once for a theta grid, then request any number of shapes against it. The cache holds only what does not depend on the parameters (Legendre tables, quadrature). It is immutable after creation and may be shared across threads. For loops and hot paths.
|
|
24
|
+
|
|
25
|
+
Both tiers return bitwise-identical results. Nothing parameter-dependent is stored: every call is independent of the calls before it.
|
|
26
|
+
|
|
27
|
+
Rules common to both tiers:
|
|
28
|
+
|
|
29
|
+
- A cache accepts `1 .. max_params` parameters per call; a one-shot call accepts `1 .. 64`.
|
|
30
|
+
- Missing trailing parameters are zero. A short vector and its zero-padded form give identical bits.
|
|
31
|
+
- Thetas are in radians, at least two, none at a pole (the open grid `θ_i = i·π/(n+1)` is the usual choice). Pole radii come from `resolve_shape`.
|
|
32
|
+
- Inputs must be finite and of physical magnitude. Release builds use fast-math and cannot detect NaN, and with `apply_com` the quadrature overflows for |β| beyond about 10⁷⁰.
|
|
33
|
+
- Every output is zero-filled on a nonzero status.
|
|
34
|
+
|
|
35
|
+
### Fortran
|
|
36
|
+
|
|
37
|
+
```fortran
|
|
38
|
+
program beta_example
|
|
39
|
+
use precision_utilities_mod, only: ik, rk
|
|
40
|
+
use beta_parameterization_mod, only: cache_t, cache_init_s, cache_free_s, &
|
|
41
|
+
cache_radius_grid_s, cache_resolve_shape_s, &
|
|
42
|
+
compute_radius_grid_standalone_s, SHAPE_VALID
|
|
43
|
+
implicit none
|
|
44
|
+
|
|
45
|
+
integer(kind = ik), parameter :: N = 180_ik
|
|
46
|
+
real(kind = rk), parameter :: PI = 3.141592653589793_rk
|
|
47
|
+
type(cache_t) :: cache
|
|
48
|
+
real(kind = rk) :: thetas(N), radii(N)
|
|
49
|
+
real(kind = rk) :: beta10, r_north, r_south, volume_factor
|
|
50
|
+
integer(kind = ik) :: i, status
|
|
51
|
+
|
|
52
|
+
do i = 1_ik, N
|
|
53
|
+
thetas(i) = real(i, rk) * PI / real(N + 1_ik, rk) ! open grid: no pole nodes
|
|
54
|
+
end do
|
|
55
|
+
|
|
56
|
+
! Tier 1: one-shot. params = (beta1, beta2), both options on.
|
|
57
|
+
call compute_radius_grid_standalone_s([0.0_rk, 0.25_rk], thetas, .true., .true., &
|
|
58
|
+
radii, status)
|
|
59
|
+
if (status /= SHAPE_VALID) error stop 'one-shot failed'
|
|
60
|
+
|
|
61
|
+
! Tier 2: build once, share, compute many.
|
|
62
|
+
call cache_init_s(cache, 8_ik, thetas, status) ! max_params = 8
|
|
63
|
+
call cache_radius_grid_s(cache, [0.0_rk, 0.25_rk], .true., .true., radii, status)
|
|
64
|
+
call cache_resolve_shape_s(cache, [0.0_rk, 0.25_rk], .true., .true., &
|
|
65
|
+
beta10, r_north, r_south, volume_factor, status)
|
|
66
|
+
call cache_free_s(cache)
|
|
67
|
+
end program beta_example
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Cached outputs: `cache_radius_grid_s`, `cache_radius_and_derivative_s`, `cache_resolve_shape_s` (corrected β₁, pole radii, volume factor), `cache_node_radius_and_derivative_s` (radius and derivative at a `node_set_t`: extra thetas built from the cache with `node_set_build_s`), and `cache_radius_grid_unchecked_s` (no validity gate and no volume scaling: the outline of a shape the checked path rejects). To evaluate several theta grids in one call, build one node set over their concatenation.
|
|
71
|
+
|
|
72
|
+
### C
|
|
73
|
+
|
|
74
|
+
```c
|
|
75
|
+
#include "beta_parameterization.h"
|
|
76
|
+
#include <stdio.h>
|
|
77
|
+
|
|
78
|
+
int main(void) {
|
|
79
|
+
enum { N = 180 };
|
|
80
|
+
const double pi = 3.141592653589793;
|
|
81
|
+
double thetas[N], radii[N];
|
|
82
|
+
const double params[2] = {0.0, 0.25};
|
|
83
|
+
for (int i = 0; i < N; ++i) thetas[i] = (i + 1) * pi / (N + 1);
|
|
84
|
+
|
|
85
|
+
/* Tier 1: one-shot. */
|
|
86
|
+
int status = beta_param_radius_grid_standalone(params, 2, thetas, N, 1, 1, radii);
|
|
87
|
+
if (status != BETA_PARAM_VALID) {
|
|
88
|
+
printf("%s\n", beta_param_status_message(status));
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Tier 2: build once, share, compute many. */
|
|
93
|
+
beta_param_cache_t* cache = beta_param_cache_create(8, thetas, N, &status);
|
|
94
|
+
if (cache == NULL) {
|
|
95
|
+
printf("%s\n", beta_param_status_message(status));
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
status = beta_param_cache_radius_grid(cache, params, 2, 1, 1, radii, N);
|
|
99
|
+
beta_param_cache_destroy(cache);
|
|
100
|
+
return status;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
C++20 callers can use the RAII wrapper in `beta_parameterization.hpp` (`beta_param::Cache`, `beta_param::NodeSet`, `const` compute methods, `std::span` arguments).
|
|
105
|
+
|
|
106
|
+
### Python
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import beta_parameterization as bp
|
|
110
|
+
|
|
111
|
+
thetas = bp.theta_grid(180) # open grid, no pole nodes
|
|
112
|
+
|
|
113
|
+
# Tier 1: one-shot.
|
|
114
|
+
res = bp.radius_grid([0.0, 0.25], thetas, conserve_volume=True, apply_com=True)
|
|
115
|
+
assert res.ok, res.message
|
|
116
|
+
|
|
117
|
+
# Tier 2: build once, share, compute many.
|
|
118
|
+
with bp.Cache(8, thetas) as cache: # max_params = 8
|
|
119
|
+
res = cache.radius_and_derivative([0.0, 0.25], conserve_volume=True, apply_com=True)
|
|
120
|
+
shape = cache.resolve_shape([0.0, 0.25], conserve_volume=True, apply_com=True)
|
|
121
|
+
print(res.radii[:3], shape.volume_factor)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Shape-validation failures come back as results carrying a `Status`; `BetaParamError` is raised only for usage errors (failed create, closed handle, non-1-D input).
|
|
125
|
+
|
|
126
|
+
## Status codes
|
|
127
|
+
|
|
128
|
+
| Code | Name | Meaning |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| 0 | `SHAPE_VALID` | success |
|
|
131
|
+
| 1 | `SHAPE_ERROR_TOO_MANY_PARAMS` | `max_params` or a one-shot vector exceeds 64 |
|
|
132
|
+
| 2 | `SHAPE_ERROR_CACHE_NOT_INITIALIZED` | uninitialized cache (or NULL handle) |
|
|
133
|
+
| 3 | `SHAPE_ERROR_INVALID_GRID` | fewer than 2 thetas, or a table that cannot be allocated |
|
|
134
|
+
| 4 | `SHAPE_ERROR_WRONG_PARAM_COUNT` | vector length outside `1..max_params`, or an empty one-shot vector |
|
|
135
|
+
| 5 | `SHAPE_ERROR_INVALID_INIT` | `max_params < 1` |
|
|
136
|
+
| 100 | `BETA_PARAM_ERROR_NORTH_POLE` | radius not positive at θ = 0 |
|
|
137
|
+
| 101 | `BETA_PARAM_ERROR_SOUTH_POLE` | radius not positive at θ = π |
|
|
138
|
+
| 102 | `BETA_PARAM_ERROR_INTERIOR_NEGATIVE` | radius not positive in the interior |
|
|
139
|
+
| 103 | `BETA_PARAM_ERROR_COM_NOT_CONVERGED` | centre-of-mass correction did not converge |
|
|
140
|
+
| 104 | `BETA_PARAM_ERROR_INVALID_BUFFER_SIZE` | output buffer size does not match the theta or node count |
|
|
141
|
+
| 105 | `BETA_PARAM_ERROR_POLE_NODE` | a theta at or beyond a pole |
|
|
142
|
+
| 106 | `BETA_PARAM_ERROR_NODE_SET_MISMATCH` | node set unbuilt, or built for a smaller cache |
|
|
143
|
+
|
|
144
|
+
Codes 0–5 are shared by every shape parameterization library; the C header and the Python `Status` enum mirror all of them under `BETA_PARAM_*` names.
|
|
145
|
+
|
|
146
|
+
## Building
|
|
147
|
+
|
|
148
|
+
Requires GCC (gfortran, g++) and CMake ≥ 3.20. Dependencies are fetched by CMake.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
|
152
|
+
cmake --build build -j
|
|
153
|
+
ctest --test-dir build --output-on-failure
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
As a CMake dependency:
|
|
157
|
+
|
|
158
|
+
```cmake
|
|
159
|
+
FetchContent_Declare(
|
|
160
|
+
beta-parameterization
|
|
161
|
+
GIT_REPOSITORY https://github.com/AleksanderAugustyn/beta-parameterization.git
|
|
162
|
+
GIT_TAG 4.0.0
|
|
163
|
+
)
|
|
164
|
+
FetchContent_MakeAvailable(beta-parameterization)
|
|
165
|
+
target_link_libraries(my_target PRIVATE BetaParameterization::beta_parameterization)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Targets: `BetaParameterization::beta_parameterization` (static, Fortran), `::beta_parameterization_shared` (shared, C API), `::beta_parameterization_cxx` (header-only C++ wrapper over the shared library).
|
|
169
|
+
|
|
170
|
+
Python:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
pip install beta-parameterization==4.0.0
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The wheel is self-contained (manylinux2014, x86-64). To run the Python tests against a local build instead: `BETA_PARAM_LIB=$PWD/build/libbeta_parameterization.so PYTHONPATH=python python -m pytest python/tests`.
|
|
177
|
+
|
|
178
|
+
## Dependencies
|
|
179
|
+
|
|
180
|
+
| Dependency | Version |
|
|
181
|
+
|---|---|
|
|
182
|
+
| [fortran-foundations](https://github.com/AleksanderAugustyn/fortran-foundations) | 3.0.0 |
|
|
183
|
+
| [gcc-compiler-options](https://github.com/AleksanderAugustyn/gcc-compiler-options) | 2.0.0 |
|
|
184
|
+
| numpy (wheel only) | ≥ 1.21 |
|
|
185
|
+
|
|
186
|
+
## Design
|
|
187
|
+
|
|
188
|
+
- **Stateless computes.** A cache holds only parameter-independent tables. Every compute takes it read-only and is `pure`; per-call scratch lives on the stack. One cache serves every thread and every option combination.
|
|
189
|
+
- **One pipeline.** A one-shot call builds a local cache and runs the cached routine, so the two tiers cannot drift apart.
|
|
190
|
+
- **Reproducible bits.** The library's own objects are compiled without link-time optimization. Under `-flto -ffast-math` a kernel inlined into two call sites may round differently in each; without LTO every caller executes the same machine code, and equal inputs give equal bits.
|
|
191
|
+
- **No stops.** Every failure is a status code; nothing in the library calls `error stop`.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file beta_parameterization.h
|
|
3
|
+
* @brief C API for the Fortran beta parameterization library (v4.0.0).
|
|
4
|
+
*
|
|
5
|
+
* Two tiers of computation:
|
|
6
|
+
* - One-shot: the `*_standalone` functions compute one shape per call. All
|
|
7
|
+
* workspace is internal and discarded on return.
|
|
8
|
+
* - Read-only cache: create a `beta_param_cache_t` once, then call the
|
|
9
|
+
* `beta_param_cache_*` functions for any number of shapes.
|
|
10
|
+
*
|
|
11
|
+
* Two handle types:
|
|
12
|
+
* - `beta_param_cache_t` — everything determined by `max_params` and the
|
|
13
|
+
* primary theta set (normalization constants, Gauss-Legendre tables,
|
|
14
|
+
* Legendre tables at the primary thetas).
|
|
15
|
+
* - `beta_param_node_set_t` — an extra set of evaluation thetas with its own
|
|
16
|
+
* Legendre tables, built from a cache.
|
|
17
|
+
*
|
|
18
|
+
* Thread safety:
|
|
19
|
+
* Both handles are immutable after creation. Every compute function takes
|
|
20
|
+
* them `const` and may be called concurrently, from any number of threads,
|
|
21
|
+
* on the same handles. Create and destroy must not race with any other call
|
|
22
|
+
* on the same handle. The library holds no mutable global state.
|
|
23
|
+
*
|
|
24
|
+
* Lifetime:
|
|
25
|
+
* A cache must outlive every node set built from it. Destroy order: node
|
|
26
|
+
* sets first, their cache last. A node set serves a cache only if it was
|
|
27
|
+
* built from a cache with at least that cache's `max_params`.
|
|
28
|
+
*
|
|
29
|
+
* Parameters:
|
|
30
|
+
* A cached call accepts 1 .. max_params parameters; a one-shot call accepts
|
|
31
|
+
* 1 .. BETA_PARAM_MAX_PARAMS_LIMIT. Missing trailing parameters are zero: a
|
|
32
|
+
* short vector and its zero-padded form give bitwise-identical results.
|
|
33
|
+
* `conserve_volume` and `apply_com` are per-call options (nonzero = on); one
|
|
34
|
+
* cache serves every combination.
|
|
35
|
+
*
|
|
36
|
+
* Diagnostics:
|
|
37
|
+
* There are no message buffers. `_create` functions return NULL on failure
|
|
38
|
+
* and write the reason to the nullable `int* status` out-parameter (pass
|
|
39
|
+
* NULL to ignore it). Every other function returns the status code directly;
|
|
40
|
+
* BETA_PARAM_VALID (0) means success. beta_param_status_message() maps a code
|
|
41
|
+
* to a fixed, static, null-terminated string; the returned pointer is owned
|
|
42
|
+
* by the library, never freed by the caller, and is safe to read from any
|
|
43
|
+
* thread.
|
|
44
|
+
*
|
|
45
|
+
* Failure behavior:
|
|
46
|
+
* On any nonzero status from a compute function, every output buffer is
|
|
47
|
+
* zero-filled. No state exists, so the next call is unaffected. A NULL
|
|
48
|
+
* handle passed into a compute function returns
|
|
49
|
+
* BETA_PARAM_ERROR_CACHE_NOT_INITIALIZED (2).
|
|
50
|
+
*
|
|
51
|
+
* Size arguments:
|
|
52
|
+
* Every size argument must be the ACTUAL extent of the caller's buffer. A
|
|
53
|
+
* wrong size that is honest about the caller's own memory is reported with a
|
|
54
|
+
* status code, however large; a stated size larger than the real buffer is
|
|
55
|
+
* undefined behavior. Negative counts are treated as zero.
|
|
56
|
+
*
|
|
57
|
+
* Precondition — finite input:
|
|
58
|
+
* `params` and `thetas` must be finite. Non-finite input is undefined
|
|
59
|
+
* behavior: the library cannot detect NaN under fast-math, so no check
|
|
60
|
+
* rejects it, and no particular result is promised. A call may return
|
|
61
|
+
* BETA_PARAM_VALID (0) with NaN outputs; a NaN trailing parameter is
|
|
62
|
+
* trimmed like a zero, giving the finite outputs of the shorter vector; a
|
|
63
|
+
* Debug build may trap. Screen inputs before calling. Magnitudes must be
|
|
64
|
+
* physical as well: with `apply_com` the centre-of-mass quadrature evaluates
|
|
65
|
+
* R^4 before any validity gate and overflows for |beta| beyond about 1e70.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
#ifndef BETA_PARAMETERIZATION_H
|
|
69
|
+
#define BETA_PARAMETERIZATION_H
|
|
70
|
+
|
|
71
|
+
#ifdef __cplusplus
|
|
72
|
+
extern "C" {
|
|
73
|
+
#endif
|
|
74
|
+
|
|
75
|
+
/* --- Limits --- */
|
|
76
|
+
/** Longest parameter vector either tier accepts; the highest `max_params`. */
|
|
77
|
+
#define BETA_PARAM_MAX_PARAMS_LIMIT 64
|
|
78
|
+
|
|
79
|
+
/* --- Shared contract status codes (0-99, identical numbers in every
|
|
80
|
+
* shape-parameterization library; 6 is retired and never reused) --- */
|
|
81
|
+
#define BETA_PARAM_VALID 0
|
|
82
|
+
#define BETA_PARAM_ERROR_TOO_MANY_PARAMS 1
|
|
83
|
+
#define BETA_PARAM_ERROR_CACHE_NOT_INITIALIZED 2
|
|
84
|
+
#define BETA_PARAM_ERROR_INVALID_GRID 3
|
|
85
|
+
#define BETA_PARAM_ERROR_WRONG_PARAM_COUNT 4
|
|
86
|
+
#define BETA_PARAM_ERROR_INVALID_INIT 5
|
|
87
|
+
|
|
88
|
+
/* --- Library status codes (>= 100, append-only) --- */
|
|
89
|
+
#define BETA_PARAM_ERROR_NORTH_POLE 100
|
|
90
|
+
#define BETA_PARAM_ERROR_SOUTH_POLE 101
|
|
91
|
+
#define BETA_PARAM_ERROR_INTERIOR_NEGATIVE 102
|
|
92
|
+
#define BETA_PARAM_ERROR_COM_NOT_CONVERGED 103
|
|
93
|
+
#define BETA_PARAM_ERROR_INVALID_BUFFER_SIZE 104
|
|
94
|
+
#define BETA_PARAM_ERROR_POLE_NODE 105
|
|
95
|
+
#define BETA_PARAM_ERROR_NODE_SET_MISMATCH 106
|
|
96
|
+
|
|
97
|
+
/* --- Opaque handles --- */
|
|
98
|
+
typedef struct beta_param_cache beta_param_cache_t;
|
|
99
|
+
typedef struct beta_param_node_set beta_param_node_set_t;
|
|
100
|
+
|
|
101
|
+
/* --- Diagnostics --- */
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Fixed description of a status code. Never NULL; unknown codes map to an
|
|
105
|
+
* "unknown status code" string. The pointer is to static storage: do not free
|
|
106
|
+
* it, and it stays valid for the life of the process.
|
|
107
|
+
*/
|
|
108
|
+
const char* beta_param_status_message(int status);
|
|
109
|
+
|
|
110
|
+
/* --- Cache lifecycle --- */
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Build the read-only cache. Returns NULL on failure.
|
|
114
|
+
*
|
|
115
|
+
* @param max_params Longest parameter vector the cache accepts,
|
|
116
|
+
* 1 .. BETA_PARAM_MAX_PARAMS_LIMIT; also the Legendre order
|
|
117
|
+
* of its tables. Below 1: BETA_PARAM_ERROR_INVALID_INIT;
|
|
118
|
+
* above the limit: BETA_PARAM_ERROR_TOO_MANY_PARAMS.
|
|
119
|
+
* @param thetas Primary theta set in radians; at least 2 entries
|
|
120
|
+
* (BETA_PARAM_ERROR_INVALID_GRID), none at or beyond a pole
|
|
121
|
+
* (cos(theta)^2 == 1 in double precision;
|
|
122
|
+
* BETA_PARAM_ERROR_POLE_NODE)
|
|
123
|
+
* @param n_thetas Number of entries in thetas
|
|
124
|
+
* @param status Nullable; receives BETA_PARAM_VALID or the rejecting code
|
|
125
|
+
*/
|
|
126
|
+
beta_param_cache_t* beta_param_cache_create(
|
|
127
|
+
int max_params, const double* thetas, int n_thetas, int* status);
|
|
128
|
+
|
|
129
|
+
/** Destroy a cache. NULL-safe. Every node set built from it must already be
|
|
130
|
+
* destroyed. */
|
|
131
|
+
void beta_param_cache_destroy(beta_param_cache_t* cache);
|
|
132
|
+
|
|
133
|
+
/* --- Node-set lifecycle --- */
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Build an extra evaluation set (thetas plus Legendre P_k and P_k' tables)
|
|
137
|
+
* sized to `cache`. Returns NULL on failure — including a pole node, which is
|
|
138
|
+
* rejected with BETA_PARAM_ERROR_POLE_NODE; use the resolve_shape polar radii
|
|
139
|
+
* for the poles instead. A NULL cache gives
|
|
140
|
+
* BETA_PARAM_ERROR_CACHE_NOT_INITIALIZED.
|
|
141
|
+
*
|
|
142
|
+
* @param cache Cache handle; supplies max_params. Must outlive the node
|
|
143
|
+
* set.
|
|
144
|
+
* @param thetas Node angles in radians; any order, need not be uniform
|
|
145
|
+
* @param n_thetas Number of nodes (at least 2)
|
|
146
|
+
* @param status Nullable; receives the rejecting code on failure
|
|
147
|
+
*/
|
|
148
|
+
beta_param_node_set_t* beta_param_node_set_create(
|
|
149
|
+
const beta_param_cache_t* cache, const double* thetas, int n_thetas,
|
|
150
|
+
int* status);
|
|
151
|
+
|
|
152
|
+
/** Destroy a node set. NULL-safe. */
|
|
153
|
+
void beta_param_node_set_destroy(beta_param_node_set_t* node_set);
|
|
154
|
+
|
|
155
|
+
/* --- Cached computes ---
|
|
156
|
+
*
|
|
157
|
+
* None of these modifies the cache. `params` holds 1 .. max_params entries,
|
|
158
|
+
* else BETA_PARAM_ERROR_WRONG_PARAM_COUNT. Output buffer lengths must equal
|
|
159
|
+
* the cache's theta count (or the node set's node count), else
|
|
160
|
+
* BETA_PARAM_ERROR_INVALID_BUFFER_SIZE. `conserve_volume` and `apply_com` are
|
|
161
|
+
* nonzero to switch the option on.
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
/** R(theta) at the cache's primary thetas. `n_radii` must equal that count. */
|
|
165
|
+
int beta_param_cache_radius_grid(
|
|
166
|
+
const beta_param_cache_t* cache, const double* params, int n_params,
|
|
167
|
+
int conserve_volume, int apply_com, double* radii, int n_radii);
|
|
168
|
+
|
|
169
|
+
/** R(theta) and dR/dtheta at the cache's primary thetas. */
|
|
170
|
+
int beta_param_cache_radius_and_derivative(
|
|
171
|
+
const beta_param_cache_t* cache, const double* params, int n_params,
|
|
172
|
+
int conserve_volume, int apply_com,
|
|
173
|
+
double* radii, double* dr_dthetas, int n_radii);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* R(theta) at the primary thetas with NO validation gates and NO volume
|
|
177
|
+
* scaling — the rendering/diagnostic path. It reports usage errors and a
|
|
178
|
+
* failed COM correction only, so a shape rejected by the checked path still
|
|
179
|
+
* yields its (partly negative) outline instead of a zero-filled buffer.
|
|
180
|
+
*
|
|
181
|
+
* CAUTION: the radii are UNSCALED — this path never computes the volume
|
|
182
|
+
* factor, which is why it takes no `conserve_volume`. Mixing it with
|
|
183
|
+
* beta_param_cache_radius_grid(..., conserve_volume = 1, ...) draws two
|
|
184
|
+
* outlines of different size for one shape; scale by
|
|
185
|
+
* beta_param_cache_resolve_shape()'s volume_factor if the sizes must agree.
|
|
186
|
+
*/
|
|
187
|
+
int beta_param_cache_radius_grid_unchecked(
|
|
188
|
+
const beta_param_cache_t* cache, const double* params, int n_params,
|
|
189
|
+
int apply_com, double* radii, int n_radii);
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Resolve a shape without evaluating a grid: the COM-corrected beta10, the
|
|
193
|
+
* analytic polar radii, and the applied volume factor.
|
|
194
|
+
*
|
|
195
|
+
* `corrected_beta10` is a beta-space value and is never volume-scaled;
|
|
196
|
+
* `r_north` and `r_south` are scaled. `volume_factor` is exactly 1.0 when
|
|
197
|
+
* conserve_volume = 0.
|
|
198
|
+
*/
|
|
199
|
+
int beta_param_cache_resolve_shape(
|
|
200
|
+
const beta_param_cache_t* cache, const double* params, int n_params,
|
|
201
|
+
int conserve_volume, int apply_com,
|
|
202
|
+
double* corrected_beta10, double* r_north, double* r_south,
|
|
203
|
+
double* volume_factor);
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* R(theta) and dR/dtheta at a node set's thetas. The node set must come from
|
|
207
|
+
* a cache with at least this cache's max_params, else
|
|
208
|
+
* BETA_PARAM_ERROR_NODE_SET_MISMATCH; a node set built from this cache always
|
|
209
|
+
* qualifies. `n_nodes` must equal the node set's node count. To fold several
|
|
210
|
+
* theta grids into one call, build one node set over their concatenation.
|
|
211
|
+
*/
|
|
212
|
+
int beta_param_cache_node_radius_and_derivative(
|
|
213
|
+
const beta_param_cache_t* cache, const double* params, int n_params,
|
|
214
|
+
const beta_param_node_set_t* node_set,
|
|
215
|
+
int conserve_volume, int apply_com,
|
|
216
|
+
double* radii, double* dr_dthetas, int n_nodes);
|
|
217
|
+
|
|
218
|
+
/* --- One-shot computes (tier 1) ---
|
|
219
|
+
*
|
|
220
|
+
* A cache is built, used and discarded per call — no handle, nothing to free.
|
|
221
|
+
* `n_params` may be 1 .. BETA_PARAM_MAX_PARAMS_LIMIT: above that,
|
|
222
|
+
* BETA_PARAM_ERROR_TOO_MANY_PARAMS, never silent truncation; below 1,
|
|
223
|
+
* BETA_PARAM_ERROR_WRONG_PARAM_COUNT. Output buffers hold n_thetas doubles.
|
|
224
|
+
* Outputs are zero-filled on failure. Results are bitwise identical to the
|
|
225
|
+
* cached functions on a cache with max_params >= n_params.
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
int beta_param_radius_grid_standalone(
|
|
229
|
+
const double* params, int n_params,
|
|
230
|
+
const double* thetas, int n_thetas,
|
|
231
|
+
int conserve_volume, int apply_com, double* radii);
|
|
232
|
+
|
|
233
|
+
int beta_param_radius_and_derivative_standalone(
|
|
234
|
+
const double* params, int n_params,
|
|
235
|
+
const double* thetas, int n_thetas,
|
|
236
|
+
int conserve_volume, int apply_com,
|
|
237
|
+
double* radii, double* dr_dthetas);
|
|
238
|
+
|
|
239
|
+
#ifdef __cplusplus
|
|
240
|
+
}
|
|
241
|
+
#endif
|
|
242
|
+
|
|
243
|
+
#endif /* BETA_PARAMETERIZATION_H */
|