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.
Files changed (106) hide show
  1. drbx/__init__.py +20 -0
  2. drbx/__main__.py +5 -0
  3. drbx/cli.py +586 -0
  4. drbx/config/__init__.py +20 -0
  5. drbx/config/boutinp.py +556 -0
  6. drbx/config/model.py +34 -0
  7. drbx/config/normalization.py +66 -0
  8. drbx/data/atomic_rates/__init__.py +1 -0
  9. drbx/data/atomic_rates/iz_AMJUEL_H.x_2.1.5.json +213 -0
  10. drbx/data/atomic_rates/iz_AMJUEL_H.x_2.3.9a.json +213 -0
  11. drbx/data/atomic_rates/rec_AMJUEL_H.x_2.1.8.json +213 -0
  12. drbx/data/atomic_rates/rec_AMJUEL_H.x_2.3.13a.json +213 -0
  13. drbx/geometry/__init__.py +207 -0
  14. drbx/geometry/embedding.py +56 -0
  15. drbx/geometry/essos_import.py +1385 -0
  16. drbx/geometry/fci_geometry.py +4622 -0
  17. drbx/geometry/fci_maps.py +85 -0
  18. drbx/geometry/island_divertor.py +291 -0
  19. drbx/geometry/metric_tensor.py +99 -0
  20. drbx/geometry/open_slab.py +150 -0
  21. drbx/geometry/rotating_ellipse.py +253 -0
  22. drbx/geometry/shifted_torus.py +225 -0
  23. drbx/geometry/stellarator.py +287 -0
  24. drbx/geometry/vmec_extender_import.py +499 -0
  25. drbx/geometry/vmec_jax_import.py +306 -0
  26. drbx/linear/__init__.py +37 -0
  27. drbx/linear/dispersion.py +138 -0
  28. drbx/linear/eigen.py +91 -0
  29. drbx/native/__init__.py +224 -0
  30. drbx/native/array_backend.py +64 -0
  31. drbx/native/deck_runner.py +779 -0
  32. drbx/native/electromagnetic.py +250 -0
  33. drbx/native/expression.py +173 -0
  34. drbx/native/fci.py +295 -0
  35. drbx/native/fci_2_field_rhs.py +182 -0
  36. drbx/native/fci_4_field_rhs.py +1267 -0
  37. drbx/native/fci_boundaries.py +2494 -0
  38. drbx/native/fci_differentiable_case.py +304 -0
  39. drbx/native/fci_drb_EB_rhs.py +1243 -0
  40. drbx/native/fci_drb_rhs.py +190 -0
  41. drbx/native/fci_halo.py +1575 -0
  42. drbx/native/fci_helpers.py +350 -0
  43. drbx/native/fci_model.py +294 -0
  44. drbx/native/fci_neutral.py +139 -0
  45. drbx/native/fci_operators.py +4081 -0
  46. drbx/native/fci_sharding.py +597 -0
  47. drbx/native/fci_sheath_recycling.py +206 -0
  48. drbx/native/fci_time_integrator.py +96 -0
  49. drbx/native/fci_vorticity.py +198 -0
  50. drbx/native/fluid_1d.py +330 -0
  51. drbx/native/hasegawa_wakatani.py +196 -0
  52. drbx/native/limiters.py +57 -0
  53. drbx/native/mesh.py +238 -0
  54. drbx/native/metrics.py +234 -0
  55. drbx/native/neutrals/__init__.py +58 -0
  56. drbx/native/neutrals/atomic_rates.py +134 -0
  57. drbx/native/neutrals/detachment_sol_model.py +221 -0
  58. drbx/native/neutrals/reactions.py +164 -0
  59. drbx/native/neutrals/recycling_sol_model.py +197 -0
  60. drbx/native/sol_flux_tube.py +133 -0
  61. drbx/native/stellarator_turbulence.py +343 -0
  62. drbx/native/transport.py +134 -0
  63. drbx/native/units.py +32 -0
  64. drbx/native/vorticity.py +252 -0
  65. drbx/runtime/__init__.py +53 -0
  66. drbx/runtime/artifacts.py +161 -0
  67. drbx/runtime/memory.py +144 -0
  68. drbx/runtime/output.py +374 -0
  69. drbx/runtime/paths.py +9 -0
  70. drbx/runtime/performance.py +161 -0
  71. drbx/runtime/run_config.py +184 -0
  72. drbx/runtime/scheduler.py +99 -0
  73. drbx/runtime/state.py +40 -0
  74. drbx/validation/__init__.py +424 -0
  75. drbx/validation/autodiff_diffusion.py +329 -0
  76. drbx/validation/autodiff_diffusion_uncertainty.py +235 -0
  77. drbx/validation/diverted_tokamak_movie.py +558 -0
  78. drbx/validation/essos_fieldline_import_campaign.py +181 -0
  79. drbx/validation/essos_imported_artifact_audit.py +535 -0
  80. drbx/validation/essos_imported_drb_movie_campaign.py +2826 -0
  81. drbx/validation/essos_imported_fci_campaign.py +5241 -0
  82. drbx/validation/essos_imported_pytree_campaign.py +406 -0
  83. drbx/validation/essos_vmec_closed_field_campaign.py +314 -0
  84. drbx/validation/essos_vmec_closed_field_transient_campaign.py +629 -0
  85. drbx/validation/essos_vmec_fieldline_surface_campaign.py +620 -0
  86. drbx/validation/fluid_1d_mms_convergence.py +250 -0
  87. drbx/validation/geometry_lineouts.py +136 -0
  88. drbx/validation/geometry_slices.py +178 -0
  89. drbx/validation/publication_plotting.py +91 -0
  90. drbx/validation/stellarator_drb_pytree_campaign.py +621 -0
  91. drbx/validation/stellarator_fci_geometry_campaign.py +200 -0
  92. drbx/validation/stellarator_fci_operator_campaign.py +304 -0
  93. drbx/validation/stellarator_fci_suite_campaign.py +264 -0
  94. drbx/validation/stellarator_metric_mms_campaign.py +289 -0
  95. drbx/validation/stellarator_neutral_physics_campaign.py +255 -0
  96. drbx/validation/stellarator_sheath_recycling_campaign.py +331 -0
  97. drbx/validation/stellarator_sol_showcase.py +628 -0
  98. drbx/validation/stellarator_vorticity_campaign.py +304 -0
  99. drbx/validation/vmec_extender_edge_field_campaign.py +260 -0
  100. drbx/validation/vmec_extender_sol_smoke_campaign.py +365 -0
  101. drbx-2.0.0.dev0.dist-info/METADATA +380 -0
  102. drbx-2.0.0.dev0.dist-info/RECORD +106 -0
  103. drbx-2.0.0.dev0.dist-info/WHEEL +5 -0
  104. drbx-2.0.0.dev0.dist-info/entry_points.txt +2 -0
  105. drbx-2.0.0.dev0.dist-info/licenses/LICENSE +21 -0
  106. drbx-2.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1267 @@
