beta-parameterization 3.0.0__tar.gz → 4.0.1__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.
Files changed (46) hide show
  1. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/CHANGELOG.md +90 -0
  2. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/CMakeLists.txt +23 -12
  3. beta_parameterization-4.0.1/PKG-INFO +206 -0
  4. beta_parameterization-4.0.1/README.md +191 -0
  5. beta_parameterization-4.0.1/include/beta_parameterization.h +243 -0
  6. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/include/beta_parameterization.hpp +102 -164
  7. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/pyproject.toml +6 -0
  8. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/python/beta_parameterization/__init__.py +2 -2
  9. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/python/beta_parameterization/_cdefs.py +14 -26
  10. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/python/beta_parameterization/api.py +117 -47
  11. beta_parameterization-4.0.1/python/tests/test_api.py +318 -0
  12. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/src/beta_parameterization_c_api_mod.f08 +197 -165
  13. beta_parameterization-4.0.1/src/beta_parameterization_mod.f08 +996 -0
  14. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/src/beta_parameterization_workers_mod.f08 +7 -1
  15. beta_parameterization-4.0.1/tests/beta_param_boundary_test.f08 +256 -0
  16. beta_parameterization-4.0.1/tests/beta_param_equivalence_test.f08 +385 -0
  17. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/tests/beta_param_golden_test.f08 +5 -5
  18. beta_parameterization-4.0.1/tests/beta_param_lifecycle_test.f08 +115 -0
  19. beta_parameterization-4.0.1/tests/beta_param_outputs_test.f08 +187 -0
  20. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/tests/beta_param_property_test.f08 +5 -5
  21. beta_parameterization-4.0.1/tests/beta_param_resolve_test.f08 +81 -0
  22. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/tests/beta_param_standalone_test.f08 +39 -30
  23. beta_parameterization-4.0.1/tests/beta_param_statelessness_test.f08 +304 -0
  24. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/tests/beta_param_status_test.f08 +13 -1
  25. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/tests/beta_pes_sweep.f08 +4 -4
  26. beta_parameterization-4.0.1/tests/c_api_smoke_test.cpp +309 -0
  27. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/tests/golden_capture.f08 +4 -5
  28. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/tests/test_utils_mod.f08 +34 -1
  29. beta_parameterization-4.0.1/tests/thread_stress_test.cpp +242 -0
  30. beta_parameterization-3.0.0/PKG-INFO +0 -11
  31. beta_parameterization-3.0.0/include/beta_parameterization.h +0 -263
  32. beta_parameterization-3.0.0/python/tests/test_api.py +0 -153
  33. beta_parameterization-3.0.0/src/beta_parameterization_mod.f08 +0 -1419
  34. beta_parameterization-3.0.0/tests/beta_param_bitwise_test.f08 +0 -252
  35. beta_parameterization-3.0.0/tests/beta_param_boundary_test.f08 +0 -148
  36. beta_parameterization-3.0.0/tests/beta_param_interleave_test.f08 +0 -378
  37. beta_parameterization-3.0.0/tests/beta_param_lifecycle_test.f08 +0 -124
  38. beta_parameterization-3.0.0/tests/beta_param_minimality_test.f08 +0 -260
  39. beta_parameterization-3.0.0/tests/beta_param_outputs_test.f08 +0 -132
  40. beta_parameterization-3.0.0/tests/beta_param_resolve_test.f08 +0 -53
  41. beta_parameterization-3.0.0/tests/c_api_smoke_test.cpp +0 -213
  42. beta_parameterization-3.0.0/tests/thread_stress_test.cpp +0 -168
  43. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/.gitignore +0 -0
  44. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/LICENSE +0 -0
  45. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/ci/build-wheel.sh +0 -0
  46. {beta_parameterization-3.0.0 → beta_parameterization-4.0.1}/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 3.0.0
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
- GCC-Compiler-Options
18
- GIT_REPOSITORY https://github.com/AleksanderAugustyn/GCC-Compiler-Options.git
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(GCC-Compiler-Options)
21
+ FetchContent_MakeAvailable(gcc-compiler-options)
22
22
 
23
23
  # --- Fetch foundational Fortran modules ---
24
24
  FetchContent_Declare(
25
- Fortran-Foundations
26
- GIT_REPOSITORY https://github.com/AleksanderAugustyn/Fortran-Foundations.git
27
- GIT_TAG 2.4.0
25
+ fortran-foundations
26
+ GIT_REPOSITORY https://github.com/AleksanderAugustyn/fortran-foundations.git
27
+ GIT_TAG 3.0.0
28
28
  )
