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,287 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import jax.numpy as jnp
6
+ import numpy as np
7
+
8
+ from .fci_maps import FciMaps
9
+ from .metric_tensor import MetricTensor3D
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class SyntheticStellaratorGeometry:
14
+ """Analytic non-axisymmetric stellarator-like geometry for validation."""
15
+
16
+ coordinates_x: jnp.ndarray
17
+ coordinates_y: jnp.ndarray
18
+ coordinates_z: jnp.ndarray
19
+ radial: jnp.ndarray
20
+ toroidal_angle: jnp.ndarray
21
+ poloidal_angle: jnp.ndarray
22
+ iota: jnp.ndarray
23
+ curvature: jnp.ndarray
24
+ connection_length: jnp.ndarray
25
+ metric: MetricTensor3D
26
+ maps: FciMaps
27
+ metadata: dict[str, float | int | str]
28
+
29
+ @property
30
+ def shape(self) -> tuple[int, int, int]:
31
+ return tuple(int(value) for value in self.radial.shape)
32
+
33
+
34
+ def build_synthetic_stellarator_geometry(
35
+ *,
36
+ nx: int = 36,
37
+ ny: int = 32,
38
+ nz: int = 64,
39
+ major_radius: float = 3.8,
40
+ minor_radius: float = 0.7,
41
+ elongation: float = 1.45,
42
+ field_periods: int = 5,
43
+ island_mode: int = 2,
44
+ island_amplitude: float = 0.030,
45
+ mirror_amplitude: float = 0.16,
46
+ iota_axis: float = 0.38,
47
+ iota_edge: float = 0.58,
48
+ ) -> SyntheticStellaratorGeometry:
49
+ """Construct a deterministic analytic 3D stellarator/SOL validation geometry."""
50
+
51
+ s_1d = np.linspace(0.08, 1.0, nx)
52
+ phi_1d = np.linspace(0.0, 2.0 * np.pi, ny, endpoint=False)
53
+ theta_1d = np.linspace(0.0, 2.0 * np.pi, nz, endpoint=False)
54
+ s, phi, theta = np.meshgrid(s_1d, phi_1d, theta_1d, indexing="ij")
55
+
56
+ iota = iota_axis + (iota_edge - iota_axis) * s**1.7
57
+ rotating_angle = theta - 0.38 * np.sin(field_periods * phi)
58
+ radial_size = minor_radius * s
59
+ ellipse_modulation = (
60
+ 1.0
61
+ + 0.10 * np.cos(field_periods * phi)
62
+ + 0.035 * s * np.cos(island_mode * theta - field_periods * phi)
63
+ )
64
+ vertical_modulation = 1.0 + 0.08 * np.sin(field_periods * phi + 0.4)
65
+ r_major = major_radius + radial_size * ellipse_modulation * np.cos(rotating_angle)
66
+ z_vertical = elongation * radial_size * vertical_modulation * np.sin(rotating_angle)
67
+ x_cart = r_major * np.cos(phi)
68
+ y_cart = r_major * np.sin(phi)
69
+
70
+ metric = _metric_from_coordinates(
71
+ x_cart,
72
+ y_cart,
73
+ z_vertical,
74
+ s_1d=s_1d,
75
+ phi_1d=phi_1d,
76
+ theta_1d=theta_1d,
77
+ Bxy=_magnetic_field_strength(
78
+ s=s,
79
+ phi=phi,
80
+ theta=theta,
81
+ field_periods=field_periods,
82
+ island_mode=island_mode,
83
+ mirror_amplitude=mirror_amplitude,
84
+ ),
85
+ )
86
+ maps = _build_maps(
87
+ s=s,
88
+ phi=phi,
89
+ theta=theta,
90
+ iota=iota,
91
+ nx=nx,
92
+ ny=ny,
93
+ nz=nz,
94
+ field_periods=field_periods,
95
+ island_mode=island_mode,
96
+ island_amplitude=island_amplitude,
97
+ )
98
+ curvature = _curvature_proxy(
99
+ s=s,
100
+ phi=phi,
101
+ theta=theta,
102
+ field_periods=field_periods,
103
+ island_mode=island_mode,
104
+ major_radius=major_radius,
105
+ )
106
+ connection_length = _estimate_connection_length(maps, metric, max_steps=max(2 * ny, 16))
107
+ return SyntheticStellaratorGeometry(
108
+ coordinates_x=jnp.asarray(x_cart, dtype=jnp.float64),
109
+ coordinates_y=jnp.asarray(y_cart, dtype=jnp.float64),
110
+ coordinates_z=jnp.asarray(z_vertical, dtype=jnp.float64),
111
+ radial=jnp.asarray(s, dtype=jnp.float64),
112
+ toroidal_angle=jnp.asarray(phi, dtype=jnp.float64),
113
+ poloidal_angle=jnp.asarray(theta, dtype=jnp.float64),
114
+ iota=jnp.asarray(iota, dtype=jnp.float64),
115
+ curvature=jnp.asarray(curvature, dtype=jnp.float64),
116
+ connection_length=jnp.asarray(connection_length, dtype=jnp.float64),
117
+ metric=metric,
118
+ maps=maps,
119
+ metadata={
120
+ "geometry_family": "analytic_non_axisymmetric_stellarator",
121
+ "nx": int(nx),
122
+ "ny": int(ny),
123
+ "nz": int(nz),
124
+ "major_radius": float(major_radius),
125
+ "minor_radius": float(minor_radius),
126
+ "elongation": float(elongation),
127
+ "field_periods": int(field_periods),
128
+ "island_mode": int(island_mode),
129
+ "island_amplitude": float(island_amplitude),
130
+ "mirror_amplitude": float(mirror_amplitude),
131
+ "iota_axis": float(iota_axis),
132
+ "iota_edge": float(iota_edge),
133
+ },
134
+ )
135
+
136
+
137
+ def _magnetic_field_strength(
138
+ *,
139
+ s: np.ndarray,
140
+ phi: np.ndarray,
141
+ theta: np.ndarray,
142
+ field_periods: int,
143
+ island_mode: int,
144
+ mirror_amplitude: float,
145
+ ) -> np.ndarray:
146
+ mirror = mirror_amplitude * np.cos(field_periods * phi - island_mode * theta)
147
+ radial = 0.10 * (s - np.mean(s))
148
+ corrugation = 0.035 * s * np.sin((field_periods + 1) * phi + theta)
149
+ return 1.0 + mirror + radial + corrugation
150
+
151
+
152
+ def _metric_from_coordinates(
153
+ x_cart: np.ndarray,
154
+ y_cart: np.ndarray,
155
+ z_cart: np.ndarray,
156
+ *,
157
+ s_1d: np.ndarray,
158
+ phi_1d: np.ndarray,
159
+ theta_1d: np.ndarray,
160
+ Bxy: np.ndarray,
161
+ ) -> MetricTensor3D:
162
+ ds = float(s_1d[1] - s_1d[0]) if s_1d.size > 1 else 1.0
163
+ dphi = float(phi_1d[1] - phi_1d[0]) if phi_1d.size > 1 else 2.0 * np.pi
164
+ dtheta = float(theta_1d[1] - theta_1d[0]) if theta_1d.size > 1 else 2.0 * np.pi
165
+
166
+ derivs = []
167
+ for coords in (x_cart, y_cart, z_cart):
168
+ derivs.append(np.gradient(coords, ds, dphi, dtheta, edge_order=2))
169
+
170
+ r_s = np.stack([derivs[0][0], derivs[1][0], derivs[2][0]], axis=-1)
171
+ r_phi = np.stack([derivs[0][1], derivs[1][1], derivs[2][1]], axis=-1)
172
+ r_theta = np.stack([derivs[0][2], derivs[1][2], derivs[2][2]], axis=-1)
173
+ cov = np.empty(x_cart.shape + (3, 3), dtype=np.float64)
174
+ basis = (r_s, r_phi, r_theta)
175
+ for i, left in enumerate(basis):
176
+ for j, right in enumerate(basis):
177
+ cov[..., i, j] = np.sum(left * right, axis=-1)
178
+ determinant = np.linalg.det(cov)
179
+ regularization = np.maximum(1.0e-12, 1.0e-11 * np.nanmax(np.abs(determinant)))
180
+ bad = determinant <= regularization
181
+ if np.any(bad):
182
+ cov[bad] = cov[bad] + np.eye(3) * regularization
183
+ contrav = np.linalg.inv(cov)
184
+ jacobian = np.sqrt(np.maximum(np.linalg.det(cov), regularization))
185
+ dx = np.full_like(x_cart, ds)
186
+ dy = np.full_like(x_cart, dphi)
187
+ dz = np.full_like(x_cart, dtheta)
188
+ return MetricTensor3D(
189
+ dx=jnp.asarray(dx, dtype=jnp.float64),
190
+ dy=jnp.asarray(dy, dtype=jnp.float64),
191
+ dz=jnp.asarray(dz, dtype=jnp.float64),
192
+ J=jnp.asarray(jacobian, dtype=jnp.float64),
193
+ Bxy=jnp.asarray(Bxy, dtype=jnp.float64),
194
+ g11=jnp.asarray(contrav[..., 0, 0], dtype=jnp.float64),
195
+ g22=jnp.asarray(contrav[..., 1, 1], dtype=jnp.float64),
196
+ g33=jnp.asarray(contrav[..., 2, 2], dtype=jnp.float64),
197
+ g12=jnp.asarray(contrav[..., 0, 1], dtype=jnp.float64),
198
+ g13=jnp.asarray(contrav[..., 0, 2], dtype=jnp.float64),
199
+ g23=jnp.asarray(contrav[..., 1, 2], dtype=jnp.float64),
200
+ g_11=jnp.asarray(cov[..., 0, 0], dtype=jnp.float64),
201
+ g_22=jnp.asarray(cov[..., 1, 1], dtype=jnp.float64),
202
+ g_33=jnp.asarray(cov[..., 2, 2], dtype=jnp.float64),
203
+ g_12=jnp.asarray(cov[..., 0, 1], dtype=jnp.float64),
204
+ g_13=jnp.asarray(cov[..., 0, 2], dtype=jnp.float64),
205
+ g_23=jnp.asarray(cov[..., 1, 2], dtype=jnp.float64),
206
+ )
207
+
208
+
209
+ def _build_maps(
210
+ *,
211
+ s: np.ndarray,
212
+ phi: np.ndarray,
213
+ theta: np.ndarray,
214
+ iota: np.ndarray,
215
+ nx: int,
216
+ ny: int,
217
+ nz: int,
218
+ field_periods: int,
219
+ island_mode: int,
220
+ island_amplitude: float,
221
+ ) -> FciMaps:
222
+ dphi = 2.0 * np.pi / float(ny)
223
+ phase = island_mode * theta - field_periods * phi
224
+ island_envelope = np.exp(-((s - 0.74) / 0.19) ** 2)
225
+ dx_dphi = island_amplitude * island_envelope * np.sin(phase)
226
+ dtheta_dphi = iota + 0.07 * island_envelope * np.cos(phase)
227
+ x_index = np.arange(nx, dtype=np.float64)[:, None, None]
228
+ z_index = np.arange(nz, dtype=np.float64)[None, None, :]
229
+ forward_x = x_index + dx_dphi * dphi * (nx - 1)
230
+ backward_x = x_index - dx_dphi * dphi * (nx - 1)
231
+ forward_z = z_index + dtheta_dphi * dphi * nz / (2.0 * np.pi)
232
+ backward_z = z_index - dtheta_dphi * dphi * nz / (2.0 * np.pi)
233
+ return FciMaps(
234
+ forward_x=jnp.asarray(forward_x, dtype=jnp.float64),
235
+ forward_z=jnp.asarray(forward_z, dtype=jnp.float64),
236
+ backward_x=jnp.asarray(backward_x, dtype=jnp.float64),
237
+ backward_z=jnp.asarray(backward_z, dtype=jnp.float64),
238
+ forward_boundary=jnp.asarray((forward_x < 0.0) | (forward_x > nx - 1)),
239
+ backward_boundary=jnp.asarray((backward_x < 0.0) | (backward_x > nx - 1)),
240
+ dphi=float(dphi),
241
+ )
242
+
243
+
244
+ def _curvature_proxy(
245
+ *,
246
+ s: np.ndarray,
247
+ phi: np.ndarray,
248
+ theta: np.ndarray,
249
+ field_periods: int,
250
+ island_mode: int,
251
+ major_radius: float,
252
+ ) -> np.ndarray:
253
+ bad_curvature = -np.cos(theta) / major_radius
254
+ non_axisymmetric = 0.35 * np.cos(field_periods * phi - island_mode * theta) / major_radius
255
+ radial_weight = 0.4 + 0.6 * s
256
+ return radial_weight * (bad_curvature + non_axisymmetric)
257
+
258
+
259
+ def _estimate_connection_length(maps: FciMaps, metric: MetricTensor3D, *, max_steps: int) -> np.ndarray:
260
+ nx, ny, nz = maps.shape
261
+ x0 = np.arange(nx, dtype=np.float64)[:, None, None]
262
+ z0 = np.arange(nz, dtype=np.float64)[None, None, :]
263
+ x = np.broadcast_to(x0, (nx, ny, nz)).copy()
264
+ z = np.broadcast_to(z0, (nx, ny, nz)).copy()
265
+ alive = np.ones((nx, ny, nz), dtype=bool)
266
+ steps = np.zeros((nx, ny, nz), dtype=np.float64)
267
+ step_length = float(np.nanmean(np.sqrt(np.asarray(metric.g_22)))) * float(maps.dphi)
268
+ forward_x = np.asarray(maps.forward_x)
269
+ forward_z = np.asarray(maps.forward_z)
270
+ for _ in range(max_steps):
271
+ i = np.clip(np.rint(x).astype(np.int64), 0, nx - 1)
272
+ k = np.mod(np.rint(z).astype(np.int64), nz)
273
+ j = np.broadcast_to(np.arange(ny, dtype=np.int64)[None, :, None], (nx, ny, nz))
274
+ next_x = forward_x[i, j, k]
275
+ next_z = forward_z[i, j, k]
276
+ still_inside = (next_x >= 0.0) & (next_x <= nx - 1.0)
277
+ steps = np.where(alive & still_inside, steps + 1.0, steps)
278
+ alive = alive & still_inside
279
+ x = np.where(alive, next_x, x)
280
+ z = np.where(alive, next_z, z)
281
+ radial = np.broadcast_to(x0 / float(max(nx - 1, 1)), (nx, ny, nz))
282
+ radial_shift = np.abs(forward_x - x0)
283
+ island_lobe = np.exp(-((radial - 0.76) / 0.16) ** 2) * (1.0 + 2.5 * radial_shift)
284
+ wall_distance = np.maximum(1.0 - radial, 0.02)
285
+ proxy_steps = 3.0 + 42.0 * wall_distance + 16.0 * island_lobe
286
+ bounded_steps = np.minimum(np.maximum(steps, 1.0), proxy_steps)
287
+ return bounded_steps * step_length