1
+ from __future__ import annotations
2
+
3
+ import time as time_module
4
+ from dataclasses import dataclass
5
+
6
+ import jax
7
+ import jax.numpy as jnp
8
+
9
+ from ..geometry import (
10
+ ConservativeStencilBuilder,
11
+ FciGeometry3D,
12
+ LocalStencilBuilder,
13
+ build_conservative_stencil_from_field,
14
+ build_local_stencil_from_field,
15
+ )
16
+ from .fci_model import FciModelState
17
+ from .fci_boundaries import (
18
+ BoundaryFaceBC3D,
19
+ CutWallBC3D,
20
+ CutWallGeometry3D,
21
+ FourFieldBoundaryConditions,
22
+ )
23
+ from .fci_operators import (
24
+ PerpLaplacianInverseSolver,
25
+ PerpLaplacianMgHierarchy,
26
+ curvature_op,
27
+ grad_parallel_op_direct,
28
+ perp_laplacian_conservative_op,
29
+ poisson_bracket_op,
30
+ )
31
+
32
+
33
+ @jax.tree_util.register_pytree_node_class
34
+ @dataclass(frozen=True)
35
+ class Fci4FieldState(FciModelState):
36
+ density: jax.Array
37
+ omega: jax.Array
38
+ v_ion_parallel: jax.Array
39
+ v_electron_parallel: jax.Array
40
+
41
+
42
+ @jax.tree_util.register_pytree_node_class
43
+ @dataclass(frozen=True)
44
+ class Fci4FieldRhsParameters:
45
+ """Physical and inversion parameters for the four-field model."""
46
+
47
+ rho_star: float = 1.0
48
+ Te: float = 1.0
49
+ mi_over_me: float = 1836.0
50
+ phi_inversion_tol: float = 1.0e-6
51
+ phi_inversion_maxiter: int = 50
52
+ phi_inversion_restart: int = 50
53
+ phi_inversion_regularization: float = 0.0
54
+
55
+ def tree_flatten(self):
56
+ return (
57
+ (
58
+ self.rho_star,
59
+ self.Te,
60
+ self.mi_over_me,
61
+ self.phi_inversion_tol,
62
+ self.phi_inversion_maxiter,
63
+ self.phi_inversion_restart,
64
+ self.phi_inversion_regularization,
65
+ ),
66
+ None,
67
+ )
68
+
69
+ @classmethod
70
+ def tree_unflatten(cls, _aux_data, children):
71
+ (
72
+ rho_star,
73
+ Te,
74
+ mi_over_me,
75
+ phi_inversion_tol,
76
+ phi_inversion_maxiter,
77
+ phi_inversion_restart,
78
+ phi_inversion_regularization,
79
+ ) = children
80
+ return cls(
81
+ rho_star=rho_star,
82
+ Te=Te,
83
+ mi_over_me=mi_over_me,
84
+ phi_inversion_tol=phi_inversion_tol,
85
+ phi_inversion_maxiter=phi_inversion_maxiter,
86
+ phi_inversion_restart=phi_inversion_restart,
87
+ phi_inversion_regularization=phi_inversion_regularization,
88
+ )
89
+
90
+
91
+ @jax.tree_util.register_pytree_node_class
92
+ @dataclass(frozen=True)
93
+ class Fci4FieldFreeDecayParameters:
94
+ """Four-field free-decay parameters, including perpendicular diffusion."""
95
+
96
+ rho_star: float = 1.0
97
+ Te: float = 1.0
98
+ mi_over_me: float = 1836.0
99
+ phi_inversion_tol: float = 1.0e-6
100
+ phi_inversion_maxiter: int = 50
101
+ phi_inversion_restart: int = 50
102
+ phi_inversion_regularization: float = 0.0
103
+ density_perp_diffusion: float = 1.0e-2
104
+ omega_perp_diffusion: float = 1.0e-2
105
+ v_ion_parallel_perp_diffusion: float = 1.0e-2
106
+ v_electron_parallel_perp_diffusion: float = 1.0e-2
107
+
108
+ def rhs_parameters(self) -> Fci4FieldRhsParameters:
109
+ return Fci4FieldRhsParameters(
110
+ rho_star=self.rho_star,
111
+ Te=self.Te,
112
+ mi_over_me=self.mi_over_me,
113
+ phi_inversion_tol=self.phi_inversion_tol,
114
+ phi_inversion_maxiter=self.phi_inversion_maxiter,
115
+ phi_inversion_restart=self.phi_inversion_restart,
116
+ phi_inversion_regularization=self.phi_inversion_regularization,
117
+ )
118
+
119
+ def tree_flatten(self):
120
+ return (
121
+ (
122
+ self.rho_star,
123
+ self.Te,
124
+ self.mi_over_me,
125
+ self.phi_inversion_tol,
126
+ self.phi_inversion_maxiter,
127
+ self.phi_inversion_restart,
128
+ self.phi_inversion_regularization,
129
+ self.density_perp_diffusion,
130
+ self.omega_perp_diffusion,
131
+ self.v_ion_parallel_perp_diffusion,
132
+ self.v_electron_parallel_perp_diffusion,
133
+ ),
134
+ None,
135
+ )
136
+
137
+ @classmethod
138
+ def tree_unflatten(cls, _aux_data, children):
139
+ (
140
+ rho_star,
141
+ Te,
142
+ mi_over_me,
143
+ phi_inversion_tol,
144
+ phi_inversion_maxiter,
145
+ phi_inversion_restart,
146
+ phi_inversion_regularization,
147
+ density_perp_diffusion,
148
+ omega_perp_diffusion,
149
+ v_ion_parallel_perp_diffusion,
150
+ v_electron_parallel_perp_diffusion,
151
+ ) = children
152
+ return cls(
153
+ rho_star=rho_star,
154
+ Te=Te,
155
+ mi_over_me=mi_over_me,
156
+ phi_inversion_tol=phi_inversion_tol,
157
+ phi_inversion_maxiter=phi_inversion_maxiter,
158
+ phi_inversion_restart=phi_inversion_restart,
159
+ phi_inversion_regularization=phi_inversion_regularization,
160
+ density_perp_diffusion=density_perp_diffusion,
161
+ omega_perp_diffusion=omega_perp_diffusion,
162
+ v_ion_parallel_perp_diffusion=v_ion_parallel_perp_diffusion,
163
+ v_electron_parallel_perp_diffusion=v_electron_parallel_perp_diffusion,
164
+ )
165
+
166
+
167
+ @jax.tree_util.register_pytree_node_class
168
+ @dataclass(frozen=True)
169
+ class Fci4FieldBlobParameters:
170
+ """Four-field blob/interchange parameters."""
171
+
172
+ rho_star: float = 1.0
173
+ Te: float = 1.0
174
+ mi_over_me: float = 1836.0
175
+ phi_inversion_tol: float = 1.0e-6
176
+ phi_inversion_maxiter: int = 50
177
+ phi_inversion_restart: int = 50
178
+ phi_inversion_regularization: float = 0.0
179
+ density_perp_diffusion: float = 1.0e-2
180
+ omega_perp_diffusion: float = 1.0e-2
181
+ v_ion_parallel_perp_diffusion: float = 1.0e-2
182
+ v_electron_parallel_perp_diffusion: float = 1.0e-2
183
+
184
+ def rhs_parameters(self) -> Fci4FieldRhsParameters:
185
+ return Fci4FieldRhsParameters(
186
+ rho_star=self.rho_star,
187
+ Te=self.Te,
188
+ mi_over_me=self.mi_over_me,
189
+ phi_inversion_tol=self.phi_inversion_tol,
190
+ phi_inversion_maxiter=self.phi_inversion_maxiter,
191
+ phi_inversion_restart=self.phi_inversion_restart,
192
+ phi_inversion_regularization=self.phi_inversion_regularization,
193
+ )
194
+
195
+ def tree_flatten(self):
196
+ return (
197
+ (
198
+ self.rho_star,
199
+ self.Te,
200
+ self.mi_over_me,
201
+ self.phi_inversion_tol,
202
+ self.phi_inversion_maxiter,
203
+ self.phi_inversion_restart,
204
+ self.phi_inversion_regularization,
205
+ self.density_perp_diffusion,
206
+ self.omega_perp_diffusion,
207
+ self.v_ion_parallel_perp_diffusion,
208
+ self.v_electron_parallel_perp_diffusion,
209
+ ),
210
+ None,
211
+ )
212
+
213
+ @classmethod
214
+ def tree_unflatten(cls, _aux_data, children):
215
+ (
216
+ rho_star,
217
+ Te,
218
+ mi_over_me,
219
+ phi_inversion_tol,
220
+ phi_inversion_maxiter,
221
+ phi_inversion_restart,
222
+ phi_inversion_regularization,
223
+ density_perp_diffusion,
224
+ omega_perp_diffusion,
225
+ v_ion_parallel_perp_diffusion,
226
+ v_electron_parallel_perp_diffusion,
227
+ ) = children
228
+ return cls(
229
+ rho_star=rho_star,
230
+ Te=Te,
231
+ mi_over_me=mi_over_me,
232
+ phi_inversion_tol=phi_inversion_tol,
233
+ phi_inversion_maxiter=phi_inversion_maxiter,
234
+ phi_inversion_restart=phi_inversion_restart,
235
+ phi_inversion_regularization=phi_inversion_regularization,
236
+ density_perp_diffusion=density_perp_diffusion,
237
+ omega_perp_diffusion=omega_perp_diffusion,
238
+ v_ion_parallel_perp_diffusion=v_ion_parallel_perp_diffusion,
239
+ v_electron_parallel_perp_diffusion=v_electron_parallel_perp_diffusion,
240
+ )
241
+
242
+
243
+ @jax.tree_util.register_pytree_node_class
244
+ @dataclass(frozen=True)
245
+ class Fci4FieldRhsResult:
246
+ rhs: Fci4FieldState
247
+
248
+ def tree_flatten(self):
249
+ return ((self.rhs,), None)
250
+
251
+ @classmethod
252
+ def tree_unflatten(cls, _aux_data, children):
253
+ (rhs,) = children
254
+ return cls(rhs=rhs)
255
+
256
+
257
+ _FOUR_FIELD_FACE_BC_NAMES = (
258
+ "phi_face_bc",
259
+ "density_face_bc",
260
+ "omega_face_bc",
261
+ "v_ion_parallel_face_bc",
262
+ "v_electron_parallel_face_bc",
263
+ )
264
+
265
+
266
+ def _resolve_four_field_bcs(
267
+ boundary_conditions: FourFieldBoundaryConditions | None,
268
+ **explicit,
269
+ ) -> FourFieldBoundaryConditions:
270
+ """Merge the optional BC bundle with the legacy per-field BC kwargs.
271
+
272
+ Without a bundle the explicit kwargs are repackaged unchanged (the five
273
+ face BCs must then all be given, exactly as before the bundle existed).
274
+ With a bundle, passing any of the per-field BC kwargs as well is an error.
275
+ """
276
+
277
+ if boundary_conditions is None:
278
+ missing = [name for name in _FOUR_FIELD_FACE_BC_NAMES if explicit[name] is None]
279
+ if missing:
280
+ raise TypeError(
281
+ "missing face boundary conditions (pass them explicitly or via "
282
+ f"boundary_conditions=): {', '.join(missing)}"
283
+ )
284
+ return FourFieldBoundaryConditions(**explicit)
285
+ conflicts = [name for name, value in explicit.items() if value is not None]
286
+ if conflicts:
287
+ raise ValueError(
288
+ "boundary_conditions= was given, but these per-field BC kwargs were "
289
+ f"also passed explicitly: {', '.join(conflicts)}"
290
+ )
291
+ return boundary_conditions
292
+
293
+
294
+ def _cell_volume_weights(geometry: FciGeometry3D) -> jnp.ndarray:
295
+ return (
296
+ jnp.asarray(geometry.cell_metric.J, dtype=jnp.float64)
297
+ * jnp.asarray(geometry.spacing.dx, dtype=jnp.float64)
298
+ * jnp.asarray(geometry.spacing.dy, dtype=jnp.float64)
299
+ * jnp.asarray(geometry.spacing.dz, dtype=jnp.float64)
300
+ )
301
+
302
+
303
+ def _weighted_mean(field: jnp.ndarray, geometry: FciGeometry3D) -> jnp.ndarray:
304
+ weights = _cell_volume_weights(geometry)
305
+ return jnp.sum(weights * field) / jnp.maximum(jnp.sum(weights), 1.0e-30)
306
+
307
+
308
+ def _weighted_l2(field: jnp.ndarray, geometry: FciGeometry3D) -> jnp.ndarray:
309
+ weights = _cell_volume_weights(geometry)
310
+ return jnp.sqrt(jnp.sum(weights * field * field) / jnp.maximum(jnp.sum(weights), 1.0e-30))
311
+
312
+
313
+ def _assemble_4field_non_diffusive_rhs(
314
+ *,
315
+ density: jnp.ndarray,
316
+ omega: jnp.ndarray,
317
+ v_ion_parallel: jnp.ndarray,
318
+ v_electron_parallel: jnp.ndarray,
319
+ rho_star: jnp.ndarray,
320
+ te: jnp.ndarray,
321
+ mi_over_me: jnp.ndarray,
322
+ bmag: jnp.ndarray,
323
+ density_safe: jnp.ndarray,
324
+ geometry: FciGeometry3D,
325
+ curvature_coefficients: jnp.ndarray,
326
+ density_stencil: object,
327
+ omega_stencil: object,
328
+ phi_stencil: object,
329
+ v_ion_parallel_stencil: object,
330
+ v_electron_parallel_stencil: object,
331
+ ) -> tuple[Fci4FieldState, Fci4FieldState, Fci4FieldState]:
332
+ poisson_density = poisson_bracket_op(phi_stencil, density_stencil, geometry)
333
+ poisson_omega = poisson_bracket_op(phi_stencil, omega_stencil, geometry)
334
+ poisson_v_ion = poisson_bracket_op(phi_stencil, v_ion_parallel_stencil, geometry)
335
+ poisson_v_electron = poisson_bracket_op(phi_stencil, v_electron_parallel_stencil, geometry)
336
+
337
+ curvature_density = curvature_op(density_stencil, geometry, curvature_coefficients=curvature_coefficients)
338
+ curvature_phi = curvature_op(phi_stencil, geometry, curvature_coefficients=curvature_coefficients)
339
+ grad_parallel_density = grad_parallel_op_direct(density_stencil, geometry)
340
+ grad_parallel_phi = grad_parallel_op_direct(phi_stencil, geometry)
341
+ grad_parallel_v_ion = grad_parallel_op_direct(v_ion_parallel_stencil, geometry)
342
+ grad_parallel_v_electron = grad_parallel_op_direct(v_electron_parallel_stencil, geometry)
343
+
344
+ poisson_rhs = Fci4FieldState(
345
+ density=-(poisson_density / (rho_star * bmag)),
346
+ omega=-(poisson_omega / (rho_star * bmag)),
347
+ v_ion_parallel=-(poisson_v_ion / (rho_star * bmag)),
348
+ v_electron_parallel=-(poisson_v_electron / (rho_star * bmag)),
349
+ )
350
+ curvature_rhs = Fci4FieldState(
351
+ density=(2.0 * te / bmag) * curvature_density - (2.0 * density / bmag) * curvature_phi,
352
+ omega=(2.0 * bmag * te / density_safe) * curvature_density,
353
+ v_ion_parallel=jnp.zeros_like(density),
354
+ v_electron_parallel=jnp.zeros_like(density),
355
+ )
356
+ parallel_rhs = Fci4FieldState(
357
+ density=-density * grad_parallel_v_electron,
358
+ omega=(bmag * bmag / density_safe) * (grad_parallel_v_ion - grad_parallel_v_electron),
359
+ v_ion_parallel=-(te / density_safe) * grad_parallel_density,
360
+ v_electron_parallel=mi_over_me * grad_parallel_phi - mi_over_me * (te / density_safe) * grad_parallel_density,
361
+ )
362
+ return poisson_rhs, curvature_rhs, parallel_rhs
363
+
364
+
365
+ def _assemble_4field_diffusion_rhs(
366
+ *,
367
+ density: jnp.ndarray,
368
+ omega: jnp.ndarray,
369
+ v_ion_parallel: jnp.ndarray,
370
+ v_electron_parallel: jnp.ndarray,
371
+ geometry: FciGeometry3D,
372
+ parameters: Fci4FieldFreeDecayParameters,
373
+ phi_face_projectors: tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray] | None,
374
+ conservative_stencil_builder: ConservativeStencilBuilder = build_conservative_stencil_from_field,
375
+ boundary_conditions: FourFieldBoundaryConditions,
376
+ periodic_axes: tuple[bool, bool, bool],
377
+ b_floor: float,
378
+ jacobian_floor: float,
379
+ ) -> Fci4FieldState:
380
+ bcs = boundary_conditions
381
+ # The perpendicular diffusion is a conservative control-volume operator, so it
382
+ # requires conservative (face-gradient) stencils. Rebuild them from the fields
383
+ # here rather than reuse the local advection stencils the callers pass in (which
384
+ # carry no face-gradient data), so every caller gets a correct diffusion term.
385
+ density_stencil = conservative_stencil_builder(
386
+ density, geometry, periodic_axes=periodic_axes, face_bc=bcs.density_face_bc
387
+ )
388
+ omega_stencil = conservative_stencil_builder(
389
+ omega, geometry, periodic_axes=periodic_axes, face_bc=bcs.omega_face_bc
390
+ )
391
+ v_ion_parallel_stencil = conservative_stencil_builder(
392
+ v_ion_parallel, geometry, periodic_axes=periodic_axes, face_bc=bcs.v_ion_parallel_face_bc
393
+ )
394
+ v_electron_parallel_stencil = conservative_stencil_builder(
395
+ v_electron_parallel, geometry, periodic_axes=periodic_axes, face_bc=bcs.v_electron_parallel_face_bc
396
+ )
397
+ density_diffusion = jnp.asarray(parameters.density_perp_diffusion, dtype=jnp.float64) * perp_laplacian_conservative_op(
398
+ density_stencil,
399
+ geometry,
400
+ face_projectors=phi_face_projectors,
401
+ face_bc=bcs.density_face_bc,
402
+ cut_wall_geometry=bcs.density_cut_wall_geometry,
403
+ cut_wall_bc=bcs.density_cut_wall_bc,
404
+ periodic_axes=periodic_axes,
405
+ b_floor=b_floor,
406
+ jacobian_floor=jacobian_floor,
407
+ )
408
+ omega_diffusion = jnp.asarray(parameters.omega_perp_diffusion, dtype=jnp.float64) * perp_laplacian_conservative_op(
409
+ omega_stencil,
410
+ geometry,
411
+ face_projectors=phi_face_projectors,
412
+ face_bc=bcs.omega_face_bc,
413
+ cut_wall_geometry=bcs.omega_cut_wall_geometry,
414
+ cut_wall_bc=bcs.omega_cut_wall_bc,
415
+ periodic_axes=periodic_axes,
416
+ b_floor=b_floor,
417
+ jacobian_floor=jacobian_floor,
418
+ )
419
+ v_ion_parallel_diffusion = jnp.asarray(parameters.v_ion_parallel_perp_diffusion, dtype=jnp.float64) * perp_laplacian_conservative_op(
420
+ v_ion_parallel_stencil,
421
+ geometry,
422
+ face_projectors=phi_face_projectors,
423
+ face_bc=bcs.v_ion_parallel_face_bc,
424
+ cut_wall_geometry=bcs.v_ion_parallel_cut_wall_geometry,
425
+ cut_wall_bc=bcs.v_ion_parallel_cut_wall_bc,
426
+ periodic_axes=periodic_axes,
427
+ b_floor=b_floor,
428
+ jacobian_floor=jacobian_floor,
429
+ )
430
+ v_electron_parallel_diffusion = jnp.asarray(parameters.v_electron_parallel_perp_diffusion, dtype=jnp.float64) * perp_laplacian_conservative_op(
431
+ v_electron_parallel_stencil,
432
+ geometry,
433
+ face_projectors=phi_face_projectors,
434
+ face_bc=bcs.v_electron_parallel_face_bc,
435
+ cut_wall_geometry=bcs.v_electron_parallel_cut_wall_geometry,
436
+ cut_wall_bc=bcs.v_electron_parallel_cut_wall_bc,
437
+ periodic_axes=periodic_axes,
438
+ b_floor=b_floor,
439
+ jacobian_floor=jacobian_floor,
440
+ )
441
+ return Fci4FieldState(
442
+ density=density_diffusion,
443
+ omega=omega_diffusion,
444
+ v_ion_parallel=v_ion_parallel_diffusion,
445
+ v_electron_parallel=v_electron_parallel_diffusion,
446
+ )
447
+
448
+
449
+ def _compute_4field_term_components(
450
+ state: Fci4FieldState,
451
+ *,
452
+ geometry: FciGeometry3D,
453
+ stencil_builder: LocalStencilBuilder,
454
+ conservative_stencil_builder: ConservativeStencilBuilder,
455
+ parameters: Fci4FieldFreeDecayParameters,
456
+ curvature_coefficients: jnp.ndarray,
457
+ phi: jnp.ndarray,
458
+ boundary_conditions: FourFieldBoundaryConditions,
459
+ phi_face_projectors: tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray] | None,
460
+ periodic_axes: tuple[bool, bool, bool],
461
+ b_floor: float,
462
+ jacobian_floor: float,
463
+ ) -> tuple[Fci4FieldState, Fci4FieldState, Fci4FieldState, Fci4FieldState]:
464
+ bcs = boundary_conditions
465
+ density = jnp.asarray(state.density, dtype=jnp.float64)
466
+ omega = jnp.asarray(state.omega, dtype=jnp.float64)
467
+ v_ion_parallel = jnp.asarray(state.v_ion_parallel, dtype=jnp.float64)
468
+ v_electron_parallel = jnp.asarray(state.v_electron_parallel, dtype=jnp.float64)
469
+ bmag = jnp.maximum(jnp.asarray(geometry.cell_bfield.Bmag, dtype=jnp.float64), float(b_floor))
470
+ density_safe = jnp.maximum(density, 1.0e-30)
471
+ rho_star = jnp.asarray(parameters.rho_star, dtype=jnp.float64)
472
+ te = jnp.asarray(parameters.Te, dtype=jnp.float64)
473
+ mi_over_me = jnp.asarray(parameters.mi_over_me, dtype=jnp.float64)
474
+
475
+ density_stencil = stencil_builder(
476
+ density,
477
+ geometry,
478
+ periodic_axes=periodic_axes,
479
+ face_bc=bcs.density_face_bc,
480
+ cut_wall_geometry=bcs.density_cut_wall_geometry,
481
+ cut_wall_bc=bcs.density_cut_wall_bc,
482
+ )
483
+ omega_stencil = stencil_builder(
484
+ omega,
485
+ geometry,
486
+ periodic_axes=periodic_axes,
487
+ face_bc=bcs.omega_face_bc,
488
+ cut_wall_geometry=bcs.omega_cut_wall_geometry,
489
+ cut_wall_bc=bcs.omega_cut_wall_bc,
490
+ )
491
+ phi_stencil = stencil_builder(
492
+ phi,
493
+ geometry,
494
+ periodic_axes=periodic_axes,
495
+ face_bc=bcs.phi_face_bc,
496
+ cut_wall_geometry=bcs.phi_cut_wall_geometry,
497
+ cut_wall_bc=bcs.phi_cut_wall_bc,
498
+ )
499
+ v_ion_parallel_stencil = stencil_builder(
500
+ v_ion_parallel,
501
+ geometry,
502
+ periodic_axes=periodic_axes,
503
+ face_bc=bcs.v_ion_parallel_face_bc,
504
+ cut_wall_geometry=bcs.v_ion_parallel_cut_wall_geometry,
505
+ cut_wall_bc=bcs.v_ion_parallel_cut_wall_bc,
506
+ )
507
+ v_electron_parallel_stencil = stencil_builder(
508
+ v_electron_parallel,
509
+ geometry,
510
+ periodic_axes=periodic_axes,
511
+ face_bc=bcs.v_electron_parallel_face_bc,
512
+ cut_wall_geometry=bcs.v_electron_parallel_cut_wall_geometry,
513
+ cut_wall_bc=bcs.v_electron_parallel_cut_wall_bc,
514
+ )
515
+
516
+ poisson_rhs, curvature_rhs, parallel_rhs = _assemble_4field_non_diffusive_rhs(
517
+ density=density,
518
+ omega=omega,
519
+ v_ion_parallel=v_ion_parallel,
520
+ v_electron_parallel=v_electron_parallel,
521
+ rho_star=rho_star,
522
+ te=te,
523
+ mi_over_me=mi_over_me,
524
+ bmag=bmag,
525
+ density_safe=density_safe,
526
+ geometry=geometry,
527
+ curvature_coefficients=curvature_coefficients,
528
+ density_stencil=density_stencil,
529
+ omega_stencil=omega_stencil,
530
+ phi_stencil=phi_stencil,
531
+ v_ion_parallel_stencil=v_ion_parallel_stencil,
532
+ v_electron_parallel_stencil=v_electron_parallel_stencil,
533
+ )
534
+ diffusion_rhs = _assemble_4field_diffusion_rhs(
535
+ density=density,
536
+ omega=omega,
537
+ v_ion_parallel=v_ion_parallel,
538
+ v_electron_parallel=v_electron_parallel,
539
+ geometry=geometry,
540
+ parameters=parameters,
541
+ phi_face_projectors=phi_face_projectors,
542
+ conservative_stencil_builder=conservative_stencil_builder,
543
+ boundary_conditions=bcs,
544
+ periodic_axes=periodic_axes,
545
+ b_floor=b_floor,
546
+ jacobian_floor=jacobian_floor,
547
+ )
548
+ return poisson_rhs, curvature_rhs, parallel_rhs, diffusion_rhs
549
+
550
+
551
+ def compute_4field_rhs(
552
+ state: Fci4FieldState,
553
+ *,
554
+ geometry: FciGeometry3D,
555
+ stencil_builder: LocalStencilBuilder = build_local_stencil_from_field,
556
+ conservative_stencil_builder: ConservativeStencilBuilder = build_conservative_stencil_from_field,
557
+ parameters: Fci4FieldRhsParameters = Fci4FieldRhsParameters(),
558
+ curvature_coefficients: jnp.ndarray,
559
+ phi_guess: jnp.ndarray | None = None,
560
+ boundary_conditions: FourFieldBoundaryConditions | None = None,
561
+ phi_face_bc: BoundaryFaceBC3D | None = None,
562
+ density_face_bc: BoundaryFaceBC3D | None = None,
563
+ omega_face_bc: BoundaryFaceBC3D | None = None,
564
+ v_ion_parallel_face_bc: BoundaryFaceBC3D | None = None,
565
+ v_electron_parallel_face_bc: BoundaryFaceBC3D | None = None,
566
+ phi_cut_wall_geometry: CutWallGeometry3D | None = None,
567
+ phi_cut_wall_bc: CutWallBC3D | None = None,
568
+ density_cut_wall_geometry: CutWallGeometry3D | None = None,
569
+ density_cut_wall_bc: CutWallBC3D | None = None,
570
+ omega_cut_wall_geometry: CutWallGeometry3D | None = None,
571
+ omega_cut_wall_bc: CutWallBC3D | None = None,
572
+ v_ion_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
573
+ v_ion_parallel_cut_wall_bc: CutWallBC3D | None = None,
574
+ v_electron_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
575
+ v_electron_parallel_cut_wall_bc: CutWallBC3D | None = None,
576
+ phi_face_projectors: tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray] | None = None,
577
+ phi_mg_hierarchy: PerpLaplacianMgHierarchy | None = None,
578
+ phi_inverse_solver: PerpLaplacianInverseSolver | None = None,
579
+ periodic_axes: tuple[bool, bool, bool] = (False, True, True),
580
+ b_floor: float = 1.0e-30,
581
+ jacobian_floor: float = 1.0e-30,
582
+ gmres_debug: bool = False,
583
+ return_phi: bool = False,
584
+ with_diagnostics: bool = False,
585
+ ) -> tuple[Fci4FieldRhsResult, jnp.ndarray | None] | tuple[Fci4FieldRhsResult, jnp.ndarray | None, jnp.ndarray]:
586
+ """Assemble the normalized four-field RHS using the new stencil pipeline.
587
+
588
+ By default the assembly is sync-free (``timings`` is ``None``) so the whole
589
+ RHS can run inside ``jit``; ``with_diagnostics=True`` restores the
590
+ host-synced stage timings and phi-solver diagnostics payload used by the
591
+ validation harnesses.
592
+
593
+ Boundary conditions may be given either as one
594
+ :class:`FourFieldBoundaryConditions` bundle (``boundary_conditions=``) or
595
+ as the individual per-field ``*_face_bc`` / ``*_cut_wall_*`` kwargs, but
596
+ not both.
597
+ """
598
+
599
+ bcs = _resolve_four_field_bcs(
600
+ boundary_conditions,
601
+ phi_face_bc=phi_face_bc,
602
+ density_face_bc=density_face_bc,
603
+ omega_face_bc=omega_face_bc,
604
+ v_ion_parallel_face_bc=v_ion_parallel_face_bc,
605
+ v_electron_parallel_face_bc=v_electron_parallel_face_bc,
606
+ phi_cut_wall_geometry=phi_cut_wall_geometry,
607
+ phi_cut_wall_bc=phi_cut_wall_bc,
608
+ density_cut_wall_geometry=density_cut_wall_geometry,
609
+ density_cut_wall_bc=density_cut_wall_bc,
610
+ omega_cut_wall_geometry=omega_cut_wall_geometry,
611
+ omega_cut_wall_bc=omega_cut_wall_bc,
612
+ v_ion_parallel_cut_wall_geometry=v_ion_parallel_cut_wall_geometry,
613
+ v_ion_parallel_cut_wall_bc=v_ion_parallel_cut_wall_bc,
614
+ v_electron_parallel_cut_wall_geometry=v_electron_parallel_cut_wall_geometry,
615
+ v_electron_parallel_cut_wall_bc=v_electron_parallel_cut_wall_bc,
616
+ )
617
+ density = jnp.asarray(state.density, dtype=jnp.float64)
618
+ omega = jnp.asarray(state.omega, dtype=jnp.float64)
619
+ v_ion_parallel = jnp.asarray(state.v_ion_parallel, dtype=jnp.float64)
620
+ v_electron_parallel = jnp.asarray(state.v_electron_parallel, dtype=jnp.float64)
621
+ rho_star = jnp.asarray(parameters.rho_star, dtype=jnp.float64)
622
+ te = jnp.asarray(parameters.Te, dtype=jnp.float64)
623
+ mi_over_me = jnp.asarray(parameters.mi_over_me, dtype=jnp.float64)
624
+ bmag = jnp.maximum(jnp.asarray(geometry.cell_bfield.Bmag, dtype=jnp.float64), float(b_floor))
625
+ density_safe = jnp.maximum(density, 1.0e-30)
626
+
627
+ if phi_inverse_solver is None:
628
+ raise ValueError("compute_4field_rhs requires a PerpLaplacianInverseSolver instance")
629
+ if with_diagnostics:
630
+ phi_start = time_module.perf_counter()
631
+ phi, phi_diagnostics = phi_inverse_solver(
632
+ -omega,
633
+ phi_guess=phi_guess,
634
+ face_bc=bcs.phi_face_bc,
635
+ cut_wall_bc=bcs.phi_cut_wall_bc,
636
+ return_diagnostics=True,
637
+ )
638
+ jax.block_until_ready(phi)
639
+ phi_time = time_module.perf_counter() - phi_start
640
+ phi_num_steps = float(phi_diagnostics["num_steps"])
641
+ rhs_mean_j = float(phi_diagnostics["rhs_mean_J"])
642
+ rhs_l2_j = float(phi_diagnostics["rhs_l2_J"])
643
+ rhs_compatibility_ratio = float(phi_diagnostics["rhs_compatibility_ratio"])
644
+ projected_rhs_mean_j = float(phi_diagnostics["projected_rhs_mean_J"])
645
+ projected_rhs_l2_j = float(phi_diagnostics["projected_rhs_l2_J"])
646
+ projected_rhs_compatibility_ratio = float(phi_diagnostics["projected_rhs_compatibility_ratio"])
647
+ else:
648
+ phi = phi_inverse_solver(
649
+ -omega,
650
+ phi_guess=phi_guess,
651
+ face_bc=bcs.phi_face_bc,
652
+ cut_wall_bc=bcs.phi_cut_wall_bc,
653
+ )
654
+
655
+ stencil_start = time_module.perf_counter() if with_diagnostics else 0.0
656
+ density_stencil = stencil_builder(
657
+ density,
658
+ geometry,
659
+ periodic_axes=periodic_axes,
660
+ face_bc=bcs.density_face_bc,
661
+ cut_wall_geometry=bcs.density_cut_wall_geometry,
662
+ cut_wall_bc=bcs.density_cut_wall_bc,
663
+ )
664
+ omega_stencil = stencil_builder(
665
+ omega,
666
+ geometry,
667
+ periodic_axes=periodic_axes,
668
+ face_bc=bcs.omega_face_bc,
669
+ cut_wall_geometry=bcs.omega_cut_wall_geometry,
670
+ cut_wall_bc=bcs.omega_cut_wall_bc,
671
+ )
672
+ phi_stencil = stencil_builder(
673
+ phi,
674
+ geometry,
675
+ periodic_axes=periodic_axes,
676
+ face_bc=bcs.phi_face_bc,
677
+ cut_wall_geometry=bcs.phi_cut_wall_geometry,
678
+ cut_wall_bc=bcs.phi_cut_wall_bc,
679
+ )
680
+ v_ion_parallel_stencil = stencil_builder(
681
+ v_ion_parallel,
682
+ geometry,
683
+ periodic_axes=periodic_axes,
684
+ face_bc=bcs.v_ion_parallel_face_bc,
685
+ cut_wall_geometry=bcs.v_ion_parallel_cut_wall_geometry,
686
+ cut_wall_bc=bcs.v_ion_parallel_cut_wall_bc,
687
+ )
688
+ v_electron_parallel_stencil = stencil_builder(
689
+ v_electron_parallel,
690
+ geometry,
691
+ periodic_axes=periodic_axes,
692
+ face_bc=bcs.v_electron_parallel_face_bc,
693
+ cut_wall_geometry=bcs.v_electron_parallel_cut_wall_geometry,
694
+ cut_wall_bc=bcs.v_electron_parallel_cut_wall_bc,
695
+ )
696
+ if with_diagnostics:
697
+ jax.block_until_ready(density_stencil.x.center)
698
+ jax.block_until_ready(omega_stencil.x.center)
699
+ jax.block_until_ready(phi_stencil.x.center)
700
+ jax.block_until_ready(v_ion_parallel_stencil.x.center)
701
+ jax.block_until_ready(v_electron_parallel_stencil.x.center)
702
+ local_stencil_time = (time_module.perf_counter() - stencil_start) if with_diagnostics else 0.0
703
+
704
+ operator_start = time_module.perf_counter() if with_diagnostics else 0.0
705
+ poisson_rhs, curvature_rhs, parallel_rhs = _assemble_4field_non_diffusive_rhs(
706
+ density=density,
707
+ omega=omega,
708
+ v_ion_parallel=v_ion_parallel,
709
+ v_electron_parallel=v_electron_parallel,
710
+ rho_star=rho_star,
711
+ te=te,
712
+ mi_over_me=mi_over_me,
713
+ bmag=bmag,
714
+ density_safe=density_safe,
715
+ geometry=geometry,
716
+ curvature_coefficients=curvature_coefficients,
717
+ density_stencil=density_stencil,
718
+ omega_stencil=omega_stencil,
719
+ phi_stencil=phi_stencil,
720
+ v_ion_parallel_stencil=v_ion_parallel_stencil,
721
+ v_electron_parallel_stencil=v_electron_parallel_stencil,
722
+ )
723
+
724
+ rhs_density = jnp.asarray(
725
+ poisson_rhs.density + curvature_rhs.density + parallel_rhs.density,
726
+ dtype=jnp.float64,
727
+ )
728
+ rhs_omega = jnp.asarray(
729
+ poisson_rhs.omega + curvature_rhs.omega + parallel_rhs.omega,
730
+ dtype=jnp.float64,
731
+ )
732
+ rhs_v_ion_parallel = jnp.asarray(
733
+ poisson_rhs.v_ion_parallel + curvature_rhs.v_ion_parallel + parallel_rhs.v_ion_parallel,
734
+ dtype=jnp.float64,
735
+ )
736
+ rhs_v_electron_parallel = jnp.asarray(
737
+ poisson_rhs.v_electron_parallel + curvature_rhs.v_electron_parallel + parallel_rhs.v_electron_parallel,
738
+ dtype=jnp.float64,
739
+ )
740
+ rhs = Fci4FieldState(
741
+ density=rhs_density,
742
+ omega=rhs_omega,
743
+ v_ion_parallel=rhs_v_ion_parallel,
744
+ v_electron_parallel=rhs_v_electron_parallel,
745
+ )
746
+ if with_diagnostics:
747
+ jax.block_until_ready(rhs_density)
748
+ jax.block_until_ready(rhs_omega)
749
+ jax.block_until_ready(rhs_v_ion_parallel)
750
+ jax.block_until_ready(rhs_v_electron_parallel)
751
+ operator_time = time_module.perf_counter() - operator_start
752
+ timings = jnp.asarray(
753
+ [
754
+ phi_time,
755
+ local_stencil_time,
756
+ operator_time,
757
+ phi_num_steps,
758
+ rhs_mean_j,
759
+ rhs_l2_j,
760
+ rhs_compatibility_ratio,
761
+ projected_rhs_mean_j,
762
+ projected_rhs_l2_j,
763
+ projected_rhs_compatibility_ratio,
764
+ float(phi_diagnostics["final_residual_rel_l2"]),
765
+ ],
766
+ dtype=jnp.float64,
767
+ )
768
+ else:
769
+ timings = None
770
+ if return_phi:
771
+ return Fci4FieldRhsResult(rhs=rhs), timings, phi
772
+ return Fci4FieldRhsResult(rhs=rhs), timings
773
+
774
+
775
+ def compute_4field_free_decay_rhs(
776
+ state: Fci4FieldState,
777
+ *,
778
+ geometry: FciGeometry3D,
779
+ stencil_builder: LocalStencilBuilder = build_local_stencil_from_field,
780
+ conservative_stencil_builder: ConservativeStencilBuilder = build_conservative_stencil_from_field,
781
+ parameters: Fci4FieldFreeDecayParameters = Fci4FieldFreeDecayParameters(),
782
+ curvature_coefficients: jnp.ndarray,
783
+ phi_guess: jnp.ndarray | None = None,
784
+ boundary_conditions: FourFieldBoundaryConditions | None = None,
785
+ phi_face_bc: BoundaryFaceBC3D | None = None,
786
+ density_face_bc: BoundaryFaceBC3D | None = None,
787
+ omega_face_bc: BoundaryFaceBC3D | None = None,
788
+ v_ion_parallel_face_bc: BoundaryFaceBC3D | None = None,
789
+ v_electron_parallel_face_bc: BoundaryFaceBC3D | None = None,
790
+ phi_cut_wall_geometry: CutWallGeometry3D | None = None,
791
+ phi_cut_wall_bc: CutWallBC3D | None = None,
792
+ density_cut_wall_geometry: CutWallGeometry3D | None = None,
793
+ density_cut_wall_bc: CutWallBC3D | None = None,
794
+ omega_cut_wall_geometry: CutWallGeometry3D | None = None,
795
+ omega_cut_wall_bc: CutWallBC3D | None = None,
796
+ v_ion_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
797
+ v_ion_parallel_cut_wall_bc: CutWallBC3D | None = None,
798
+ v_electron_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
799
+ v_electron_parallel_cut_wall_bc: CutWallBC3D | None = None,
800
+ phi_face_projectors: tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray] | None = None,
801
+ phi_mg_hierarchy: PerpLaplacianMgHierarchy | None = None,
802
+ phi_inverse_solver: PerpLaplacianInverseSolver | None = None,
803
+ periodic_axes: tuple[bool, bool, bool] = (False, True, True),
804
+ b_floor: float = 1.0e-30,
805
+ jacobian_floor: float = 1.0e-30,
806
+ gmres_debug: bool = False,
807
+ return_phi: bool = False,
808
+ with_diagnostics: bool = False,
809
+ ) -> tuple[Fci4FieldRhsResult, jnp.ndarray] | tuple[Fci4FieldRhsResult, jnp.ndarray, jnp.ndarray]:
810
+ bcs = _resolve_four_field_bcs(
811
+ boundary_conditions,
812
+ phi_face_bc=phi_face_bc,
813
+ density_face_bc=density_face_bc,
814
+ omega_face_bc=omega_face_bc,
815
+ v_ion_parallel_face_bc=v_ion_parallel_face_bc,
816
+ v_electron_parallel_face_bc=v_electron_parallel_face_bc,
817
+ phi_cut_wall_geometry=phi_cut_wall_geometry,
818
+ phi_cut_wall_bc=phi_cut_wall_bc,
819
+ density_cut_wall_geometry=density_cut_wall_geometry,
820
+ density_cut_wall_bc=density_cut_wall_bc,
821
+ omega_cut_wall_geometry=omega_cut_wall_geometry,
822
+ omega_cut_wall_bc=omega_cut_wall_bc,
823
+ v_ion_parallel_cut_wall_geometry=v_ion_parallel_cut_wall_geometry,
824
+ v_ion_parallel_cut_wall_bc=v_ion_parallel_cut_wall_bc,
825
+ v_electron_parallel_cut_wall_geometry=v_electron_parallel_cut_wall_geometry,
826
+ v_electron_parallel_cut_wall_bc=v_electron_parallel_cut_wall_bc,
827
+ )
828
+ base_result, timings, phi = compute_4field_rhs(
829
+ state,
830
+ geometry=geometry,
831
+ stencil_builder=stencil_builder,
832
+ conservative_stencil_builder=conservative_stencil_builder,
833
+ parameters=parameters.rhs_parameters(),
834
+ curvature_coefficients=curvature_coefficients,
835
+ phi_guess=phi_guess,
836
+ boundary_conditions=bcs,
837
+ phi_face_projectors=phi_face_projectors,
838
+ phi_mg_hierarchy=phi_mg_hierarchy,
839
+ phi_inverse_solver=phi_inverse_solver,
840
+ periodic_axes=periodic_axes,
841
+ b_floor=b_floor,
842
+ jacobian_floor=jacobian_floor,
843
+ gmres_debug=gmres_debug,
844
+ return_phi=True,
845
+ with_diagnostics=with_diagnostics,
846
+ )
847
+
848
+ diffusion_rhs = _assemble_4field_diffusion_rhs(
849
+ density=state.density,
850
+ omega=state.omega,
851
+ v_ion_parallel=state.v_ion_parallel,
852
+ v_electron_parallel=state.v_electron_parallel,
853
+ geometry=geometry,
854
+ parameters=parameters,
855
+ phi_face_projectors=phi_face_projectors,
856
+ conservative_stencil_builder=conservative_stencil_builder,
857
+ boundary_conditions=bcs,
858
+ periodic_axes=periodic_axes,
859
+ b_floor=b_floor,
860
+ jacobian_floor=jacobian_floor,
861
+ )
862
+
863
+ rhs = base_result.rhs
864
+ result = Fci4FieldRhsResult(
865
+ rhs=Fci4FieldState(
866
+ density=rhs.density + diffusion_rhs.density,
867
+ omega=rhs.omega + diffusion_rhs.omega,
868
+ v_ion_parallel=rhs.v_ion_parallel + diffusion_rhs.v_ion_parallel,
869
+ v_electron_parallel=rhs.v_electron_parallel + diffusion_rhs.v_electron_parallel,
870
+ )
871
+ )
872
+ if return_phi:
873
+ return result, timings, phi
874
+ return result, timings
875
+
876
+
877
+ def compute_4field_blob_rhs(
878
+ state: Fci4FieldState,
879
+ *,
880
+ geometry: FciGeometry3D,
881
+ stencil_builder: LocalStencilBuilder = build_local_stencil_from_field,
882
+ conservative_stencil_builder: ConservativeStencilBuilder = build_conservative_stencil_from_field,
883
+ parameters: Fci4FieldBlobParameters = Fci4FieldBlobParameters(),
884
+ curvature_coefficients: jnp.ndarray,
885
+ phi_guess: jnp.ndarray | None = None,
886
+ boundary_conditions: FourFieldBoundaryConditions | None = None,
887
+ phi_face_bc: BoundaryFaceBC3D | None = None,
888
+ density_face_bc: BoundaryFaceBC3D | None = None,
889
+ omega_face_bc: BoundaryFaceBC3D | None = None,
890
+ v_ion_parallel_face_bc: BoundaryFaceBC3D | None = None,
891
+ v_electron_parallel_face_bc: BoundaryFaceBC3D | None = None,
892
+ phi_cut_wall_geometry: CutWallGeometry3D | None = None,
893
+ phi_cut_wall_bc: CutWallBC3D | None = None,
894
+ density_cut_wall_geometry: CutWallGeometry3D | None = None,
895
+ density_cut_wall_bc: CutWallBC3D | None = None,
896
+ omega_cut_wall_geometry: CutWallGeometry3D | None = None,
897
+ omega_cut_wall_bc: CutWallBC3D | None = None,
898
+ v_ion_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
899
+ v_ion_parallel_cut_wall_bc: CutWallBC3D | None = None,
900
+ v_electron_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
901
+ v_electron_parallel_cut_wall_bc: CutWallBC3D | None = None,
902
+ phi_face_projectors: tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray] | None = None,
903
+ phi_mg_hierarchy: PerpLaplacianMgHierarchy | None = None,
904
+ phi_inverse_solver: PerpLaplacianInverseSolver | None = None,
905
+ periodic_axes: tuple[bool, bool, bool] = (False, True, True),
906
+ b_floor: float = 1.0e-30,
907
+ jacobian_floor: float = 1.0e-30,
908
+ gmres_debug: bool = False,
909
+ return_phi: bool = False,
910
+ with_diagnostics: bool = False,
911
+ ) -> tuple[Fci4FieldRhsResult, jnp.ndarray] | tuple[Fci4FieldRhsResult, jnp.ndarray, jnp.ndarray]:
912
+ """Assemble the four-field blob RHS.
913
+
914
+ The blob model shares its full right-hand side with the free-decay
915
+ wrapper (non-diffusive terms plus conservative perpendicular
916
+ diffusion), so this simply delegates -- the two were verified to
917
+ produce bitwise-identical RHS fields and phi.
918
+ """
919
+
920
+ return compute_4field_free_decay_rhs(
921
+ state,
922
+ geometry=geometry,
923
+ stencil_builder=stencil_builder,
924
+ conservative_stencil_builder=conservative_stencil_builder,
925
+ parameters=parameters,
926
+ curvature_coefficients=curvature_coefficients,
927
+ phi_guess=phi_guess,
928
+ boundary_conditions=boundary_conditions,
929
+ phi_face_bc=phi_face_bc,
930
+ density_face_bc=density_face_bc,
931
+ omega_face_bc=omega_face_bc,
932
+ v_ion_parallel_face_bc=v_ion_parallel_face_bc,
933
+ v_electron_parallel_face_bc=v_electron_parallel_face_bc,
934
+ phi_cut_wall_geometry=phi_cut_wall_geometry,
935
+ phi_cut_wall_bc=phi_cut_wall_bc,
936
+ density_cut_wall_geometry=density_cut_wall_geometry,
937
+ density_cut_wall_bc=density_cut_wall_bc,
938
+ omega_cut_wall_geometry=omega_cut_wall_geometry,
939
+ omega_cut_wall_bc=omega_cut_wall_bc,
940
+ v_ion_parallel_cut_wall_geometry=v_ion_parallel_cut_wall_geometry,
941
+ v_ion_parallel_cut_wall_bc=v_ion_parallel_cut_wall_bc,
942
+ v_electron_parallel_cut_wall_geometry=v_electron_parallel_cut_wall_geometry,
943
+ v_electron_parallel_cut_wall_bc=v_electron_parallel_cut_wall_bc,
944
+ phi_face_projectors=phi_face_projectors,
945
+ phi_mg_hierarchy=phi_mg_hierarchy,
946
+ phi_inverse_solver=phi_inverse_solver,
947
+ periodic_axes=periodic_axes,
948
+ b_floor=b_floor,
949
+ jacobian_floor=jacobian_floor,
950
+ gmres_debug=gmres_debug,
951
+ return_phi=return_phi,
952
+ with_diagnostics=with_diagnostics,
953
+ )
954
+
955
+
956
+ def compute_4field_diffusion(
957
+ state: Fci4FieldState,
958
+ *,
959
+ geometry: FciGeometry3D,
960
+ stencil_builder: LocalStencilBuilder = build_local_stencil_from_field,
961
+ conservative_stencil_builder: ConservativeStencilBuilder = build_conservative_stencil_from_field,
962
+ parameters: Fci4FieldFreeDecayParameters = Fci4FieldFreeDecayParameters(),
963
+ curvature_coefficients: jnp.ndarray,
964
+ phi_guess: jnp.ndarray | None = None,
965
+ boundary_conditions: FourFieldBoundaryConditions | None = None,
966
+ phi_face_bc: BoundaryFaceBC3D | None = None,
967
+ density_face_bc: BoundaryFaceBC3D | None = None,
968
+ omega_face_bc: BoundaryFaceBC3D | None = None,
969
+ v_ion_parallel_face_bc: BoundaryFaceBC3D | None = None,
970
+ v_electron_parallel_face_bc: BoundaryFaceBC3D | None = None,
971
+ phi_cut_wall_geometry: CutWallGeometry3D | None = None,
972
+ phi_cut_wall_bc: CutWallBC3D | None = None,
973
+ density_cut_wall_geometry: CutWallGeometry3D | None = None,
974
+ density_cut_wall_bc: CutWallBC3D | None = None,
975
+ omega_cut_wall_geometry: CutWallGeometry3D | None = None,
976
+ omega_cut_wall_bc: CutWallBC3D | None = None,
977
+ v_ion_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
978
+ v_ion_parallel_cut_wall_bc: CutWallBC3D | None = None,
979
+ v_electron_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
980
+ v_electron_parallel_cut_wall_bc: CutWallBC3D | None = None,
981
+ phi_face_projectors: tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray] | None = None,
982
+ phi_mg_hierarchy: PerpLaplacianMgHierarchy | None = None,
983
+ phi_inverse_solver: PerpLaplacianInverseSolver | None = None,
984
+ periodic_axes: tuple[bool, bool, bool] = (False, True, True),
985
+ b_floor: float = 1.0e-30,
986
+ jacobian_floor: float = 1.0e-30,
987
+ gmres_debug: bool = False,
988
+ return_phi: bool = False,
989
+ with_diagnostics: bool = False,
990
+ ) -> tuple[Fci4FieldRhsResult, jnp.ndarray] | tuple[Fci4FieldRhsResult, jnp.ndarray, jnp.ndarray]:
991
+ bcs = _resolve_four_field_bcs(
992
+ boundary_conditions,
993
+ phi_face_bc=phi_face_bc,
994
+ density_face_bc=density_face_bc,
995
+ omega_face_bc=omega_face_bc,
996
+ v_ion_parallel_face_bc=v_ion_parallel_face_bc,
997
+ v_electron_parallel_face_bc=v_electron_parallel_face_bc,
998
+ phi_cut_wall_geometry=phi_cut_wall_geometry,
999
+ phi_cut_wall_bc=phi_cut_wall_bc,
1000
+ density_cut_wall_geometry=density_cut_wall_geometry,
1001
+ density_cut_wall_bc=density_cut_wall_bc,
1002
+ omega_cut_wall_geometry=omega_cut_wall_geometry,
1003
+ omega_cut_wall_bc=omega_cut_wall_bc,
1004
+ v_ion_parallel_cut_wall_geometry=v_ion_parallel_cut_wall_geometry,
1005
+ v_ion_parallel_cut_wall_bc=v_ion_parallel_cut_wall_bc,
1006
+ v_electron_parallel_cut_wall_geometry=v_electron_parallel_cut_wall_geometry,
1007
+ v_electron_parallel_cut_wall_bc=v_electron_parallel_cut_wall_bc,
1008
+ )
1009
+ base_result, timings, phi = compute_4field_free_decay_rhs(
1010
+ state,
1011
+ geometry=geometry,
1012
+ stencil_builder=stencil_builder,
1013
+ conservative_stencil_builder=conservative_stencil_builder,
1014
+ parameters=parameters,
1015
+ curvature_coefficients=curvature_coefficients,
1016
+ phi_guess=phi_guess,
1017
+ boundary_conditions=bcs,
1018
+ phi_face_projectors=phi_face_projectors,
1019
+ phi_mg_hierarchy=phi_mg_hierarchy,
1020
+ phi_inverse_solver=phi_inverse_solver,
1021
+ periodic_axes=periodic_axes,
1022
+ b_floor=b_floor,
1023
+ jacobian_floor=jacobian_floor,
1024
+ gmres_debug=gmres_debug,
1025
+ return_phi=True,
1026
+ with_diagnostics=with_diagnostics,
1027
+ )
1028
+ poisson_rhs, curvature_rhs, parallel_rhs, diffusion_rhs = _compute_4field_term_components(
1029
+ state,
1030
+ geometry=geometry,
1031
+ stencil_builder=stencil_builder,
1032
+ conservative_stencil_builder=conservative_stencil_builder,
1033
+ parameters=parameters,
1034
+ curvature_coefficients=curvature_coefficients,
1035
+ phi=phi,
1036
+ boundary_conditions=bcs,
1037
+ phi_face_projectors=phi_face_projectors,
1038
+ periodic_axes=periodic_axes,
1039
+ b_floor=b_floor,
1040
+ jacobian_floor=jacobian_floor,
1041
+ )
1042
+ result = Fci4FieldRhsResult(
1043
+ rhs=Fci4FieldState(
1044
+ density=diffusion_rhs.density,
1045
+ omega=diffusion_rhs.omega,
1046
+ v_ion_parallel=diffusion_rhs.v_ion_parallel,
1047
+ v_electron_parallel=diffusion_rhs.v_electron_parallel,
1048
+ )
1049
+ )
1050
+ if return_phi:
1051
+ return result, timings, phi
1052
+ return result, timings
1053
+
1054
+
1055
+ def compute_4field_poisson_diffusion(
1056
+ state: Fci4FieldState,
1057
+ *,
1058
+ geometry: FciGeometry3D,
1059
+ stencil_builder: LocalStencilBuilder = build_local_stencil_from_field,
1060
+ conservative_stencil_builder: ConservativeStencilBuilder = build_conservative_stencil_from_field,
1061
+ parameters: Fci4FieldFreeDecayParameters = Fci4FieldFreeDecayParameters(),
1062
+ curvature_coefficients: jnp.ndarray,
1063
+ phi_guess: jnp.ndarray | None = None,
1064
+ boundary_conditions: FourFieldBoundaryConditions | None = None,
1065
+ phi_face_bc: BoundaryFaceBC3D | None = None,
1066
+ density_face_bc: BoundaryFaceBC3D | None = None,
1067
+ omega_face_bc: BoundaryFaceBC3D | None = None,
1068
+ v_ion_parallel_face_bc: BoundaryFaceBC3D | None = None,
1069
+ v_electron_parallel_face_bc: BoundaryFaceBC3D | None = None,
1070
+ phi_cut_wall_geometry: CutWallGeometry3D | None = None,
1071
+ phi_cut_wall_bc: CutWallBC3D | None = None,
1072
+ density_cut_wall_geometry: CutWallGeometry3D | None = None,
1073
+ density_cut_wall_bc: CutWallBC3D | None = None,
1074
+ omega_cut_wall_geometry: CutWallGeometry3D | None = None,
1075
+ omega_cut_wall_bc: CutWallBC3D | None = None,
1076
+ v_ion_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
1077
+ v_ion_parallel_cut_wall_bc: CutWallBC3D | None = None,
1078
+ v_electron_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
1079
+ v_electron_parallel_cut_wall_bc: CutWallBC3D | None = None,
1080
+ phi_face_projectors: tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray] | None = None,
1081
+ phi_mg_hierarchy: PerpLaplacianMgHierarchy | None = None,
1082
+ phi_inverse_solver: PerpLaplacianInverseSolver | None = None,
1083
+ periodic_axes: tuple[bool, bool, bool] = (False, True, True),
1084
+ b_floor: float = 1.0e-30,
1085
+ jacobian_floor: float = 1.0e-30,
1086
+ gmres_debug: bool = False,
1087
+ return_phi: bool = False,
1088
+ with_diagnostics: bool = False,
1089
+ ) -> tuple[Fci4FieldRhsResult, jnp.ndarray] | tuple[Fci4FieldRhsResult, jnp.ndarray, jnp.ndarray]:
1090
+ bcs = _resolve_four_field_bcs(
1091
+ boundary_conditions,
1092
+ phi_face_bc=phi_face_bc,
1093
+ density_face_bc=density_face_bc,
1094
+ omega_face_bc=omega_face_bc,
1095
+ v_ion_parallel_face_bc=v_ion_parallel_face_bc,
1096
+ v_electron_parallel_face_bc=v_electron_parallel_face_bc,
1097
+ phi_cut_wall_geometry=phi_cut_wall_geometry,
1098
+ phi_cut_wall_bc=phi_cut_wall_bc,
1099
+ density_cut_wall_geometry=density_cut_wall_geometry,
1100
+ density_cut_wall_bc=density_cut_wall_bc,
1101
+ omega_cut_wall_geometry=omega_cut_wall_geometry,
1102
+ omega_cut_wall_bc=omega_cut_wall_bc,
1103
+ v_ion_parallel_cut_wall_geometry=v_ion_parallel_cut_wall_geometry,
1104
+ v_ion_parallel_cut_wall_bc=v_ion_parallel_cut_wall_bc,
1105
+ v_electron_parallel_cut_wall_geometry=v_electron_parallel_cut_wall_geometry,
1106
+ v_electron_parallel_cut_wall_bc=v_electron_parallel_cut_wall_bc,
1107
+ )
1108
+ base_result, timings, phi = compute_4field_free_decay_rhs(
1109
+ state,
1110
+ geometry=geometry,
1111
+ stencil_builder=stencil_builder,
1112
+ conservative_stencil_builder=conservative_stencil_builder,
1113
+ parameters=parameters,
1114
+ curvature_coefficients=curvature_coefficients,
1115
+ phi_guess=phi_guess,
1116
+ boundary_conditions=bcs,
1117
+ phi_face_projectors=phi_face_projectors,
1118
+ phi_mg_hierarchy=phi_mg_hierarchy,
1119
+ phi_inverse_solver=phi_inverse_solver,
1120
+ periodic_axes=periodic_axes,
1121
+ b_floor=b_floor,
1122
+ jacobian_floor=jacobian_floor,
1123
+ gmres_debug=gmres_debug,
1124
+ return_phi=True,
1125
+ with_diagnostics=with_diagnostics,
1126
+ )
1127
+ poisson_rhs, curvature_rhs, parallel_rhs, diffusion_rhs = _compute_4field_term_components(
1128
+ state,
1129
+ geometry=geometry,
1130
+ stencil_builder=stencil_builder,
1131
+ conservative_stencil_builder=conservative_stencil_builder,
1132
+ parameters=parameters,
1133
+ curvature_coefficients=curvature_coefficients,
1134
+ phi=phi,
1135
+ boundary_conditions=bcs,
1136
+ phi_face_projectors=phi_face_projectors,
1137
+ periodic_axes=periodic_axes,
1138
+ b_floor=b_floor,
1139
+ jacobian_floor=jacobian_floor,
1140
+ )
1141
+ result = Fci4FieldRhsResult(
1142
+ rhs=Fci4FieldState(
1143
+ density=poisson_rhs.density + diffusion_rhs.density + parallel_rhs.density,
1144
+ omega=poisson_rhs.omega + diffusion_rhs.omega + parallel_rhs.omega,
1145
+ v_ion_parallel=poisson_rhs.v_ion_parallel + diffusion_rhs.v_ion_parallel + parallel_rhs.v_ion_parallel,
1146
+ v_electron_parallel=poisson_rhs.v_electron_parallel
1147
+ + diffusion_rhs.v_electron_parallel
1148
+ + parallel_rhs.v_electron_parallel,
1149
+ )
1150
+ )
1151
+ if return_phi:
1152
+ return result, timings, phi
1153
+ return result, timings
1154
+
1155
+
1156
+ def compute_4field_curvature(
1157
+ state: Fci4FieldState,
1158
+ *,
1159
+ geometry: FciGeometry3D,
1160
+ stencil_builder: LocalStencilBuilder = build_local_stencil_from_field,
1161
+ conservative_stencil_builder: ConservativeStencilBuilder = build_conservative_stencil_from_field,
1162
+ parameters: Fci4FieldFreeDecayParameters = Fci4FieldFreeDecayParameters(),
1163
+ curvature_coefficients: jnp.ndarray,
1164
+ phi_guess: jnp.ndarray | None = None,
1165
+ boundary_conditions: FourFieldBoundaryConditions | None = None,
1166
+ phi_face_bc: BoundaryFaceBC3D | None = None,
1167
+ density_face_bc: BoundaryFaceBC3D | None = None,
1168
+ omega_face_bc: BoundaryFaceBC3D | None = None,
1169
+ v_ion_parallel_face_bc: BoundaryFaceBC3D | None = None,
1170
+ v_electron_parallel_face_bc: BoundaryFaceBC3D | None = None,
1171
+ phi_cut_wall_geometry: CutWallGeometry3D | None = None,
1172
+ phi_cut_wall_bc: CutWallBC3D | None = None,
1173
+ density_cut_wall_geometry: CutWallGeometry3D | None = None,
1174
+ density_cut_wall_bc: CutWallBC3D | None = None,
1175
+ omega_cut_wall_geometry: CutWallGeometry3D | None = None,
1176
+ omega_cut_wall_bc: CutWallBC3D | None = None,
1177
+ v_ion_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
1178
+ v_ion_parallel_cut_wall_bc: CutWallBC3D | None = None,
1179
+ v_electron_parallel_cut_wall_geometry: CutWallGeometry3D | None = None,
1180
+ v_electron_parallel_cut_wall_bc: CutWallBC3D | None = None,
1181
+ phi_face_projectors: tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray] | None = None,
1182
+ phi_mg_hierarchy: PerpLaplacianMgHierarchy | None = None,
1183
+ phi_inverse_solver: PerpLaplacianInverseSolver | None = None,
1184
+ periodic_axes: tuple[bool, bool, bool] = (False, True, True),
1185
+ b_floor: float = 1.0e-30,
1186
+ jacobian_floor: float = 1.0e-30,
1187
+ gmres_debug: bool = False,
1188
+ return_phi: bool = False,
1189
+ with_diagnostics: bool = False,
1190
+ ) -> tuple[Fci4FieldRhsResult, jnp.ndarray] | tuple[Fci4FieldRhsResult, jnp.ndarray, jnp.ndarray]:
1191
+ bcs = _resolve_four_field_bcs(
1192
+ boundary_conditions,
1193
+ phi_face_bc=phi_face_bc,
1194
+ density_face_bc=density_face_bc,
1195
+ omega_face_bc=omega_face_bc,
1196
+ v_ion_parallel_face_bc=v_ion_parallel_face_bc,
1197
+ v_electron_parallel_face_bc=v_electron_parallel_face_bc,
1198
+ phi_cut_wall_geometry=phi_cut_wall_geometry,
1199
+ phi_cut_wall_bc=phi_cut_wall_bc,
1200
+ density_cut_wall_geometry=density_cut_wall_geometry,
1201
+ density_cut_wall_bc=density_cut_wall_bc,
1202
+ omega_cut_wall_geometry=omega_cut_wall_geometry,
1203
+ omega_cut_wall_bc=omega_cut_wall_bc,
1204
+ v_ion_parallel_cut_wall_geometry=v_ion_parallel_cut_wall_geometry,
1205
+ v_ion_parallel_cut_wall_bc=v_ion_parallel_cut_wall_bc,
1206
+ v_electron_parallel_cut_wall_geometry=v_electron_parallel_cut_wall_geometry,
1207
+ v_electron_parallel_cut_wall_bc=v_electron_parallel_cut_wall_bc,
1208
+ )
1209
+ base_result, timings, phi = compute_4field_free_decay_rhs(
1210
+ state,
1211
+ geometry=geometry,
1212
+ stencil_builder=stencil_builder,
1213
+ conservative_stencil_builder=conservative_stencil_builder,
1214
+ parameters=parameters,
1215
+ curvature_coefficients=curvature_coefficients,
1216
+ phi_guess=phi_guess,
1217
+ boundary_conditions=bcs,
1218
+ phi_face_projectors=phi_face_projectors,
1219
+ phi_mg_hierarchy=phi_mg_hierarchy,
1220
+ phi_inverse_solver=phi_inverse_solver,
1221
+ periodic_axes=periodic_axes,
1222
+ b_floor=b_floor,
1223
+ jacobian_floor=jacobian_floor,
1224
+ gmres_debug=gmres_debug,
1225
+ return_phi=True,
1226
+ with_diagnostics=with_diagnostics,
1227
+ )
1228
+ poisson_rhs, curvature_rhs, parallel_rhs, diffusion_rhs = _compute_4field_term_components(
1229
+ state,
1230
+ geometry=geometry,
1231
+ stencil_builder=stencil_builder,
1232
+ conservative_stencil_builder=conservative_stencil_builder,
1233
+ parameters=parameters,
1234
+ curvature_coefficients=curvature_coefficients,
1235
+ phi=phi,
1236
+ boundary_conditions=bcs,
1237
+ phi_face_projectors=phi_face_projectors,
1238
+ periodic_axes=periodic_axes,
1239
+ b_floor=b_floor,
1240
+ jacobian_floor=jacobian_floor,
1241
+ )
1242
+ result = Fci4FieldRhsResult(
1243
+ rhs=Fci4FieldState(
1244
+ density=curvature_rhs.density + parallel_rhs.density,
1245
+ omega=curvature_rhs.omega + parallel_rhs.omega,
1246
+ v_ion_parallel=curvature_rhs.v_ion_parallel + parallel_rhs.v_ion_parallel,
1247
+ v_electron_parallel=curvature_rhs.v_electron_parallel + parallel_rhs.v_electron_parallel,
1248
+ )
1249
+ )
1250
+ if return_phi:
1251
+ return result, timings, phi
1252
+ return result, timings
1253
+
1254
+
1255
+ __all__ = [
1256
+ "Fci4FieldBlobParameters",
1257
+ "Fci4FieldFreeDecayParameters",
1258
+ "Fci4FieldState",
1259
+ "Fci4FieldRhsParameters",
1260
+ "Fci4FieldRhsResult",
1261
+ "compute_4field_blob_rhs",
1262
+ "compute_4field_curvature",
1263
+ "compute_4field_diffusion",
1264
+ "compute_4field_free_decay_rhs",
1265
+ "compute_4field_rhs",
1266
+ "compute_4field_poisson_diffusion",
1267
+ ]