drbx 2.0.0.dev0__py3-none-any.whl
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.
- drbx/__init__.py +20 -0
- drbx/__main__.py +5 -0
- drbx/cli.py +586 -0
- drbx/config/__init__.py +20 -0
- drbx/config/boutinp.py +556 -0
- drbx/config/model.py +34 -0
- drbx/config/normalization.py +66 -0
- drbx/data/atomic_rates/__init__.py +1 -0
- drbx/data/atomic_rates/iz_AMJUEL_H.x_2.1.5.json +213 -0
- drbx/data/atomic_rates/iz_AMJUEL_H.x_2.3.9a.json +213 -0
- drbx/data/atomic_rates/rec_AMJUEL_H.x_2.1.8.json +213 -0
- drbx/data/atomic_rates/rec_AMJUEL_H.x_2.3.13a.json +213 -0
- drbx/geometry/__init__.py +207 -0
- drbx/geometry/embedding.py +56 -0
- drbx/geometry/essos_import.py +1385 -0
- drbx/geometry/fci_geometry.py +4622 -0
- drbx/geometry/fci_maps.py +85 -0
- drbx/geometry/island_divertor.py +291 -0
- drbx/geometry/metric_tensor.py +99 -0
- drbx/geometry/open_slab.py +150 -0
- drbx/geometry/rotating_ellipse.py +253 -0
- drbx/geometry/shifted_torus.py +225 -0
- drbx/geometry/stellarator.py +287 -0
- drbx/geometry/vmec_extender_import.py +499 -0
- drbx/geometry/vmec_jax_import.py +306 -0
- drbx/linear/__init__.py +37 -0
- drbx/linear/dispersion.py +138 -0
- drbx/linear/eigen.py +91 -0
- drbx/native/__init__.py +224 -0
- drbx/native/array_backend.py +64 -0
- drbx/native/deck_runner.py +779 -0
- drbx/native/electromagnetic.py +250 -0
- drbx/native/expression.py +173 -0
- drbx/native/fci.py +295 -0
- drbx/native/fci_2_field_rhs.py +182 -0
- drbx/native/fci_4_field_rhs.py +1267 -0
- drbx/native/fci_boundaries.py +2494 -0
- drbx/native/fci_differentiable_case.py +304 -0
- drbx/native/fci_drb_EB_rhs.py +1243 -0
- drbx/native/fci_drb_rhs.py +190 -0
- drbx/native/fci_halo.py +1575 -0
- drbx/native/fci_helpers.py +350 -0
- drbx/native/fci_model.py +294 -0
- drbx/native/fci_neutral.py +139 -0
- drbx/native/fci_operators.py +4081 -0
- drbx/native/fci_sharding.py +597 -0
- drbx/native/fci_sheath_recycling.py +206 -0
- drbx/native/fci_time_integrator.py +96 -0
- drbx/native/fci_vorticity.py +198 -0
- drbx/native/fluid_1d.py +330 -0
- drbx/native/hasegawa_wakatani.py +196 -0
- drbx/native/limiters.py +57 -0
- drbx/native/mesh.py +238 -0
- drbx/native/metrics.py +234 -0
- drbx/native/neutrals/__init__.py +58 -0
- drbx/native/neutrals/atomic_rates.py +134 -0
- drbx/native/neutrals/detachment_sol_model.py +221 -0
- drbx/native/neutrals/reactions.py +164 -0
- drbx/native/neutrals/recycling_sol_model.py +197 -0
- drbx/native/sol_flux_tube.py +133 -0
- drbx/native/stellarator_turbulence.py +343 -0
- drbx/native/transport.py +134 -0
- drbx/native/units.py +32 -0
- drbx/native/vorticity.py +252 -0
- drbx/runtime/__init__.py +53 -0
- drbx/runtime/artifacts.py +161 -0
- drbx/runtime/memory.py +144 -0
- drbx/runtime/output.py +374 -0
- drbx/runtime/paths.py +9 -0
- drbx/runtime/performance.py +161 -0
- drbx/runtime/run_config.py +184 -0
- drbx/runtime/scheduler.py +99 -0
- drbx/runtime/state.py +40 -0
- drbx/validation/__init__.py +424 -0
- drbx/validation/autodiff_diffusion.py +329 -0
- drbx/validation/autodiff_diffusion_uncertainty.py +235 -0
- drbx/validation/diverted_tokamak_movie.py +558 -0
- drbx/validation/essos_fieldline_import_campaign.py +181 -0
- drbx/validation/essos_imported_artifact_audit.py +535 -0
- drbx/validation/essos_imported_drb_movie_campaign.py +2826 -0
- drbx/validation/essos_imported_fci_campaign.py +5241 -0
- drbx/validation/essos_imported_pytree_campaign.py +406 -0
- drbx/validation/essos_vmec_closed_field_campaign.py +314 -0
- drbx/validation/essos_vmec_closed_field_transient_campaign.py +629 -0
- drbx/validation/essos_vmec_fieldline_surface_campaign.py +620 -0
- drbx/validation/fluid_1d_mms_convergence.py +250 -0
- drbx/validation/geometry_lineouts.py +136 -0
- drbx/validation/geometry_slices.py +178 -0
- drbx/validation/publication_plotting.py +91 -0
- drbx/validation/stellarator_drb_pytree_campaign.py +621 -0
- drbx/validation/stellarator_fci_geometry_campaign.py +200 -0
- drbx/validation/stellarator_fci_operator_campaign.py +304 -0
- drbx/validation/stellarator_fci_suite_campaign.py +264 -0
- drbx/validation/stellarator_metric_mms_campaign.py +289 -0
- drbx/validation/stellarator_neutral_physics_campaign.py +255 -0
- drbx/validation/stellarator_sheath_recycling_campaign.py +331 -0
- drbx/validation/stellarator_sol_showcase.py +628 -0
- drbx/validation/stellarator_vorticity_campaign.py +304 -0
- drbx/validation/vmec_extender_edge_field_campaign.py +260 -0
- drbx/validation/vmec_extender_sol_smoke_campaign.py +365 -0
- drbx-2.0.0.dev0.dist-info/METADATA +380 -0
- drbx-2.0.0.dev0.dist-info/RECORD +106 -0
- drbx-2.0.0.dev0.dist-info/WHEEL +5 -0
- drbx-2.0.0.dev0.dist-info/entry_points.txt +2 -0
- drbx-2.0.0.dev0.dist-info/licenses/LICENSE +21 -0
- drbx-2.0.0.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Differentiable drift-reduced two-field FCI rollout on the shifted torus.
|
|
2
|
+
|
|
3
|
+
Reusable pieces behind the Phase 6 non-axisymmetric differentiability flagship
|
|
4
|
+
(``examples/stellarator/fci_differentiable.py``) and its gate
|
|
5
|
+
(``tests/test_fci_differentiable.py``):
|
|
6
|
+
|
|
7
|
+
- :func:`build_context` -- shifted-torus geometry plus the fixed-wall FCI
|
|
8
|
+
operator scaffold, bundled as a :class:`DemoContext`;
|
|
9
|
+
- :func:`seeded_initial_state` -- smooth multi-mode seed whose fluctuation
|
|
10
|
+
vanishes at the radial walls; the amplitude is the differentiation knob;
|
|
11
|
+
- :func:`single_rhs` -- one drift-reduced two-field FCI RHS evaluation;
|
|
12
|
+
- :func:`rollout` / :func:`evolved_density_variance` -- the differentiable
|
|
13
|
+
multi-step RK4 rollout (via ``jax.lax.scan``) and the scalar objective;
|
|
14
|
+
- :func:`differentiability_report` -- ``jax.grad`` of the evolved density
|
|
15
|
+
variance vs a central finite difference, with finiteness diagnostics;
|
|
16
|
+
- :func:`single_rhs_grad_and_fd` -- the cheaper single-RHS gradient witness.
|
|
17
|
+
|
|
18
|
+
Everything is pure JAX and ``jit``/``grad``-transparent; no files are written
|
|
19
|
+
here. Plotting and JSON summaries live in the example script.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from functools import partial
|
|
25
|
+
from typing import NamedTuple
|
|
26
|
+
|
|
27
|
+
import jax
|
|
28
|
+
import jax.numpy as jnp
|
|
29
|
+
import numpy as np
|
|
30
|
+
|
|
31
|
+
from ..geometry import (
|
|
32
|
+
RegularFaceGeometry3D,
|
|
33
|
+
build_curvature_coefficients,
|
|
34
|
+
build_shifted_torus_geometry,
|
|
35
|
+
logical_grid_from_axis_vectors,
|
|
36
|
+
)
|
|
37
|
+
from .fci_2_field_rhs import (
|
|
38
|
+
Fci2FieldRhsParameters,
|
|
39
|
+
Fci2FieldState,
|
|
40
|
+
compute_2field_rhs,
|
|
41
|
+
)
|
|
42
|
+
from .fci_boundaries import (
|
|
43
|
+
BC_DIRICHLET,
|
|
44
|
+
BoundaryFaceBC3D,
|
|
45
|
+
CutWallBC3D,
|
|
46
|
+
CutWallGeometry3D,
|
|
47
|
+
)
|
|
48
|
+
from .fci_time_integrator import rk4_step
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"DemoContext",
|
|
52
|
+
"build_context",
|
|
53
|
+
"density_variance",
|
|
54
|
+
"differentiability_report",
|
|
55
|
+
"evolved_density_variance",
|
|
56
|
+
"rollout",
|
|
57
|
+
"seeded_initial_state",
|
|
58
|
+
"single_rhs",
|
|
59
|
+
"single_rhs_grad_and_fd",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class DemoContext(NamedTuple):
|
|
64
|
+
"""Everything the differentiable rollout needs, built once per resolution."""
|
|
65
|
+
|
|
66
|
+
geometry: object
|
|
67
|
+
curvature_coefficients: jax.Array
|
|
68
|
+
density_face_bc: BoundaryFaceBC3D
|
|
69
|
+
phi_face_bc: BoundaryFaceBC3D
|
|
70
|
+
v_parallel_face_bc: BoundaryFaceBC3D
|
|
71
|
+
parameters: Fci2FieldRhsParameters
|
|
72
|
+
x_min: float
|
|
73
|
+
x_max: float
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _fixed_radial_dirichlet_face_bc(geometry, boundary_value: float) -> BoundaryFaceBC3D:
|
|
77
|
+
"""A fixed Dirichlet face BC at the two radial (x) walls; periodic elsewhere.
|
|
78
|
+
|
|
79
|
+
``boundary_value`` is held constant in time, so this is a simple fixed wall,
|
|
80
|
+
not an exact-solution boundary. For the density the wall value is the
|
|
81
|
+
background (1.0); for the fluctuation quantities phi and v_parallel it is 0
|
|
82
|
+
(zero-Dirichlet on the fluctuation).
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
face = RegularFaceGeometry3D.unit(geometry)
|
|
86
|
+
return BoundaryFaceBC3D(
|
|
87
|
+
kind_x=jnp.zeros_like(face.x_area, dtype=jnp.int32).at[0].set(BC_DIRICHLET).at[-1].set(BC_DIRICHLET),
|
|
88
|
+
kind_y=jnp.zeros_like(face.y_area, dtype=jnp.int32),
|
|
89
|
+
kind_z=jnp.zeros_like(face.z_area, dtype=jnp.int32),
|
|
90
|
+
value_x=jnp.full_like(face.x_area, 0.0).at[0].set(boundary_value).at[-1].set(boundary_value),
|
|
91
|
+
value_y=jnp.zeros_like(face.y_area, dtype=jnp.float64),
|
|
92
|
+
value_z=jnp.zeros_like(face.z_area, dtype=jnp.float64),
|
|
93
|
+
mask_x=jnp.zeros_like(face.x_open_mask, dtype=bool).at[0].set(True).at[-1].set(True),
|
|
94
|
+
mask_y=jnp.zeros_like(face.y_open_mask, dtype=bool),
|
|
95
|
+
mask_z=jnp.zeros_like(face.z_open_mask, dtype=bool),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def build_context(
|
|
100
|
+
shape: tuple[int, int, int] = (16, 16, 8),
|
|
101
|
+
*,
|
|
102
|
+
sigma: float = 0.6,
|
|
103
|
+
rho_star: float = 1.0,
|
|
104
|
+
x_min: float = 0.15,
|
|
105
|
+
x_max: float = 1.0,
|
|
106
|
+
r0: float = 3.0,
|
|
107
|
+
alpha_value: float = 0.25,
|
|
108
|
+
iota: float = 1.1,
|
|
109
|
+
c_phi: float = 3.0,
|
|
110
|
+
) -> DemoContext:
|
|
111
|
+
"""Build the shifted-torus geometry and the fixed-wall FCI operator scaffold."""
|
|
112
|
+
|
|
113
|
+
geometry = build_shifted_torus_geometry(
|
|
114
|
+
shape,
|
|
115
|
+
x_min=x_min,
|
|
116
|
+
x_max=x_max,
|
|
117
|
+
r0=r0,
|
|
118
|
+
alpha_value=alpha_value,
|
|
119
|
+
iota=iota,
|
|
120
|
+
c_phi=c_phi,
|
|
121
|
+
sigma=sigma,
|
|
122
|
+
construct_fci_maps=False,
|
|
123
|
+
)
|
|
124
|
+
curvature_coefficients = build_curvature_coefficients(geometry, periodic_axes=(False, True, True))
|
|
125
|
+
return DemoContext(
|
|
126
|
+
geometry=geometry,
|
|
127
|
+
curvature_coefficients=curvature_coefficients,
|
|
128
|
+
density_face_bc=_fixed_radial_dirichlet_face_bc(geometry, 1.0),
|
|
129
|
+
phi_face_bc=_fixed_radial_dirichlet_face_bc(geometry, 0.0),
|
|
130
|
+
v_parallel_face_bc=_fixed_radial_dirichlet_face_bc(geometry, 0.0),
|
|
131
|
+
parameters=Fci2FieldRhsParameters(rho_star=rho_star),
|
|
132
|
+
x_min=float(x_min),
|
|
133
|
+
x_max=float(x_max),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def seeded_initial_state(
|
|
138
|
+
ctx: DemoContext,
|
|
139
|
+
amp: jax.Array,
|
|
140
|
+
*,
|
|
141
|
+
m1: int = 2,
|
|
142
|
+
n1: int = 1,
|
|
143
|
+
m2: int = 3,
|
|
144
|
+
n2: int = 2,
|
|
145
|
+
) -> Fci2FieldState:
|
|
146
|
+
"""Smooth seeded initial state whose fluctuation vanishes at the radial walls.
|
|
147
|
+
|
|
148
|
+
``density = background * exp(amp * perturbation)`` keeps the density strictly
|
|
149
|
+
positive (the model derives ``phi = log(density / background)`` internally),
|
|
150
|
+
and ``perturbation`` carries a radial envelope ``sin(pi * x_norm)`` so the
|
|
151
|
+
fluctuation is zero at both radial boundaries, consistent with the fixed
|
|
152
|
+
zero-Dirichlet fluctuation wall. ``(m1, n1)`` and ``(m2, n2)`` are the seeded
|
|
153
|
+
(poloidal, toroidal) mode numbers.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
logical_grid = logical_grid_from_axis_vectors(*ctx.geometry.grid.logical_axis_vectors)
|
|
157
|
+
x = logical_grid[..., 0]
|
|
158
|
+
theta = logical_grid[..., 1]
|
|
159
|
+
zeta = logical_grid[..., 2]
|
|
160
|
+
x_norm = (x - ctx.x_min) / (ctx.x_max - ctx.x_min)
|
|
161
|
+
envelope = jnp.sin(jnp.pi * x_norm)
|
|
162
|
+
perturbation = envelope * (
|
|
163
|
+
jnp.cos(m1 * theta) * jnp.sin(n1 * zeta)
|
|
164
|
+
+ 0.5 * jnp.sin(m2 * theta) * jnp.cos(n2 * zeta)
|
|
165
|
+
)
|
|
166
|
+
background = jnp.ones(ctx.geometry.shape, dtype=jnp.float64)
|
|
167
|
+
density = background * jnp.exp(amp * perturbation)
|
|
168
|
+
v_parallel = amp * envelope * jnp.sin(m1 * theta) * jnp.cos(n1 * zeta)
|
|
169
|
+
return Fci2FieldState(density=density, v_parallel=v_parallel, density_background=background)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _clamp_radial_boundaries(ctx: DemoContext, state: Fci2FieldState) -> Fci2FieldState:
|
|
173
|
+
"""Hold the radial boundary rows at the fixed wall values (density=bg, v=0)."""
|
|
174
|
+
|
|
175
|
+
del ctx
|
|
176
|
+
density = state.density.at[0, :, :].set(1.0).at[-1, :, :].set(1.0)
|
|
177
|
+
v_parallel = state.v_parallel.at[0, :, :].set(0.0).at[-1, :, :].set(0.0)
|
|
178
|
+
return Fci2FieldState(
|
|
179
|
+
density=density,
|
|
180
|
+
v_parallel=v_parallel,
|
|
181
|
+
density_background=state.density_background,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def single_rhs(ctx: DemoContext, state: Fci2FieldState) -> Fci2FieldState:
|
|
186
|
+
"""One drift-reduced two-field FCI RHS evaluation on the shifted torus."""
|
|
187
|
+
|
|
188
|
+
state = _clamp_radial_boundaries(ctx, state)
|
|
189
|
+
result, _timings = compute_2field_rhs(
|
|
190
|
+
state,
|
|
191
|
+
geometry=ctx.geometry,
|
|
192
|
+
parameters=ctx.parameters,
|
|
193
|
+
curvature_coefficients=ctx.curvature_coefficients,
|
|
194
|
+
periodic_axes=(False, True, True),
|
|
195
|
+
density_face_bc=ctx.density_face_bc,
|
|
196
|
+
phi_face_bc=ctx.phi_face_bc,
|
|
197
|
+
v_parallel_face_bc=ctx.v_parallel_face_bc,
|
|
198
|
+
density_cut_wall_geometry=CutWallGeometry3D.empty(),
|
|
199
|
+
density_cut_wall_bc=CutWallBC3D.empty(),
|
|
200
|
+
phi_cut_wall_geometry=CutWallGeometry3D.empty(),
|
|
201
|
+
phi_cut_wall_bc=CutWallBC3D.empty(),
|
|
202
|
+
v_parallel_cut_wall_geometry=CutWallGeometry3D.empty(),
|
|
203
|
+
v_parallel_cut_wall_bc=CutWallBC3D.empty(),
|
|
204
|
+
)
|
|
205
|
+
return result.rhs
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def rollout(ctx: DemoContext, amp: jax.Array, *, n_steps: int, dt: float) -> Fci2FieldState:
|
|
209
|
+
"""Advance the seeded free state ``n_steps`` RK4 steps (differentiable, via scan)."""
|
|
210
|
+
|
|
211
|
+
initial_state = _clamp_radial_boundaries(ctx, seeded_initial_state(ctx, amp))
|
|
212
|
+
|
|
213
|
+
def _rhs_fn(current_state, _stage_time, _carry):
|
|
214
|
+
return single_rhs(ctx, current_state), None, jnp.asarray(0.0)
|
|
215
|
+
|
|
216
|
+
def _body(state, _):
|
|
217
|
+
step = rk4_step(state, time=0.0, timestep=dt, rhs_fn=_rhs_fn, carry=None)
|
|
218
|
+
return _clamp_radial_boundaries(ctx, step.state), None
|
|
219
|
+
|
|
220
|
+
final_state, _ = jax.lax.scan(_body, initial_state, None, length=int(n_steps))
|
|
221
|
+
return final_state
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def density_variance(state: Fci2FieldState) -> jax.Array:
|
|
225
|
+
"""Scalar diagnostic: variance of the density over interior (non-wall) cells."""
|
|
226
|
+
|
|
227
|
+
interior = state.density[1:-1, :, :]
|
|
228
|
+
return jnp.mean((interior - jnp.mean(interior)) ** 2)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def evolved_density_variance(ctx: DemoContext, amp: jax.Array, *, n_steps: int, dt: float) -> jax.Array:
|
|
232
|
+
"""The scalar objective we differentiate: variance of the evolved density."""
|
|
233
|
+
|
|
234
|
+
return density_variance(rollout(ctx, amp, n_steps=n_steps, dt=dt))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def differentiability_report(
|
|
238
|
+
ctx: DemoContext,
|
|
239
|
+
*,
|
|
240
|
+
amp0: float = 0.1,
|
|
241
|
+
n_steps: int = 24,
|
|
242
|
+
dt: float = 1.0e-3,
|
|
243
|
+
fd_step: float = 1.0e-5,
|
|
244
|
+
) -> dict:
|
|
245
|
+
"""Roll out the free run and compare autodiff grad to a central FD (wrt amp0).
|
|
246
|
+
|
|
247
|
+
Returns the evolved state and the gradient diagnostics. The objective is
|
|
248
|
+
JIT-compiled once so the finite-difference samples reuse the compiled rollout.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
objective = partial(evolved_density_variance, ctx, n_steps=n_steps, dt=dt)
|
|
252
|
+
objective_jit = jax.jit(objective)
|
|
253
|
+
grad_jit = jax.jit(jax.grad(objective))
|
|
254
|
+
|
|
255
|
+
amp0 = float(amp0)
|
|
256
|
+
grad_value = float(grad_jit(amp0))
|
|
257
|
+
plus = float(objective_jit(amp0 + fd_step))
|
|
258
|
+
minus = float(objective_jit(amp0 - fd_step))
|
|
259
|
+
fd_value = (plus - minus) / (2.0 * fd_step)
|
|
260
|
+
rel_error = abs(grad_value - fd_value) / max(abs(fd_value), 1.0e-30)
|
|
261
|
+
|
|
262
|
+
final_state = rollout(ctx, amp0, n_steps=n_steps, dt=dt)
|
|
263
|
+
density = np.asarray(final_state.density, dtype=np.float64)
|
|
264
|
+
v_parallel = np.asarray(final_state.v_parallel, dtype=np.float64)
|
|
265
|
+
finite = bool(np.all(np.isfinite(density)) and np.all(np.isfinite(v_parallel)))
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
"grad": grad_value,
|
|
269
|
+
"fd": fd_value,
|
|
270
|
+
"rel_error": rel_error,
|
|
271
|
+
"fd_step": float(fd_step),
|
|
272
|
+
"amp0": amp0,
|
|
273
|
+
"n_steps": int(n_steps),
|
|
274
|
+
"dt": float(dt),
|
|
275
|
+
"objective_value": float(objective_jit(amp0)),
|
|
276
|
+
"finite": finite,
|
|
277
|
+
"density_max": float(np.max(np.abs(density))),
|
|
278
|
+
"v_parallel_max": float(np.max(np.abs(v_parallel))),
|
|
279
|
+
"final_state": final_state,
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def single_rhs_grad_and_fd(
|
|
284
|
+
ctx: DemoContext,
|
|
285
|
+
*,
|
|
286
|
+
amp0: float = 0.1,
|
|
287
|
+
fd_step: float = 1.0e-5,
|
|
288
|
+
) -> dict:
|
|
289
|
+
"""Secondary cheaper witness: differentiate a SINGLE RHS evaluation wrt amp.
|
|
290
|
+
|
|
291
|
+
A fast cross-check that the FCI RHS itself is differentiable, independent of
|
|
292
|
+
the RK4 rollout.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
def objective(amp: jax.Array) -> jax.Array:
|
|
296
|
+
return density_variance(single_rhs(ctx, seeded_initial_state(ctx, amp)))
|
|
297
|
+
|
|
298
|
+
objective_jit = jax.jit(objective)
|
|
299
|
+
grad_value = float(jax.jit(jax.grad(objective))(float(amp0)))
|
|
300
|
+
plus = float(objective_jit(float(amp0) + fd_step))
|
|
301
|
+
minus = float(objective_jit(float(amp0) - fd_step))
|
|
302
|
+
fd_value = (plus - minus) / (2.0 * fd_step)
|
|
303
|
+
rel_error = abs(grad_value - fd_value) / max(abs(fd_value), 1.0e-30)
|
|
304
|
+
return {"grad": grad_value, "fd": fd_value, "rel_error": rel_error}
|