29
- FetchContent_MakeAvailable(Fortran-Foundations)
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 GCC-Compiler-Options ---
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 bitwise minimality
122
- interleave boundary property golden)
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
- # Thread model of 3.0.0: shared tables, per-thread caches, via the C++ wrapper.
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,206 @@
1
+ Metadata-Version: 2.4
2
+ Name: beta-parameterization
3
+ Version: 4.0.1
4
+ Summary: Python bindings for the beta (Legendre) nuclear-shape parameterization library
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Project-URL: Homepage, https://github.com/AleksanderAugustyn/beta-parameterization
8
+ Project-URL: Repository, https://github.com/AleksanderAugustyn/beta-parameterization
9
+ Project-URL: Changelog, https://github.com/AleksanderAugustyn/beta-parameterization/blob/master/CHANGELOG.md
10
+ Requires-Python: >=3.9
11
+ Requires-Dist: numpy>=1.21
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest>=7; extra == "test"
14
+ Description-Content-Type: text/markdown
15
+
16
+ # beta-parameterization
17
+
18
+ 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.
19
+
20
+ ## The shape
21
+
22
+ The surface radius, in units of the spherical radius R₀, is
23
+
24
+ R(θ) = c · [ 1 + Σ_{λ=1}^{n} β_λ · C_λ · P_λ(cos θ) ], C_λ = √((2λ+1) / 4π)
25
+
26
+ so that `C_λ · P_λ(cos θ)` is the spherical harmonic Y_λ0. `params(λ)` is β_λ0; position λ always means order λ. Up to 64 orders are supported.
27
+
28
+ Two per-call options change the shape:
29
+
30
+ - `apply_com` — β₁ is replaced by the value that puts the centre of mass at the origin (Newton iteration, tolerance 10⁻⁵ R₀).
31
+ - `conserve_volume` — the scale `c = (2 / ∫ R³ d cos θ)^(1/3)` restores the volume of the unit sphere. Without it, `c = 1`.
32
+
33
+ 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.
34
+
35
+ ## Two tiers
36
+
37
+ 1. **One-shot.** One call computes one shape. All workspace is internal and discarded on return. For scripts and one-off plots.
38
+ 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.
39
+
40
+ Both tiers return bitwise-identical results. Nothing parameter-dependent is stored: every call is independent of the calls before it.
41
+
42
+ Rules common to both tiers:
43
+
44
+ - A cache accepts `1 .. max_params` parameters per call; a one-shot call accepts `1 .. 64`.
45
+ - Missing trailing parameters are zero. A short vector and its zero-padded form give identical bits.
46
+ - 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`.
47
+ - 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⁷⁰.
48
+ - Every output is zero-filled on a nonzero status.
49
+
50
+ ### Fortran
51
+
52
+ ```fortran
53
+ program beta_example
54
+ use precision_utilities_mod, only: ik, rk
55
+ use beta_parameterization_mod, only: cache_t, cache_init_s, cache_free_s, &
56
+ cache_radius_grid_s, cache_resolve_shape_s, &
57
+ compute_radius_grid_standalone_s, SHAPE_VALID
58
+ implicit none
59
+
60
+ integer(kind = ik), parameter :: N = 180_ik
61
+ real(kind = rk), parameter :: PI = 3.141592653589793_rk
62
+ type(cache_t) :: cache
63
+ real(kind = rk) :: thetas(N), radii(N)
64
+ real(kind = rk) :: beta10, r_north, r_south, volume_factor
65
+ integer(kind = ik) :: i, status
66
+
67
+ do i = 1_ik, N
68
+ thetas(i) = real(i, rk) * PI / real(N + 1_ik, rk) ! open grid: no pole nodes
69
+ end do
70
+
71
+ ! Tier 1: one-shot. params = (beta1, beta2), both options on.
72
+ call compute_radius_grid_standalone_s([0.0_rk, 0.25_rk], thetas, .true., .true., &
73
+ radii, status)
74
+ if (status /= SHAPE_VALID) error stop 'one-shot failed'
75
+
76
+ ! Tier 2: build once, share, compute many.
77
+ call cache_init_s(cache, 8_ik, thetas, status) ! max_params = 8
78
+ call cache_radius_grid_s(cache, [0.0_rk, 0.25_rk], .true., .true., radii, status)
79
+ call cache_resolve_shape_s(cache, [0.0_rk, 0.25_rk], .true., .true., &
80
+ beta10, r_north, r_south, volume_factor, status)
81
+ call cache_free_s(cache)
82
+ end program beta_example
83
+ ```
84
+
85
+ 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.
86
+
87
+ ### C
88
+
89
+ ```c
90
+ #include "beta_parameterization.h"
91
+ #include <stdio.h>
92
+
93
+ int main(void) {
94
+ enum { N = 180 };
95
+ const double pi = 3.141592653589793;
96
+ double thetas[N], radii[N];
97
+ const double params[2] = {0.0, 0.25};
98
+ for (int i = 0; i < N; ++i) thetas[i] = (i + 1) * pi / (N + 1);
99
+
100
+ /* Tier 1: one-shot. */
101
+ int status = beta_param_radius_grid_standalone(params, 2, thetas, N, 1, 1, radii);
102
+ if (status != BETA_PARAM_VALID) {
103
+ printf("%s\n", beta_param_status_message(status));
104
+ return 1;
105
+ }
106
+
107
+ /* Tier 2: build once, share, compute many. */
108
+ beta_param_cache_t* cache = beta_param_cache_create(8, thetas, N, &status);
109
+ if (cache == NULL) {
110
+ printf("%s\n", beta_param_status_message(status));
111
+ return 1;
112
+ }
113
+ status = beta_param_cache_radius_grid(cache, params, 2, 1, 1, radii, N);
114
+ beta_param_cache_destroy(cache);
115
+ return status;
116
+ }
117
+ ```
118
+
119
+ C++20 callers can use the RAII wrapper in `beta_parameterization.hpp` (`beta_param::Cache`, `beta_param::NodeSet`, `const` compute methods, `std::span` arguments).
120
+
121
+ ### Python
122
+
123
+ ```python
124
+ import beta_parameterization as bp
125
+
126
+ thetas = bp.theta_grid(180) # open grid, no pole nodes
127
+
128
+ # Tier 1: one-shot.
129
+ res = bp.radius_grid([0.0, 0.25], thetas, conserve_volume=True, apply_com=True)
130
+ assert res.ok, res.message
131
+
132
+ # Tier 2: build once, share, compute many.
133
+ with bp.Cache(8, thetas) as cache: # max_params = 8
134
+ res = cache.radius_and_derivative([0.0, 0.25], conserve_volume=True, apply_com=True)
135
+ shape = cache.resolve_shape([0.0, 0.25], conserve_volume=True, apply_com=True)
136
+ print(res.radii[:3], shape.volume_factor)
137
+ ```
138
+
139
+ 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).
140
+
141
+ ## Status codes
142
+
143
+ | Code | Name | Meaning |
144
+ |---|---|---|
145
+ | 0 | `SHAPE_VALID` | success |
146
+ | 1 | `SHAPE_ERROR_TOO_MANY_PARAMS` | `max_params` or a one-shot vector exceeds 64 |
147
+ | 2 | `SHAPE_ERROR_CACHE_NOT_INITIALIZED` | uninitialized cache (or NULL handle) |
148
+ | 3 | `SHAPE_ERROR_INVALID_GRID` | fewer than 2 thetas, or a table that cannot be allocated |
149
+ | 4 | `SHAPE_ERROR_WRONG_PARAM_COUNT` | vector length outside `1..max_params`, or an empty one-shot vector |
150
+ | 5 | `SHAPE_ERROR_INVALID_INIT` | `max_params < 1` |
151
+ | 100 | `BETA_PARAM_ERROR_NORTH_POLE` | radius not positive at θ = 0 |
152
+ | 101 | `BETA_PARAM_ERROR_SOUTH_POLE` | radius not positive at θ = π |
153
+ | 102 | `BETA_PARAM_ERROR_INTERIOR_NEGATIVE` | radius not positive in the interior |
154
+ | 103 | `BETA_PARAM_ERROR_COM_NOT_CONVERGED` | centre-of-mass correction did not converge |
155
+ | 104 | `BETA_PARAM_ERROR_INVALID_BUFFER_SIZE` | output buffer size does not match the theta or node count |
156
+ | 105 | `BETA_PARAM_ERROR_POLE_NODE` | a theta at or beyond a pole |
157
+ | 106 | `BETA_PARAM_ERROR_NODE_SET_MISMATCH` | node set unbuilt, or built for a smaller cache |
158
+
159
+ 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.
160
+
161
+ ## Building
162
+
163
+ Requires GCC (gfortran, g++) and CMake ≥ 3.20. Dependencies are fetched by CMake.
164
+
165
+ ```bash
166
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
167
+ cmake --build build -j
168
+ ctest --test-dir build --output-on-failure
169
+ ```
170
+
171
+ As a CMake dependency:
172
+
173
+ ```cmake
174
+ FetchContent_Declare(
175
+ beta-parameterization
176
+ GIT_REPOSITORY https://github.com/AleksanderAugustyn/beta-parameterization.git
177
+ GIT_TAG 4.0.0
178
+ )
179
+ FetchContent_MakeAvailable(beta-parameterization)
180
+ target_link_libraries(my_target PRIVATE BetaParameterization::beta_parameterization)
181
+ ```
182
+
183
+ Targets: `BetaParameterization::beta_parameterization` (static, Fortran), `::beta_parameterization_shared` (shared, C API), `::beta_parameterization_cxx` (header-only C++ wrapper over the shared library).
184
+
185
+ Python:
186
+
187
+ ```bash
188
+ pip install beta-parameterization==4.0.0
189
+ ```
190
+
191
+ 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`.
192
+
193
+ ## Dependencies
194
+
195
+ | Dependency | Version |
196
+ |---|---|
197
+ | [fortran-foundations](https://github.com/AleksanderAugustyn/fortran-foundations) | 3.0.0 |
198
+ | [gcc-compiler-options](https://github.com/AleksanderAugustyn/gcc-compiler-options) | 2.0.0 |
199
+ | numpy (wheel only) | ≥ 1.21 |
200
+
201
+ ## Design
202
+
203
+ - **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.
204
+ - **One pipeline.** A one-shot call builds a local cache and runs the cached routine, so the two tiers cannot drift apart.
205
+ - **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.
206
+ - **No stops.** Every failure is a status code; nothing in the library calls `error stop`.
@@ -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`.