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,253 @@
1
+ """Rotating-ellipse non-axisymmetric FCI geometry constructor.
2
+
3
+ This module provides :func:`build_rotating_ellipse_geometry`, a self-contained
4
+ constructor for a genuinely non-axisymmetric flux-coordinate-independent (FCI)
5
+ geometry: a torus whose elliptical cross-section *rotates* as it is followed
6
+ around toroidally. This is the classical rotating-ellipse (``l = 2``)
7
+ configuration and the canonical minimal non-axisymmetric field for exercising
8
+ FCI parallel operators, because the flux surfaces genuinely change orientation
9
+ with the toroidal angle (the metric depends on all three logical coordinates).
10
+
11
+ The logical coordinates are ``(x, theta, zeta)`` with a minor-radius label ``x``
12
+ and periodic poloidal / toroidal angles ``theta`` / ``zeta``. The physical
13
+ embedding into Cartesian space is
14
+
15
+ p0 = (1 + delta) * x * cos(theta)
16
+ q0 = (1 - delta) * x * sin(theta)
17
+ lam = n_field_periods * zeta # ellipse orientation
18
+ p = cos(lam) * p0 - sin(lam) * q0
19
+ q = sin(lam) * p0 + cos(lam) * q0
20
+ R = r0 + p
21
+ X, Y, Z = R cos(zeta), R sin(zeta), q
22
+
23
+ so a surface ``x = const`` is an ellipse of elongation ``(1 + delta)/(1 - delta)``
24
+ whose major axis rotates ``n_field_periods`` times per toroidal turn. The metric
25
+ is obtained **by automatic differentiation** of this embedding
26
+ (``g_ij = d_i X . d_j X``) rather than by hand, which keeps the construction
27
+ exact and differentiable with respect to the shape parameters (elongation,
28
+ rotation) themselves.
29
+
30
+ The magnetic field is helical on the flux surfaces,
31
+ ``B^x = 0``, ``B^theta = iota * c_phi / J``, ``B^zeta = c_phi / J``, so its field
32
+ lines wind with rotational transform ``iota`` and never leave a surface. Pass
33
+ ``construct_fci_maps=True`` to trace the parallel FCI maps from this field.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import jax
39
+ import jax.numpy as jnp
40
+
41
+ from .embedding import metric_from_position_fn
42
+ from .fci_geometry import (
43
+ BFieldGeometry,
44
+ CellCenteredGrid3D,
45
+ FaceBFieldGeometry,
46
+ FaceMetricGeometry,
47
+ FciGeometry3D,
48
+ FciMaps3D,
49
+ Grid1D,
50
+ MetricGeometry,
51
+ Spacing3D,
52
+ _bmag_from_contravariant_components,
53
+ build_fci_maps_from_b_contravariant,
54
+ logical_grid_from_axis_vectors,
55
+ )
56
+
57
+ __all__ = ["build_rotating_ellipse_geometry", "rotating_ellipse_position"]
58
+
59
+
60
+ def rotating_ellipse_position(
61
+ x: jax.Array,
62
+ theta: jax.Array,
63
+ zeta: jax.Array,
64
+ *,
65
+ r0: float = 3.0,
66
+ elongation: float = 0.35,
67
+ n_field_periods: int = 1,
68
+ ) -> jax.Array:
69
+ """Cartesian ``(X, Y, Z)`` position of the rotating-ellipse embedding.
70
+
71
+ ``elongation`` is the ellipse deformation ``delta`` (the semi-axes are
72
+ ``(1 +/- delta) * x``); ``n_field_periods`` is how many times the ellipse
73
+ rotates per toroidal turn. Broadcasts over array-valued arguments and
74
+ returns the Cartesian components stacked on the last axis. ``r0`` and
75
+ ``elongation`` are kept traceable (no ``float`` cast), so this map is
76
+ differentiable with respect to the shape parameters themselves.
77
+ """
78
+
79
+ lam = float(n_field_periods) * zeta
80
+ cos_lam = jnp.cos(lam)
81
+ sin_lam = jnp.sin(lam)
82
+ p0 = (1.0 + elongation) * x * jnp.cos(theta)
83
+ q0 = (1.0 - elongation) * x * jnp.sin(theta)
84
+ p = cos_lam * p0 - sin_lam * q0
85
+ q = sin_lam * p0 + cos_lam * q0
86
+ major_radius = float(r0) + p
87
+ return jnp.stack(
88
+ (major_radius * jnp.cos(zeta), major_radius * jnp.sin(zeta), q),
89
+ axis=-1,
90
+ )
91
+
92
+
93
+ def build_rotating_ellipse_geometry(
94
+ shape: tuple[int, int, int],
95
+ *,
96
+ r0: float = 3.0,
97
+ x_min: float = 0.2,
98
+ x_max: float = 1.0,
99
+ elongation: float = 0.35,
100
+ n_field_periods: int = 1,
101
+ iota: float = 0.9,
102
+ c_phi: float = 3.0,
103
+ construct_fci_maps: bool = False,
104
+ map_substeps: int = 8,
105
+ limiter_radius: float | None = None,
106
+ ) -> FciGeometry3D:
107
+ """Build a rotating-ellipse non-axisymmetric FCI geometry.
108
+
109
+ Args:
110
+ shape: ``(nx, ny, nz)`` cell-centered grid resolution.
111
+ r0: major-radius offset of the torus.
112
+ x_min, x_max: minor-radius (``x``) label bounds.
113
+ elongation: ellipse deformation ``delta`` in ``[0, 1)``; ``0`` recovers
114
+ a circular (still non-axisymmetric only if rotated) cross-section,
115
+ larger values elongate it. The cross-section aspect ratio is
116
+ ``(1 + delta) / (1 - delta)``.
117
+ n_field_periods: ellipse rotations per toroidal turn (the field
118
+ periodicity). Must be a positive integer for a ``zeta``-periodic
119
+ geometry.
120
+ iota: rotational transform of the helical field.
121
+ c_phi: toroidal-field scaling constant.
122
+ construct_fci_maps: if ``True``, trace the parallel FCI maps from the
123
+ contravariant field; otherwise install identity/placeholder maps.
124
+ map_substeps: field-line-tracer substeps per toroidal cell when
125
+ ``construct_fci_maps`` is ``True``.
126
+ limiter_radius: if set, a toroidal limiter at the ``zeta = 0`` plane
127
+ opens every field line with ``x > limiter_radius``: those cells'
128
+ forward maps exit on the last toroidal plane and their backward maps
129
+ on the first, so the endpoint masks mark a scrape-off layer while
130
+ ``x <= limiter_radius`` remains a closed core. ``None`` (default)
131
+ keeps every field line closed.
132
+
133
+ Returns:
134
+ A fully populated :class:`FciGeometry3D` of the requested ``shape``.
135
+ """
136
+
137
+ nx, ny, nz = shape
138
+ x_faces = jnp.linspace(float(x_min), float(x_max), nx + 1, dtype=jnp.float64)
139
+ theta_faces = jnp.linspace(0.0, 2.0 * jnp.pi, ny + 1, dtype=jnp.float64)
140
+ zeta_faces = jnp.linspace(0.0, 2.0 * jnp.pi, nz + 1, dtype=jnp.float64)
141
+ grid = CellCenteredGrid3D(
142
+ x=Grid1D(centers=0.5 * (x_faces[:-1] + x_faces[1:]), faces=x_faces),
143
+ y=Grid1D(centers=0.5 * (theta_faces[:-1] + theta_faces[1:]), faces=theta_faces),
144
+ z=Grid1D(centers=0.5 * (zeta_faces[:-1] + zeta_faces[1:]), faces=zeta_faces),
145
+ )
146
+ target_shape = grid.shape
147
+
148
+ def _position(u: jax.Array) -> jax.Array:
149
+ return rotating_ellipse_position(
150
+ u[0], u[1], u[2],
151
+ r0=r0, elongation=elongation, n_field_periods=n_field_periods,
152
+ )
153
+
154
+ def _metric(logical_grid: jax.Array) -> MetricGeometry:
155
+ return metric_from_position_fn(_position, logical_grid)
156
+
157
+ def _bfield(metric: MetricGeometry) -> BFieldGeometry:
158
+ jacobian = metric.J
159
+ B_contra = jnp.stack(
160
+ (
161
+ jnp.zeros_like(jacobian),
162
+ float(iota) * float(c_phi) / jacobian,
163
+ float(c_phi) / jacobian,
164
+ ),
165
+ axis=-1,
166
+ )
167
+ Bmag = _bmag_from_contravariant_components(B_contra, metric.g_cov)
168
+ return BFieldGeometry(B_contra=B_contra, Bmag=Bmag)
169
+
170
+ def _logical(x_axis: jax.Array, y_axis: jax.Array, z_axis: jax.Array) -> jax.Array:
171
+ return logical_grid_from_axis_vectors(x_axis, y_axis, z_axis)
172
+
173
+ cell_metric = _metric(_logical(grid.x.centers, grid.y.centers, grid.z.centers))
174
+ cell_bfield = _bfield(cell_metric)
175
+ face_metric = FaceMetricGeometry(
176
+ x=_metric(_logical(grid.x.faces, grid.y.centers, grid.z.centers)),
177
+ y=_metric(_logical(grid.x.centers, grid.y.faces, grid.z.centers)),
178
+ z=_metric(_logical(grid.x.centers, grid.y.centers, grid.z.faces)),
179
+ )
180
+ face_bfield = FaceBFieldGeometry(
181
+ x=_bfield(face_metric.x),
182
+ y=_bfield(face_metric.y),
183
+ z=_bfield(face_metric.z),
184
+ )
185
+
186
+ if construct_fci_maps:
187
+ map_fields = build_fci_maps_from_b_contravariant(
188
+ grid,
189
+ cell_bfield.B_contra,
190
+ cell_bfield.Bmag,
191
+ periodic_axes=(False, True, True),
192
+ substeps=int(map_substeps),
193
+ )
194
+ else:
195
+ ones = jnp.ones(target_shape, dtype=jnp.float64)
196
+ zeros = jnp.zeros(target_shape, dtype=jnp.float64)
197
+ map_fields = {
198
+ "forward_x": zeros,
199
+ "forward_y": zeros,
200
+ "backward_x": zeros,
201
+ "backward_y": zeros,
202
+ "forward_endpoint_x": zeros,
203
+ "forward_endpoint_y": zeros,
204
+ "forward_endpoint_z": zeros,
205
+ "backward_endpoint_x": zeros,
206
+ "backward_endpoint_y": zeros,
207
+ "backward_endpoint_z": zeros,
208
+ "forward_length": ones,
209
+ "backward_length": ones,
210
+ "forward_boundary": zeros.astype(bool),
211
+ "backward_boundary": zeros.astype(bool),
212
+ }
213
+
214
+ if limiter_radius is not None:
215
+ # Toroidal limiter at the zeta = 0 plane: scrape-off-layer field lines
216
+ # (x > limiter_radius) strike it — forward traces exit on the last
217
+ # toroidal plane, backward traces on the first. The core stays closed.
218
+ sol_mask = grid.x.centers > float(limiter_radius)
219
+ forward_boundary = jnp.asarray(map_fields["forward_boundary"], dtype=bool)
220
+ backward_boundary = jnp.asarray(map_fields["backward_boundary"], dtype=bool)
221
+ map_fields["forward_boundary"] = forward_boundary.at[:, :, -1].set(forward_boundary[:, :, -1] | sol_mask[:, None])
222
+ map_fields["backward_boundary"] = backward_boundary.at[:, :, 0].set(backward_boundary[:, :, 0] | sol_mask[:, None])
223
+
224
+ maps = FciMaps3D(
225
+ forward_x=map_fields["forward_x"],
226
+ forward_y=map_fields["forward_y"],
227
+ backward_x=map_fields["backward_x"],
228
+ backward_y=map_fields["backward_y"],
229
+ forward_endpoint_x=map_fields["forward_endpoint_x"],
230
+ forward_endpoint_y=map_fields["forward_endpoint_y"],
231
+ forward_endpoint_z=map_fields["forward_endpoint_z"],
232
+ backward_endpoint_x=map_fields["backward_endpoint_x"],
233
+ backward_endpoint_y=map_fields["backward_endpoint_y"],
234
+ backward_endpoint_z=map_fields["backward_endpoint_z"],
235
+ forward_length=map_fields["forward_length"],
236
+ backward_length=map_fields["backward_length"],
237
+ forward_boundary=map_fields["forward_boundary"],
238
+ backward_boundary=map_fields["backward_boundary"],
239
+ )
240
+ spacing = Spacing3D(
241
+ dx=jnp.broadcast_to(grid.x.widths[:, None, None], target_shape),
242
+ dy=jnp.broadcast_to(grid.y.widths[None, :, None], target_shape),
243
+ dz=jnp.broadcast_to(grid.z.widths[None, None, :], target_shape),
244
+ )
245
+ return FciGeometry3D(
246
+ grid=grid,
247
+ maps=maps,
248
+ spacing=spacing,
249
+ cell_metric=cell_metric,
250
+ face_metric=face_metric,
251
+ cell_bfield=cell_bfield,
252
+ face_bfield=face_bfield,
253
+ )
@@ -0,0 +1,225 @@
1
+ """Shifted-torus non-axisymmetric FCI geometry constructor.
2
+
3
+ This module provides :func:`build_shifted_torus_geometry`, a self-contained
4
+ constructor for a non-axisymmetric flux-coordinate-independent (FCI) metric on a
5
+ shifted-torus flux tube. The logical coordinates are ``(x, theta, zeta)`` with a
6
+ physical radial coordinate ``x`` and periodic poloidal/toroidal angles. The
7
+ poloidal angle is sheared by a radial shift ``Theta = theta + sigma * (x - x_mid)``
8
+ so that, for ``sigma != 0``, the metric acquires genuine off-diagonal (``g12`` /
9
+ ``g_12``) cross terms and the geometry is non-axisymmetric.
10
+
11
+ The construction was verified as a manufactured-solution scaffold in
12
+ ``tests/test_mms_shifted_torus_2_field.py`` (the two-field FCI MMS convergence
13
+ harness). It is promoted here so the reduced drift-reduced FCI model can be
14
+ exercised and differentiated on non-axisymmetric geometry from the package
15
+ itself.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import jax.numpy as jnp
21
+
22
+ from .fci_geometry import (
23
+ BFieldGeometry,
24
+ CellCenteredGrid3D,
25
+ FaceBFieldGeometry,
26
+ FaceMetricGeometry,
27
+ FciGeometry3D,
28
+ FciMaps3D,
29
+ Grid1D,
30
+ MetricGeometry,
31
+ Spacing3D,
32
+ build_fci_maps_from_b_contravariant,
33
+ logical_grid_from_axis_vectors,
34
+ )
35
+
36
+ __all__ = ["build_shifted_torus_geometry"]
37
+
38
+
39
+ def build_shifted_torus_geometry(
40
+ shape: tuple[int, int, int],
41
+ *,
42
+ x_min: float = 0.15,
43
+ x_max: float = 1.0,
44
+ r0: float = 3.0,
45
+ alpha_value: float = 0.25,
46
+ iota: float = 1.1,
47
+ c_phi: float = 3.0,
48
+ sigma: float = 0.0,
49
+ construct_fci_maps: bool = False,
50
+ B_contravariant: jnp.ndarray | None = None,
51
+ ) -> FciGeometry3D:
52
+ """Build a shifted-torus non-axisymmetric FCI geometry.
53
+
54
+ The logical coordinates are ``(x, theta, zeta)`` with periodic ``theta`` and
55
+ ``zeta``. The helper follows the same `FciGeometry3D` construction pattern used
56
+ in `test_fci_operators.py`, but uses the physical radial coordinate directly and
57
+ a shifted poloidal angle ``Theta = theta + sigma * (x - x_mid)``. A nonzero
58
+ ``sigma`` makes the metric non-axisymmetric (it introduces the off-diagonal
59
+ ``g12`` / ``g_12`` terms below).
60
+
61
+ Parameters mirror the module-level constants used by the two-field MMS
62
+ convergence harness; the defaults reproduce that verified geometry.
63
+
64
+ Args:
65
+ shape: ``(nx, ny, nz)`` cell-centered grid resolution.
66
+ x_min, x_max: physical radial coordinate bounds.
67
+ r0: major-radius offset of the shifted torus.
68
+ alpha_value: Shafranov-like shift amplitude.
69
+ iota: rotational transform used to build the contravariant field.
70
+ c_phi: toroidal field scaling constant.
71
+ sigma: poloidal shear; ``sigma != 0`` yields a non-axisymmetric metric.
72
+ construct_fci_maps: if ``True``, trace the parallel FCI maps from the
73
+ contravariant field; otherwise install identity/placeholder maps.
74
+ B_contravariant: optional override for the contravariant field
75
+ components; when ``None`` the analytic field is used.
76
+
77
+ Returns:
78
+ A fully populated :class:`FciGeometry3D` of the requested ``shape``.
79
+ """
80
+
81
+ nx, ny, nz = shape
82
+ x_centers = jnp.linspace(float(x_min), float(x_max), nx, dtype=jnp.float64)
83
+ theta_centers = jnp.linspace(0.0, 2.0 * jnp.pi, ny, endpoint=False, dtype=jnp.float64)
84
+ zeta_centers = jnp.linspace(0.0, 2.0 * jnp.pi, nz, endpoint=False, dtype=jnp.float64)
85
+ grid = CellCenteredGrid3D(
86
+ x=Grid1D.from_centers(x_centers),
87
+ y=Grid1D.from_centers(theta_centers),
88
+ z=Grid1D.from_centers(zeta_centers),
89
+ )
90
+ target_shape = grid.shape
91
+
92
+ def _logical_grid(x_axis: jnp.ndarray, y_axis: jnp.ndarray, z_axis: jnp.ndarray) -> jnp.ndarray:
93
+ return logical_grid_from_axis_vectors(x_axis, y_axis, z_axis)
94
+
95
+ def _metric(logical_grid: jnp.ndarray) -> MetricGeometry:
96
+ x = logical_grid[..., 0]
97
+ theta = logical_grid[..., 1]
98
+ x_mid = 0.5 * (float(x_min) + float(x_max))
99
+ theta_shift = theta + float(sigma) * (x - x_mid)
100
+ cos_theta = jnp.cos(theta_shift)
101
+ sin_theta = jnp.sin(theta_shift)
102
+ R = float(r0) + float(alpha_value) * x + x * cos_theta
103
+ jacobian = R * x * (1.0 + float(alpha_value) * cos_theta)
104
+ jacobian = jnp.where(jnp.abs(jacobian) < 1.0e-14, 1.0e-14, jacobian)
105
+ g11 = 1.0 / (1.0 + float(alpha_value) * cos_theta) ** 2
106
+ g12 = float(alpha_value) * sin_theta / (x * (1.0 + float(alpha_value) * cos_theta) ** 2)
107
+ g13 = jnp.zeros_like(x)
108
+ g22 = (1.0 + 2.0 * float(alpha_value) * cos_theta + float(alpha_value) ** 2) / (x**2 * (1.0 + float(alpha_value) * cos_theta) ** 2)
109
+ g23 = jnp.zeros_like(x)
110
+ g33 = 1.0 / (R**2)
111
+ g_11 = 1.0 + 2.0 * float(alpha_value) * cos_theta + float(alpha_value) ** 2
112
+ g_12 = -float(alpha_value) * x * sin_theta
113
+ g_13 = jnp.zeros_like(x)
114
+ g_22 = x**2
115
+ g_23 = jnp.zeros_like(x)
116
+ g_33 = R**2
117
+ return MetricGeometry(
118
+ J=jacobian,
119
+ g11=g11,
120
+ g22=g22,
121
+ g33=g33,
122
+ g12=g12,
123
+ g13=g13,
124
+ g23=g23,
125
+ g_11=g_11,
126
+ g_22=g_22,
127
+ g_33=g_33,
128
+ g_12=g_12,
129
+ g_13=g_13,
130
+ g_23=g_23,
131
+ )
132
+
133
+ def _bfield(logical_grid: jnp.ndarray, metric: MetricGeometry) -> BFieldGeometry:
134
+ x = logical_grid[..., 0]
135
+ theta = logical_grid[..., 1]
136
+ x_mid = 0.5 * (float(x_min) + float(x_max))
137
+ theta_shift = theta + float(sigma) * (x - x_mid)
138
+ cos_theta = jnp.cos(theta_shift)
139
+ R = float(r0) + float(alpha_value) * x + x * cos_theta
140
+ jacobian = metric.J
141
+ if B_contravariant is None:
142
+ B_contra = jnp.stack(
143
+ (
144
+ jnp.zeros_like(jacobian),
145
+ float(iota) * float(c_phi) / jacobian,
146
+ float(c_phi) / jacobian,
147
+ ),
148
+ axis=-1,
149
+ )
150
+ else:
151
+ B_contra = jnp.asarray(B_contravariant, dtype=jnp.float64)
152
+ Bmag = jnp.sqrt((float(iota) ** 2) * x**2 + R**2) * float(c_phi) / jacobian
153
+ return BFieldGeometry(B_contra=B_contra, Bmag=Bmag)
154
+
155
+ cell_logical_grid = _logical_grid(grid.x.centers, grid.y.centers, grid.z.centers)
156
+ cell_metric = _metric(cell_logical_grid)
157
+ cell_bfield = _bfield(cell_logical_grid, cell_metric)
158
+ face_metric = FaceMetricGeometry(
159
+ x=_metric(_logical_grid(grid.x.faces, grid.y.centers, grid.z.centers)),
160
+ y=_metric(_logical_grid(grid.x.centers, grid.y.faces, grid.z.centers)),
161
+ z=_metric(_logical_grid(grid.x.centers, grid.y.centers, grid.z.faces)),
162
+ )
163
+ face_bfield = FaceBFieldGeometry(
164
+ x=_bfield(_logical_grid(grid.x.faces, grid.y.centers, grid.z.centers), face_metric.x),
165
+ y=_bfield(_logical_grid(grid.x.centers, grid.y.faces, grid.z.centers), face_metric.y),
166
+ z=_bfield(_logical_grid(grid.x.centers, grid.y.centers, grid.z.faces), face_metric.z),
167
+ )
168
+
169
+ if construct_fci_maps:
170
+ map_fields = build_fci_maps_from_b_contravariant(
171
+ grid,
172
+ cell_bfield.B_contra,
173
+ cell_bfield.Bmag,
174
+ periodic_axes=(False, True, True),
175
+ )
176
+ else:
177
+ ones = jnp.ones(target_shape, dtype=jnp.float64)
178
+ zeros = jnp.zeros(target_shape, dtype=jnp.float64)
179
+ map_fields = {
180
+ "forward_x": zeros,
181
+ "forward_y": zeros,
182
+ "backward_x": zeros,
183
+ "backward_y": zeros,
184
+ "forward_endpoint_x": zeros,
185
+ "forward_endpoint_y": zeros,
186
+ "forward_endpoint_z": zeros,
187
+ "backward_endpoint_x": zeros,
188
+ "backward_endpoint_y": zeros,
189
+ "backward_endpoint_z": zeros,
190
+ "forward_length": ones,
191
+ "backward_length": ones,
192
+ "forward_boundary": zeros.astype(bool),
193
+ "backward_boundary": zeros.astype(bool),
194
+ }
195
+
196
+ maps = FciMaps3D(
197
+ forward_x=map_fields["forward_x"],
198
+ forward_y=map_fields["forward_y"],
199
+ backward_x=map_fields["backward_x"],
200
+ backward_y=map_fields["backward_y"],
201
+ forward_endpoint_x=map_fields["forward_endpoint_x"],
202
+ forward_endpoint_y=map_fields["forward_endpoint_y"],
203
+ forward_endpoint_z=map_fields["forward_endpoint_z"],
204
+ backward_endpoint_x=map_fields["backward_endpoint_x"],
205
+ backward_endpoint_y=map_fields["backward_endpoint_y"],
206
+ backward_endpoint_z=map_fields["backward_endpoint_z"],
207
+ forward_length=map_fields["forward_length"],
208
+ backward_length=map_fields["backward_length"],
209
+ forward_boundary=map_fields["forward_boundary"],
210
+ backward_boundary=map_fields["backward_boundary"],
211
+ )
212
+ spacing = Spacing3D(
213
+ dx=jnp.broadcast_to(grid.x.widths[:, None, None], target_shape),
214
+ dy=jnp.broadcast_to(grid.y.widths[None, :, None], target_shape),
215
+ dz=jnp.broadcast_to(grid.z.widths[None, None, :], target_shape),
216
+ )
217
+ return FciGeometry3D(
218
+ grid=grid,
219
+ maps=maps,
220
+ spacing=spacing,
221
+ cell_metric=cell_metric,
222
+ face_metric=face_metric,
223
+ cell_bfield=cell_bfield,
224
+ face_bfield=face_bfield,
225
+ )