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,314 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import jax.numpy as jnp
9
+ from matplotlib import pyplot as plt
10
+ import numpy as np
11
+
12
+ from ..geometry import build_essos_imported_fci_geometry
13
+ from ..native.fci import (
14
+ conservative_parallel_diffusion_fci,
15
+ grad_parallel_fci,
16
+ laplace_parallel_fci,
17
+ )
18
+ from .publication_plotting import save_publication_figure, style_axis
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class EssosVmecClosedFieldArtifacts:
23
+ report_json_path: Path
24
+ arrays_npz_path: Path
25
+ plot_png_path: Path
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class EssosVmecClosedFieldDryRunArtifacts:
30
+ contract_json_path: Path
31
+
32
+
33
+ def create_essos_vmec_closed_field_package(
34
+ *,
35
+ output_root: str | Path,
36
+ case_label: str = "essos_vmec_closed_field_campaign",
37
+ coil_json_path: str | Path | None = None,
38
+ vmec_wout_path: str | Path | None = None,
39
+ essos_root: str | Path | None = None,
40
+ nx: int = 5,
41
+ ny: int = 8,
42
+ nz: int = 20,
43
+ rho_min: float = 0.20,
44
+ rho_max: float = 0.82,
45
+ ) -> EssosVmecClosedFieldArtifacts:
46
+ """Write the VMEC closed-field validation campaign package."""
47
+
48
+ root = Path(output_root)
49
+ data_dir = root / "data"
50
+ images_dir = root / "images"
51
+ data_dir.mkdir(parents=True, exist_ok=True)
52
+ images_dir.mkdir(parents=True, exist_ok=True)
53
+ geometry = build_essos_imported_fci_geometry(
54
+ coil_json_path=coil_json_path,
55
+ vmec_wout_path=vmec_wout_path,
56
+ essos_root=essos_root,
57
+ map_source="vmec",
58
+ nx=nx,
59
+ ny=ny,
60
+ nz=nz,
61
+ rho_min=rho_min,
62
+ rho_max=rho_max,
63
+ )
64
+ report, arrays = build_essos_vmec_closed_field_report(geometry)
65
+ report_json_path = data_dir / f"{case_label}.json"
66
+ report_json_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
67
+ arrays_npz_path = data_dir / f"{case_label}.npz"
68
+ np.savez_compressed(arrays_npz_path, **arrays)
69
+ plot_png_path = images_dir / f"{case_label}.png"
70
+ save_essos_vmec_closed_field_plot(report, arrays, plot_png_path)
71
+ return EssosVmecClosedFieldArtifacts(
72
+ report_json_path=report_json_path,
73
+ arrays_npz_path=arrays_npz_path,
74
+ plot_png_path=plot_png_path,
75
+ )
76
+
77
+
78
+ def create_essos_vmec_closed_field_dry_run_package(
79
+ *,
80
+ output_root: str | Path,
81
+ case_label: str = "essos_vmec_closed_field_campaign",
82
+ nx: int = 5,
83
+ ny: int = 8,
84
+ nz: int = 20,
85
+ rho_min: float = 0.20,
86
+ rho_max: float = 0.82,
87
+ ) -> EssosVmecClosedFieldDryRunArtifacts:
88
+ """Write a self-contained contract for the live VMEC closed-field gate."""
89
+
90
+ root = Path(output_root)
91
+ data_dir = root / "data"
92
+ data_dir.mkdir(parents=True, exist_ok=True)
93
+ contract = {
94
+ "case": "essos_vmec_closed_field_dry_run_contract",
95
+ "schema_version": 1,
96
+ "self_contained": True,
97
+ "execution_mode": "dry_run",
98
+ "requires_essos_runtime": False,
99
+ "live_run_requires_essos_runtime": True,
100
+ "map_source": "vmec",
101
+ "case_label": str(case_label),
102
+ "output_root": str(root),
103
+ "planned_artifacts": {
104
+ "report_json": str(data_dir / f"{case_label}.json"),
105
+ "arrays_npz": str(data_dir / f"{case_label}.npz"),
106
+ "plot_png": str(root / "images" / f"{case_label}.png"),
107
+ "dry_run_contract_json": str(data_dir / f"{case_label}_dry_run_contract.json"),
108
+ },
109
+ "grid": {
110
+ "shape": [int(nx), int(ny), int(nz)],
111
+ "nx": int(nx),
112
+ "ny": int(ny),
113
+ "nz": int(nz),
114
+ "rho_min": float(rho_min),
115
+ "rho_max": float(rho_max),
116
+ },
117
+ "claim_scope": (
118
+ "VMEC closed-field validation: periodic FCI maps, zero endpoint "
119
+ "masks, constant-state operator checks, and no target/sheath/"
120
+ "recycling/neutral-loss semantics."
121
+ ),
122
+ "required_live_gates": [
123
+ "forward_boundary_fraction == 0",
124
+ "backward_boundary_fraction == 0",
125
+ "endpoint_fraction == 0",
126
+ "constant_grad_parallel_linf < tolerance",
127
+ "constant_laplace_parallel_linf < tolerance",
128
+ "constant_conservative_parallel_diffusion_linf < tolerance",
129
+ "magnetic_field_modulation > 1.01",
130
+ ],
131
+ "passed": True,
132
+ }
133
+ contract_json_path = data_dir / f"{case_label}_dry_run_contract.json"
134
+ contract_json_path.write_text(json.dumps(contract, indent=2, sort_keys=True) + "\n", encoding="utf-8")
135
+ return EssosVmecClosedFieldDryRunArtifacts(contract_json_path=contract_json_path)
136
+
137
+
138
+ def build_essos_vmec_closed_field_report(geometry: Any) -> tuple[dict[str, Any], dict[str, np.ndarray]]:
139
+ """Build closed-field invariants from a VMEC-map geometry object."""
140
+
141
+ shape = tuple(int(value) for value in geometry.shape)
142
+ bmag = np.asarray(geometry.magnetic_field_magnitude, dtype=np.float64)
143
+ connection = np.asarray(geometry.connection_length, dtype=np.float64)
144
+ forward_boundary = np.asarray(geometry.maps.forward_boundary, dtype=bool)
145
+ backward_boundary = np.asarray(geometry.maps.backward_boundary, dtype=bool)
146
+ forward_x = np.asarray(geometry.maps.forward_x, dtype=np.float64)
147
+ forward_z = np.asarray(geometry.maps.forward_z, dtype=np.float64)
148
+ backward_x = np.asarray(geometry.maps.backward_x, dtype=np.float64)
149
+ backward_z = np.asarray(geometry.maps.backward_z, dtype=np.float64)
150
+ endpoint = forward_boundary | backward_boundary
151
+ ones = jnp.ones(shape, dtype=jnp.float64)
152
+ coefficient = jnp.ones(shape, dtype=jnp.float64)
153
+ constant_grad = np.asarray(grad_parallel_fci(ones, geometry.maps), dtype=np.float64)
154
+ constant_laplace = np.asarray(laplace_parallel_fci(ones, geometry.maps), dtype=np.float64)
155
+ constant_diffusion = np.asarray(
156
+ conservative_parallel_diffusion_fci(
157
+ ones,
158
+ coefficient,
159
+ geometry.maps,
160
+ jacobian=geometry.metric.J,
161
+ ),
162
+ dtype=np.float64,
163
+ )
164
+
165
+ forward_shift = _periodic_cell_delta(forward_z - np.arange(shape[2], dtype=np.float64)[None, None, :], shape[2])
166
+ backward_shift = _periodic_cell_delta(backward_z - np.arange(shape[2], dtype=np.float64)[None, None, :], shape[2])
167
+ report: dict[str, Any] = {
168
+ "case": "essos_vmec_closed_field_campaign",
169
+ "source": "ESSOS VMEC-coordinate closed-field maps with DRBX FCI operator checks",
170
+ "map_source": "vmec",
171
+ "geometry": dict(geometry.metadata),
172
+ "shape": [int(value) for value in shape],
173
+ "claim_scope": (
174
+ "Closed-field VMEC control: endpoint masks, target losses, sheath "
175
+ "sources, recycling, and neutral-loss semantics are disabled."
176
+ ),
177
+ "target_semantics_applied": False,
178
+ "sheath_recycling_semantics_applied": False,
179
+ "neutral_loss_semantics_applied": False,
180
+ "forward_boundary_fraction": float(np.mean(forward_boundary)),
181
+ "backward_boundary_fraction": float(np.mean(backward_boundary)),
182
+ "endpoint_fraction": float(np.mean(endpoint)),
183
+ "map_coordinate_finite_fraction": float(
184
+ np.mean(
185
+ np.isfinite(forward_x)
186
+ & np.isfinite(forward_z)
187
+ & np.isfinite(backward_x)
188
+ & np.isfinite(backward_z)
189
+ )
190
+ ),
191
+ "magnetic_field_modulation": float(np.max(bmag) / max(float(np.min(bmag)), 1.0e-30)),
192
+ "connection_length_min": float(np.min(connection)),
193
+ "connection_length_mean": float(np.mean(connection)),
194
+ "connection_length_max": float(np.max(connection)),
195
+ "forward_abs_radial_shift_max": float(np.max(np.abs(forward_x - np.arange(shape[0], dtype=np.float64)[:, None, None]))),
196
+ "backward_abs_radial_shift_max": float(np.max(np.abs(backward_x - np.arange(shape[0], dtype=np.float64)[:, None, None]))),
197
+ "forward_abs_poloidal_shift_p95": float(np.percentile(np.abs(forward_shift), 95.0)),
198
+ "backward_abs_poloidal_shift_p95": float(np.percentile(np.abs(backward_shift), 95.0)),
199
+ "constant_grad_parallel_linf": float(np.max(np.abs(constant_grad))),
200
+ "constant_laplace_parallel_linf": float(np.max(np.abs(constant_laplace))),
201
+ "constant_conservative_parallel_diffusion_linf": float(np.max(np.abs(constant_diffusion))),
202
+ }
203
+ report["closed_field_semantics_passed"] = bool(
204
+ report["forward_boundary_fraction"] < 1.0e-12
205
+ and report["backward_boundary_fraction"] < 1.0e-12
206
+ and report["endpoint_fraction"] < 1.0e-12
207
+ and report["map_coordinate_finite_fraction"] == 1.0
208
+ and report["constant_grad_parallel_linf"] < 1.0e-10
209
+ and report["constant_laplace_parallel_linf"] < 1.0e-10
210
+ and report["constant_conservative_parallel_diffusion_linf"] < 1.0e-10
211
+ and report["magnetic_field_modulation"] > 1.01
212
+ and report["connection_length_min"] > 0.0
213
+ )
214
+ report["passed"] = bool(report["closed_field_semantics_passed"])
215
+ major_radius = np.sqrt(
216
+ np.asarray(geometry.coordinates_x, dtype=np.float64) ** 2
217
+ + np.asarray(geometry.coordinates_y, dtype=np.float64) ** 2
218
+ )
219
+ arrays = {
220
+ "major_radius_section": major_radius[:, 0, :].astype(np.float32),
221
+ "vertical_section": np.asarray(geometry.coordinates_z, dtype=np.float64)[:, 0, :].astype(np.float32),
222
+ "magnetic_field_section": bmag[:, 0, :].astype(np.float32),
223
+ "connection_toroidal": np.mean(connection, axis=0).astype(np.float32),
224
+ "forward_poloidal_shift_toroidal": np.mean(forward_shift, axis=0).astype(np.float32),
225
+ "backward_poloidal_shift_toroidal": np.mean(backward_shift, axis=0).astype(np.float32),
226
+ "endpoint_count_toroidal": np.sum(endpoint.astype(np.float64), axis=0).astype(np.float32),
227
+ "constant_operator_summary": np.asarray(
228
+ [
229
+ report["constant_grad_parallel_linf"],
230
+ report["constant_laplace_parallel_linf"],
231
+ report["constant_conservative_parallel_diffusion_linf"],
232
+ ],
233
+ dtype=np.float64,
234
+ ),
235
+ "summary": np.asarray(
236
+ [
237
+ report["magnetic_field_modulation"],
238
+ report["connection_length_mean"],
239
+ report["endpoint_fraction"],
240
+ float(report["closed_field_semantics_passed"]),
241
+ ],
242
+ dtype=np.float64,
243
+ ),
244
+ }
245
+ return report, arrays
246
+
247
+
248
+ def save_essos_vmec_closed_field_plot(
249
+ report: dict[str, Any],
250
+ arrays: dict[str, np.ndarray],
251
+ path: str | Path,
252
+ ) -> Path:
253
+ """Save a compact VMEC closed-field validation figure."""
254
+
255
+ major = np.asarray(arrays["major_radius_section"], dtype=np.float64)
256
+ vertical = np.asarray(arrays["vertical_section"], dtype=np.float64)
257
+ b_section = np.asarray(arrays["magnetic_field_section"], dtype=np.float64)
258
+ connection = np.asarray(arrays["connection_toroidal"], dtype=np.float64)
259
+ forward_shift = np.asarray(arrays["forward_poloidal_shift_toroidal"], dtype=np.float64)
260
+ endpoint = np.asarray(arrays["endpoint_count_toroidal"], dtype=np.float64)
261
+
262
+ fig, axes = plt.subplots(2, 3, figsize=(15.4, 8.2), constrained_layout=True)
263
+ axes = axes.ravel()
264
+ axes[0].plot(major.T, vertical.T, color="0.35", lw=0.7, alpha=0.72)
265
+ axes[0].set_aspect("equal", adjustable="box")
266
+ style_axis(axes[0], title="VMEC section geometry", xlabel="R", ylabel="Z", grid="both")
267
+
268
+ image = axes[1].imshow(b_section.T, origin="lower", aspect="auto", cmap="viridis")
269
+ fig.colorbar(image, ax=axes[1], label="|B|")
270
+ style_axis(axes[1], title="magnetic-field section", xlabel="radial index", ylabel="poloidal index", grid="both")
271
+
272
+ image = axes[2].imshow(connection.T, origin="lower", aspect="auto", cmap="magma")
273
+ fig.colorbar(image, ax=axes[2], label="step length")
274
+ style_axis(axes[2], title="closed-map step length", xlabel="toroidal index", ylabel="poloidal index", grid="both")
275
+
276
+ image = axes[3].imshow(forward_shift.T, origin="lower", aspect="auto", cmap="coolwarm")
277
+ fig.colorbar(image, ax=axes[3], label="cells")
278
+ style_axis(axes[3], title="forward poloidal shift", xlabel="toroidal index", ylabel="poloidal index", grid="both")
279
+
280
+ image = axes[4].imshow(endpoint.T, origin="lower", aspect="auto", cmap="Greys", vmin=0.0, vmax=1.0)
281
+ fig.colorbar(image, ax=axes[4], label="endpoint count")
282
+ style_axis(axes[4], title="endpoint mask must stay zero", xlabel="toroidal index", ylabel="poloidal index", grid="both")
283
+
284
+ axes[5].axis("off")
285
+ axes[5].text(
286
+ 0.02,
287
+ 0.96,
288
+ "\n".join(
289
+ [
290
+ "VMEC closed-field control",
291
+ f"shape: {tuple(report['shape'])}",
292
+ f"|B| modulation: {report['magnetic_field_modulation']:.3f}",
293
+ f"endpoint fraction: {report['endpoint_fraction']:.1e}",
294
+ f"connection mean: {report['connection_length_mean']:.3e}",
295
+ f"grad(1) linf: {report['constant_grad_parallel_linf']:.1e}",
296
+ f"laplace(1) linf: {report['constant_laplace_parallel_linf']:.1e}",
297
+ f"diffusion(1) linf: {report['constant_conservative_parallel_diffusion_linf']:.1e}",
298
+ f"passed: {report['passed']}",
299
+ "No target, sheath, recycling, or neutral-loss semantics.",
300
+ ]
301
+ ),
302
+ transform=axes[5].transAxes,
303
+ va="top",
304
+ fontsize=11,
305
+ bbox={"facecolor": "white", "edgecolor": "0.82", "alpha": 0.96},
306
+ )
307
+ fig.suptitle("ESSOS VMEC closed-field FCI validation", fontsize=15, fontweight="semibold")
308
+ save_publication_figure(fig, path)
309
+ return Path(path)
310
+
311
+
312
+ def _periodic_cell_delta(delta: np.ndarray, period: int) -> np.ndarray:
313
+ half = 0.5 * float(period)
314
+ return (np.asarray(delta, dtype=np.float64) + half) % float(period) - half