drbx 2.0.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- drbx/__init__.py +20 -0
- drbx/__main__.py +5 -0
- drbx/cli.py +586 -0
- drbx/config/__init__.py +20 -0
- drbx/config/boutinp.py +556 -0
- drbx/config/model.py +34 -0
- drbx/config/normalization.py +66 -0
- drbx/data/atomic_rates/__init__.py +1 -0
- drbx/data/atomic_rates/iz_AMJUEL_H.x_2.1.5.json +213 -0
- drbx/data/atomic_rates/iz_AMJUEL_H.x_2.3.9a.json +213 -0
- drbx/data/atomic_rates/rec_AMJUEL_H.x_2.1.8.json +213 -0
- drbx/data/atomic_rates/rec_AMJUEL_H.x_2.3.13a.json +213 -0
- drbx/geometry/__init__.py +207 -0
- drbx/geometry/embedding.py +56 -0
- drbx/geometry/essos_import.py +1385 -0
- drbx/geometry/fci_geometry.py +4622 -0
- drbx/geometry/fci_maps.py +85 -0
- drbx/geometry/island_divertor.py +291 -0
- drbx/geometry/metric_tensor.py +99 -0
- drbx/geometry/open_slab.py +150 -0
- drbx/geometry/rotating_ellipse.py +253 -0
- drbx/geometry/shifted_torus.py +225 -0
- drbx/geometry/stellarator.py +287 -0
- drbx/geometry/vmec_extender_import.py +499 -0
- drbx/geometry/vmec_jax_import.py +306 -0
- drbx/linear/__init__.py +37 -0
- drbx/linear/dispersion.py +138 -0
- drbx/linear/eigen.py +91 -0
- drbx/native/__init__.py +224 -0
- drbx/native/array_backend.py +64 -0
- drbx/native/deck_runner.py +779 -0
- drbx/native/electromagnetic.py +250 -0
- drbx/native/expression.py +173 -0
- drbx/native/fci.py +295 -0
- drbx/native/fci_2_field_rhs.py +182 -0
- drbx/native/fci_4_field_rhs.py +1267 -0
- drbx/native/fci_boundaries.py +2494 -0
- drbx/native/fci_differentiable_case.py +304 -0
- drbx/native/fci_drb_EB_rhs.py +1243 -0
- drbx/native/fci_drb_rhs.py +190 -0
- drbx/native/fci_halo.py +1575 -0
- drbx/native/fci_helpers.py +350 -0
- drbx/native/fci_model.py +294 -0
- drbx/native/fci_neutral.py +139 -0
- drbx/native/fci_operators.py +4081 -0
- drbx/native/fci_sharding.py +597 -0
- drbx/native/fci_sheath_recycling.py +206 -0
- drbx/native/fci_time_integrator.py +96 -0
- drbx/native/fci_vorticity.py +198 -0
- drbx/native/fluid_1d.py +330 -0
- drbx/native/hasegawa_wakatani.py +196 -0
- drbx/native/limiters.py +57 -0
- drbx/native/mesh.py +238 -0
- drbx/native/metrics.py +234 -0
- drbx/native/neutrals/__init__.py +58 -0
- drbx/native/neutrals/atomic_rates.py +134 -0
- drbx/native/neutrals/detachment_sol_model.py +221 -0
- drbx/native/neutrals/reactions.py +164 -0
- drbx/native/neutrals/recycling_sol_model.py +197 -0
- drbx/native/sol_flux_tube.py +133 -0
- drbx/native/stellarator_turbulence.py +343 -0
- drbx/native/transport.py +134 -0
- drbx/native/units.py +32 -0
- drbx/native/vorticity.py +252 -0
- drbx/runtime/__init__.py +53 -0
- drbx/runtime/artifacts.py +161 -0
- drbx/runtime/memory.py +144 -0
- drbx/runtime/output.py +374 -0
- drbx/runtime/paths.py +9 -0
- drbx/runtime/performance.py +161 -0
- drbx/runtime/run_config.py +184 -0
- drbx/runtime/scheduler.py +99 -0
- drbx/runtime/state.py +40 -0
- drbx/validation/__init__.py +424 -0
- drbx/validation/autodiff_diffusion.py +329 -0
- drbx/validation/autodiff_diffusion_uncertainty.py +235 -0
- drbx/validation/diverted_tokamak_movie.py +558 -0
- drbx/validation/essos_fieldline_import_campaign.py +181 -0
- drbx/validation/essos_imported_artifact_audit.py +535 -0
- drbx/validation/essos_imported_drb_movie_campaign.py +2826 -0
- drbx/validation/essos_imported_fci_campaign.py +5241 -0
- drbx/validation/essos_imported_pytree_campaign.py +406 -0
- drbx/validation/essos_vmec_closed_field_campaign.py +314 -0
- drbx/validation/essos_vmec_closed_field_transient_campaign.py +629 -0
- drbx/validation/essos_vmec_fieldline_surface_campaign.py +620 -0
- drbx/validation/fluid_1d_mms_convergence.py +250 -0
- drbx/validation/geometry_lineouts.py +136 -0
- drbx/validation/geometry_slices.py +178 -0
- drbx/validation/publication_plotting.py +91 -0
- drbx/validation/stellarator_drb_pytree_campaign.py +621 -0
- drbx/validation/stellarator_fci_geometry_campaign.py +200 -0
- drbx/validation/stellarator_fci_operator_campaign.py +304 -0
- drbx/validation/stellarator_fci_suite_campaign.py +264 -0
- drbx/validation/stellarator_metric_mms_campaign.py +289 -0
- drbx/validation/stellarator_neutral_physics_campaign.py +255 -0
- drbx/validation/stellarator_sheath_recycling_campaign.py +331 -0
- drbx/validation/stellarator_sol_showcase.py +628 -0
- drbx/validation/stellarator_vorticity_campaign.py +304 -0
- drbx/validation/vmec_extender_edge_field_campaign.py +260 -0
- drbx/validation/vmec_extender_sol_smoke_campaign.py +365 -0
- drbx-2.0.0.dev0.dist-info/METADATA +380 -0
- drbx-2.0.0.dev0.dist-info/RECORD +106 -0
- drbx-2.0.0.dev0.dist-info/WHEEL +5 -0
- drbx-2.0.0.dev0.dist-info/entry_points.txt +2 -0
- drbx-2.0.0.dev0.dist-info/licenses/LICENSE +21 -0
- drbx-2.0.0.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
"""Slim native runner for the proved deck models.
|
|
2
|
+
|
|
3
|
+
This module supersedes the historical ``native.runner`` recycling/reference
|
|
4
|
+
lane. It keeps only the accuracy-tested execution branches that back the
|
|
5
|
+
``drbx run`` command:
|
|
6
|
+
|
|
7
|
+
* single-component ``evolve_density`` (one-rhs),
|
|
8
|
+
* anomalous diffusion (:mod:`drbx.native.transport`),
|
|
9
|
+
* periodic fluid MMS (:mod:`drbx.native.fluid_1d`),
|
|
10
|
+
* electrostatic vorticity (:mod:`drbx.native.vorticity`).
|
|
11
|
+
|
|
12
|
+
It imports only kept modules and carries slim, dependency-free copies of the
|
|
13
|
+
portable-summary / portable-array helpers so the CLI can write run artifacts
|
|
14
|
+
without the removed ``parity`` package.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from collections.abc import Callable, Mapping as ABCMapping
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
import json
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Mapping
|
|
24
|
+
|
|
25
|
+
import jax.numpy as jnp
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
from ..config.boutinp import BoutConfig, NumericResolver, load_bout_input
|
|
29
|
+
from ..runtime import runtime_numpy_dtype
|
|
30
|
+
from ..runtime.output import RestartBundle, build_run_event, print_run_event
|
|
31
|
+
from ..runtime.run_config import RunConfiguration
|
|
32
|
+
from .expression import ArrayExpressionEvaluator
|
|
33
|
+
from .fluid_1d import Fluid1DState, advance_mms_history, compute_mms_rhs, initialize_mms_state
|
|
34
|
+
from .mesh import (
|
|
35
|
+
StructuredMesh,
|
|
36
|
+
apply_field_boundaries,
|
|
37
|
+
broadcast_to_field_shape,
|
|
38
|
+
build_structured_mesh,
|
|
39
|
+
)
|
|
40
|
+
from .metrics import StructuredMetrics, build_structured_metrics
|
|
41
|
+
from .transport import advance_anomalous_diffusion_history
|
|
42
|
+
from .units import resolved_dataset_scalars
|
|
43
|
+
from .vorticity import (
|
|
44
|
+
advance_vorticity_history,
|
|
45
|
+
apply_vorticity_boundaries,
|
|
46
|
+
build_vorticity_operator,
|
|
47
|
+
compute_vorticity_rhs,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# --------------------------------------------------------------------------- #
|
|
52
|
+
# Result / restart containers (formerly native.runner_state)
|
|
53
|
+
# --------------------------------------------------------------------------- #
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class NativeRunResult:
|
|
56
|
+
payload: Mapping[str, Any]
|
|
57
|
+
variables: Mapping[str, Any]
|
|
58
|
+
time_points: tuple[float, ...]
|
|
59
|
+
run_config: RunConfiguration
|
|
60
|
+
mesh: StructuredMesh
|
|
61
|
+
metrics: StructuredMetrics
|
|
62
|
+
diagnostics: Mapping[str, Any] = field(default_factory=dict)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class NativeExecutionResult:
|
|
67
|
+
time_points: tuple[float, ...]
|
|
68
|
+
variables: Mapping[str, Any]
|
|
69
|
+
diagnostics: Mapping[str, Any] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
def __iter__(self):
|
|
72
|
+
yield self.time_points
|
|
73
|
+
yield self.variables
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def coerce_native_execution_result(result: object) -> NativeExecutionResult:
|
|
77
|
+
if isinstance(result, NativeExecutionResult):
|
|
78
|
+
return result
|
|
79
|
+
time_points, variables = result # type: ignore[misc]
|
|
80
|
+
return NativeExecutionResult(time_points=tuple(time_points), variables=variables)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True)
|
|
84
|
+
class NativeRestartState:
|
|
85
|
+
time_offset: float
|
|
86
|
+
completed_steps: int
|
|
87
|
+
configured_timestep: float
|
|
88
|
+
variables: Mapping[str, np.ndarray]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _restart_variable_names(run_config: RunConfiguration) -> tuple[str, ...]:
|
|
92
|
+
implementations = tuple(component.implementation for component in run_config.components)
|
|
93
|
+
sections = tuple(dict.fromkeys(component.section for component in run_config.components))
|
|
94
|
+
if implementations == ("evolve_density", "evolve_pressure", "anomalous_diffusion") and len(sections) == 1:
|
|
95
|
+
section = sections[0]
|
|
96
|
+
return (f"N{section}", f"P{section}")
|
|
97
|
+
if implementations == ("evolve_density", "evolve_pressure", "evolve_momentum") and len(sections) == 1:
|
|
98
|
+
section = sections[0]
|
|
99
|
+
return (f"N{section}", f"P{section}", f"NV{section}")
|
|
100
|
+
if implementations == ("vorticity",):
|
|
101
|
+
return ("Vort",)
|
|
102
|
+
return ()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def build_restart_state(result: NativeRunResult, *, parity_mode: str) -> RestartBundle | None:
|
|
106
|
+
names = _restart_variable_names(result.run_config)
|
|
107
|
+
if not names:
|
|
108
|
+
return None
|
|
109
|
+
dtype = runtime_numpy_dtype()
|
|
110
|
+
final_state = {
|
|
111
|
+
name: np.asarray(result.variables[name][-1], dtype=dtype)
|
|
112
|
+
for name in names
|
|
113
|
+
if name in result.variables
|
|
114
|
+
}
|
|
115
|
+
if tuple(final_state) != names:
|
|
116
|
+
return None
|
|
117
|
+
return RestartBundle(
|
|
118
|
+
case_name=str(result.payload.get("case_name", "run")),
|
|
119
|
+
parity_mode=parity_mode,
|
|
120
|
+
component_labels=tuple(request.label for request in result.run_config.components),
|
|
121
|
+
current_time=float(result.time_points[-1]) if result.time_points else 0.0,
|
|
122
|
+
completed_steps=max(len(result.time_points) - 1, 0),
|
|
123
|
+
configured_timestep=float(result.run_config.time.timestep),
|
|
124
|
+
state_variables=final_state,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# --------------------------------------------------------------------------- #
|
|
129
|
+
# Portable summary / array payload helpers (formerly parity.portable / arrays)
|
|
130
|
+
# --------------------------------------------------------------------------- #
|
|
131
|
+
def _summarize_array(name: str, data: np.ndarray, dimension_names: tuple[str, ...]) -> dict[str, Any]:
|
|
132
|
+
delta = None
|
|
133
|
+
if data.ndim >= 1 and data.shape[0] >= 2:
|
|
134
|
+
delta = float(np.max(np.abs(data[-1] - data[0])))
|
|
135
|
+
if len(dimension_names) == data.ndim:
|
|
136
|
+
dimensions = dimension_names
|
|
137
|
+
else:
|
|
138
|
+
dimensions = tuple(["t", *[f"dim_{index}" for index in range(1, data.ndim)]])
|
|
139
|
+
return {
|
|
140
|
+
"name": name,
|
|
141
|
+
"dimensions": list(dimensions),
|
|
142
|
+
"shape": [int(value) for value in data.shape],
|
|
143
|
+
"minimum": float(np.min(data)),
|
|
144
|
+
"maximum": float(np.max(data)),
|
|
145
|
+
"mean": float(np.mean(data)),
|
|
146
|
+
"max_abs_delta_last_first": delta,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def build_portable_summary_payload(
|
|
151
|
+
*,
|
|
152
|
+
case_name: str,
|
|
153
|
+
parity_mode: str,
|
|
154
|
+
capability_tier: str,
|
|
155
|
+
compare_variables: tuple[str, ...],
|
|
156
|
+
component_labels: tuple[str, ...],
|
|
157
|
+
dimensions: Mapping[str, int],
|
|
158
|
+
time_points: tuple[float, ...],
|
|
159
|
+
dataset_scalars: Mapping[str, float],
|
|
160
|
+
variables: Mapping[str, Any],
|
|
161
|
+
overrides: tuple[str, ...] = (),
|
|
162
|
+
configured_nout: int | None = None,
|
|
163
|
+
configured_timestep: float | None = None,
|
|
164
|
+
producer: str = "drbx",
|
|
165
|
+
) -> dict[str, Any]:
|
|
166
|
+
summary_dimensions = tuple(dimensions)
|
|
167
|
+
summaries = {
|
|
168
|
+
name: _summarize_array(name, np.asarray(variables[name], dtype=np.float64), summary_dimensions)
|
|
169
|
+
for name in compare_variables
|
|
170
|
+
if name in variables
|
|
171
|
+
}
|
|
172
|
+
payload: dict[str, Any] = {
|
|
173
|
+
"case_name": case_name,
|
|
174
|
+
"parity_mode": parity_mode,
|
|
175
|
+
"capability_tier": capability_tier,
|
|
176
|
+
"producer": producer,
|
|
177
|
+
"overrides": list(overrides),
|
|
178
|
+
"compare_variables": list(compare_variables),
|
|
179
|
+
"component_labels": list(component_labels),
|
|
180
|
+
"dimensions": dict(dimensions),
|
|
181
|
+
"time_points": list(time_points),
|
|
182
|
+
"dataset_scalars": {key: float(value) for key, value in dataset_scalars.items()},
|
|
183
|
+
"variable_summaries": summaries,
|
|
184
|
+
"effective_output_points": len(time_points),
|
|
185
|
+
}
|
|
186
|
+
if configured_nout is not None:
|
|
187
|
+
payload["configured_nout"] = configured_nout
|
|
188
|
+
if configured_timestep is not None:
|
|
189
|
+
payload["configured_timestep"] = configured_timestep
|
|
190
|
+
return payload
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def write_portable_summary_payload(payload: Mapping[str, Any], path: str | Path) -> Path:
|
|
194
|
+
target = Path(path)
|
|
195
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
196
|
+
target.write_text(json.dumps(dict(payload), indent=2, sort_keys=True), encoding="utf-8")
|
|
197
|
+
return target
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def build_portable_array_payload(
|
|
201
|
+
*,
|
|
202
|
+
case_name: str,
|
|
203
|
+
parity_mode: str,
|
|
204
|
+
capability_tier: str,
|
|
205
|
+
compare_variables: tuple[str, ...],
|
|
206
|
+
component_labels: tuple[str, ...],
|
|
207
|
+
dimensions: Mapping[str, int],
|
|
208
|
+
time_points: tuple[float, ...],
|
|
209
|
+
dataset_scalars: Mapping[str, float],
|
|
210
|
+
variables: Mapping[str, Any],
|
|
211
|
+
variable_dimensions: Mapping[str, tuple[str, ...]] | None = None,
|
|
212
|
+
overrides: tuple[str, ...] = (),
|
|
213
|
+
configured_nout: int | None = None,
|
|
214
|
+
configured_timestep: float | None = None,
|
|
215
|
+
producer: str = "drbx",
|
|
216
|
+
) -> dict[str, Any]:
|
|
217
|
+
summary_dimensions = tuple(dimensions)
|
|
218
|
+
payload_variables: dict[str, np.ndarray] = {}
|
|
219
|
+
payload_variable_dimensions: dict[str, list[str]] = {}
|
|
220
|
+
for name in compare_variables:
|
|
221
|
+
if name not in variables:
|
|
222
|
+
continue
|
|
223
|
+
array = np.asarray(variables[name], dtype=np.float64)
|
|
224
|
+
payload_variables[name] = array
|
|
225
|
+
if variable_dimensions is not None and name in variable_dimensions:
|
|
226
|
+
dims = variable_dimensions[name]
|
|
227
|
+
elif len(summary_dimensions) == array.ndim:
|
|
228
|
+
dims = summary_dimensions
|
|
229
|
+
else:
|
|
230
|
+
dims = tuple(["t", *[f"dim_{index}" for index in range(1, array.ndim)]])
|
|
231
|
+
payload_variable_dimensions[name] = list(dims)
|
|
232
|
+
|
|
233
|
+
payload: dict[str, Any] = {
|
|
234
|
+
"case_name": case_name,
|
|
235
|
+
"parity_mode": parity_mode,
|
|
236
|
+
"capability_tier": capability_tier,
|
|
237
|
+
"producer": producer,
|
|
238
|
+
"overrides": list(overrides),
|
|
239
|
+
"compare_variables": list(compare_variables),
|
|
240
|
+
"component_labels": list(component_labels),
|
|
241
|
+
"dimensions": dict(dimensions),
|
|
242
|
+
"time_points": list(time_points),
|
|
243
|
+
"dataset_scalars": {key: float(value) for key, value in dataset_scalars.items()},
|
|
244
|
+
"variable_dimensions": payload_variable_dimensions,
|
|
245
|
+
"variables": payload_variables,
|
|
246
|
+
"effective_output_points": len(time_points),
|
|
247
|
+
}
|
|
248
|
+
if configured_nout is not None:
|
|
249
|
+
payload["configured_nout"] = configured_nout
|
|
250
|
+
if configured_timestep is not None:
|
|
251
|
+
payload["configured_timestep"] = configured_timestep
|
|
252
|
+
return payload
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def write_portable_array_payload(payload: Mapping[str, Any], path: str | Path) -> Path:
|
|
256
|
+
target = Path(path)
|
|
257
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
258
|
+
metadata = {key: value for key, value in payload.items() if key != "variables"}
|
|
259
|
+
arrays = {
|
|
260
|
+
f"var__{name}": np.asarray(value, dtype=np.float64)
|
|
261
|
+
for name, value in payload.get("variables", {}).items()
|
|
262
|
+
}
|
|
263
|
+
np.savez_compressed(target, __metadata__=json.dumps(metadata, sort_keys=True), **arrays)
|
|
264
|
+
return target
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def load_portable_array_payload(path: str | Path) -> dict[str, Any]:
|
|
268
|
+
with np.load(Path(path), allow_pickle=False) as payload:
|
|
269
|
+
metadata = json.loads(str(payload["__metadata__"]))
|
|
270
|
+
variables = {
|
|
271
|
+
key.removeprefix("var__"): np.asarray(payload[key], dtype=np.float64)
|
|
272
|
+
for key in payload.files
|
|
273
|
+
if key.startswith("var__")
|
|
274
|
+
}
|
|
275
|
+
metadata["variables"] = variables
|
|
276
|
+
return metadata
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# --------------------------------------------------------------------------- #
|
|
280
|
+
# Execution helpers
|
|
281
|
+
# --------------------------------------------------------------------------- #
|
|
282
|
+
def _effective_output_steps(parity_mode: str, *, configured_nout: int) -> int:
|
|
283
|
+
if parity_mode == "one_rhs":
|
|
284
|
+
return 0
|
|
285
|
+
if parity_mode == "one_step":
|
|
286
|
+
return 1
|
|
287
|
+
return configured_nout
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _default_overrides(parity_mode: str) -> tuple[str, ...]:
|
|
291
|
+
if parity_mode == "one_rhs":
|
|
292
|
+
return ("nout=0",)
|
|
293
|
+
if parity_mode == "one_step":
|
|
294
|
+
return ("nout=1",)
|
|
295
|
+
return ()
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _prepare_compare_variables(
|
|
299
|
+
variables: Mapping[str, Any],
|
|
300
|
+
mesh: StructuredMesh,
|
|
301
|
+
*,
|
|
302
|
+
trim_x_guards: bool,
|
|
303
|
+
trim_y_guards: bool,
|
|
304
|
+
) -> dict[str, Any]:
|
|
305
|
+
prepared: dict[str, Any] = {}
|
|
306
|
+
for name, value in variables.items():
|
|
307
|
+
array = np.asarray(value, dtype=np.float64)
|
|
308
|
+
if trim_x_guards and array.ndim >= 4 and array.shape[1] > 2 * mesh.mxg:
|
|
309
|
+
array = array[:, mesh.mxg : -mesh.mxg, ...]
|
|
310
|
+
if trim_y_guards and array.ndim >= 4 and array.shape[2] > 2 * mesh.myg:
|
|
311
|
+
array = array[:, :, mesh.myg : -mesh.myg, ...]
|
|
312
|
+
prepared[name] = array
|
|
313
|
+
return prepared
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# --------------------------------------------------------------------------- #
|
|
317
|
+
# Public run entry points
|
|
318
|
+
# --------------------------------------------------------------------------- #
|
|
319
|
+
def run_input_case(
|
|
320
|
+
input_path: str | Path,
|
|
321
|
+
*,
|
|
322
|
+
case_name: str | None = None,
|
|
323
|
+
parity_mode: str = "manual",
|
|
324
|
+
compare_variables: tuple[str, ...] = (),
|
|
325
|
+
restart_state: NativeRestartState | None = None,
|
|
326
|
+
output_steps: int | None = None,
|
|
327
|
+
verbose: bool = False,
|
|
328
|
+
verbosity: str = "detailed",
|
|
329
|
+
event_logger: Callable[[ABCMapping[str, Any]], None] | None = None,
|
|
330
|
+
) -> NativeRunResult:
|
|
331
|
+
config = load_bout_input(input_path)
|
|
332
|
+
resolved_case_name = case_name or Path(input_path).stem
|
|
333
|
+
return run_config_case(
|
|
334
|
+
config,
|
|
335
|
+
case_name=resolved_case_name,
|
|
336
|
+
parity_mode=parity_mode,
|
|
337
|
+
compare_variables=compare_variables,
|
|
338
|
+
restart_state=restart_state,
|
|
339
|
+
output_steps=output_steps,
|
|
340
|
+
verbose=verbose,
|
|
341
|
+
verbosity=verbosity,
|
|
342
|
+
event_logger=event_logger,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def run_config_case(
|
|
347
|
+
config: BoutConfig,
|
|
348
|
+
*,
|
|
349
|
+
case_name: str,
|
|
350
|
+
parity_mode: str,
|
|
351
|
+
compare_variables: tuple[str, ...] = (),
|
|
352
|
+
restart_state: NativeRestartState | None = None,
|
|
353
|
+
output_steps: int | None = None,
|
|
354
|
+
verbose: bool = False,
|
|
355
|
+
verbosity: str = "detailed",
|
|
356
|
+
event_logger: Callable[[ABCMapping[str, Any]], None] | None = None,
|
|
357
|
+
) -> NativeRunResult:
|
|
358
|
+
event_sink = event_logger
|
|
359
|
+
if event_sink is None and verbose:
|
|
360
|
+
event_sink = lambda event: print_run_event(event, verbosity=verbosity)
|
|
361
|
+
|
|
362
|
+
def emit(stage: str, message: str, **details: Any) -> None:
|
|
363
|
+
if event_sink is None:
|
|
364
|
+
return
|
|
365
|
+
event_sink(build_run_event(stage=stage, message=message, details=details or None))
|
|
366
|
+
|
|
367
|
+
run_config = RunConfiguration.from_config(config)
|
|
368
|
+
emit(
|
|
369
|
+
"configuration",
|
|
370
|
+
"Resolved native run configuration",
|
|
371
|
+
case_name=case_name,
|
|
372
|
+
parity_mode=parity_mode,
|
|
373
|
+
capability_tier="native_exact",
|
|
374
|
+
nout=run_config.time.nout,
|
|
375
|
+
timestep=run_config.time.timestep,
|
|
376
|
+
components=",".join(component.label for component in run_config.components),
|
|
377
|
+
restart=restart_state is not None,
|
|
378
|
+
)
|
|
379
|
+
mesh = build_structured_mesh(config, run_config)
|
|
380
|
+
metrics = build_structured_metrics(config, run_config, mesh)
|
|
381
|
+
emit(
|
|
382
|
+
"mesh",
|
|
383
|
+
"Built structured mesh and metrics",
|
|
384
|
+
nx=mesh.nx,
|
|
385
|
+
ny=mesh.local_ny,
|
|
386
|
+
nz=mesh.nz,
|
|
387
|
+
mxg=mesh.mxg,
|
|
388
|
+
myg=mesh.myg,
|
|
389
|
+
file=run_config.mesh.file or "<analytic mesh>",
|
|
390
|
+
)
|
|
391
|
+
execution = coerce_native_execution_result(
|
|
392
|
+
_execute_supported_case(
|
|
393
|
+
config,
|
|
394
|
+
run_config,
|
|
395
|
+
mesh,
|
|
396
|
+
metrics,
|
|
397
|
+
parity_mode=parity_mode,
|
|
398
|
+
restart_state=restart_state,
|
|
399
|
+
output_steps=output_steps,
|
|
400
|
+
)
|
|
401
|
+
)
|
|
402
|
+
time_points = execution.time_points
|
|
403
|
+
variables = execution.variables
|
|
404
|
+
emit(
|
|
405
|
+
"run",
|
|
406
|
+
"Native solver completed",
|
|
407
|
+
stored_states=len(time_points),
|
|
408
|
+
compare_variables=",".join(compare_variables or tuple(variables)),
|
|
409
|
+
)
|
|
410
|
+
compare_names = compare_variables or tuple(variables)
|
|
411
|
+
trimmed_variables = _prepare_compare_variables(
|
|
412
|
+
variables,
|
|
413
|
+
mesh,
|
|
414
|
+
trim_x_guards=False,
|
|
415
|
+
trim_y_guards=False,
|
|
416
|
+
)
|
|
417
|
+
dataset_scalars = resolved_dataset_scalars(run_config)
|
|
418
|
+
payload = build_portable_summary_payload(
|
|
419
|
+
case_name=case_name,
|
|
420
|
+
parity_mode=parity_mode,
|
|
421
|
+
capability_tier="native_exact",
|
|
422
|
+
compare_variables=compare_names,
|
|
423
|
+
component_labels=tuple(component.label for component in run_config.components),
|
|
424
|
+
dimensions={"t": len(time_points), "x": mesh.nx, "y": mesh.local_ny, "z": mesh.nz},
|
|
425
|
+
time_points=time_points,
|
|
426
|
+
dataset_scalars=dataset_scalars,
|
|
427
|
+
variables={name: np.asarray(value, dtype=np.float64) for name, value in trimmed_variables.items()},
|
|
428
|
+
overrides=_default_overrides(parity_mode),
|
|
429
|
+
configured_nout=run_config.time.nout,
|
|
430
|
+
configured_timestep=run_config.time.timestep,
|
|
431
|
+
producer="drbx",
|
|
432
|
+
)
|
|
433
|
+
emit(
|
|
434
|
+
"summary",
|
|
435
|
+
"Prepared portable summary payload",
|
|
436
|
+
variables=",".join(sorted(trimmed_variables)),
|
|
437
|
+
time_start=time_points[0] if time_points else 0.0,
|
|
438
|
+
time_end=time_points[-1] if time_points else 0.0,
|
|
439
|
+
)
|
|
440
|
+
return NativeRunResult(
|
|
441
|
+
payload=payload,
|
|
442
|
+
variables=trimmed_variables,
|
|
443
|
+
time_points=time_points,
|
|
444
|
+
run_config=run_config,
|
|
445
|
+
mesh=mesh,
|
|
446
|
+
metrics=metrics,
|
|
447
|
+
diagnostics=execution.diagnostics,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _execute_supported_case(
|
|
452
|
+
config: BoutConfig,
|
|
453
|
+
run_config: RunConfiguration,
|
|
454
|
+
mesh: StructuredMesh,
|
|
455
|
+
metrics: StructuredMetrics,
|
|
456
|
+
*,
|
|
457
|
+
parity_mode: str,
|
|
458
|
+
restart_state: NativeRestartState | None = None,
|
|
459
|
+
output_steps: int | None = None,
|
|
460
|
+
) -> NativeExecutionResult | tuple[tuple[float, ...], dict[str, Any]]:
|
|
461
|
+
implementations = tuple(component.implementation for component in run_config.components)
|
|
462
|
+
if len(run_config.components) == 1 and implementations == ("evolve_density",):
|
|
463
|
+
component = run_config.components[0]
|
|
464
|
+
variable_name = f"N{component.section}"
|
|
465
|
+
if parity_mode != "one_rhs":
|
|
466
|
+
raise NotImplementedError(
|
|
467
|
+
"Native single-component density execution currently supports one_rhs parity only."
|
|
468
|
+
)
|
|
469
|
+
field_values = _initialize_species_field(config, variable_name, mesh)
|
|
470
|
+
return (0.0,), {variable_name: field_values[None, ...]}
|
|
471
|
+
|
|
472
|
+
if _is_supported_diffusion_case(run_config):
|
|
473
|
+
return _execute_diffusion_case(
|
|
474
|
+
config,
|
|
475
|
+
run_config,
|
|
476
|
+
mesh,
|
|
477
|
+
metrics,
|
|
478
|
+
parity_mode=parity_mode,
|
|
479
|
+
restart_state=restart_state,
|
|
480
|
+
output_steps=output_steps,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
if _is_supported_periodic_fluid_mms_case(config, run_config, mesh, metrics):
|
|
484
|
+
return _execute_periodic_fluid_mms_case(
|
|
485
|
+
config,
|
|
486
|
+
run_config,
|
|
487
|
+
mesh,
|
|
488
|
+
metrics,
|
|
489
|
+
parity_mode=parity_mode,
|
|
490
|
+
restart_state=restart_state,
|
|
491
|
+
output_steps=output_steps,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
if _is_supported_electrostatic_vorticity_case(config, run_config, mesh, metrics):
|
|
495
|
+
return _execute_electrostatic_vorticity_case(
|
|
496
|
+
config,
|
|
497
|
+
run_config,
|
|
498
|
+
mesh,
|
|
499
|
+
metrics,
|
|
500
|
+
parity_mode=parity_mode,
|
|
501
|
+
restart_state=restart_state,
|
|
502
|
+
output_steps=output_steps,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
raise NotImplementedError(
|
|
506
|
+
"Native execution is not implemented for the configured component set: "
|
|
507
|
+
+ ", ".join(component.label for component in run_config.components)
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _execute_diffusion_case(
|
|
512
|
+
config: BoutConfig,
|
|
513
|
+
run_config: RunConfiguration,
|
|
514
|
+
mesh: StructuredMesh,
|
|
515
|
+
metrics: StructuredMetrics,
|
|
516
|
+
*,
|
|
517
|
+
parity_mode: str,
|
|
518
|
+
restart_state: NativeRestartState | None = None,
|
|
519
|
+
output_steps: int | None = None,
|
|
520
|
+
) -> tuple[tuple[float, ...], dict[str, Any]]:
|
|
521
|
+
dtype = runtime_numpy_dtype()
|
|
522
|
+
section = run_config.components[0].section
|
|
523
|
+
density_name = f"N{section}"
|
|
524
|
+
pressure_name = f"P{section}"
|
|
525
|
+
if restart_state is None:
|
|
526
|
+
density = _initialize_species_field(config, density_name, mesh)
|
|
527
|
+
pressure = _initialize_species_field(config, pressure_name, mesh)
|
|
528
|
+
time_offset = 0.0
|
|
529
|
+
else:
|
|
530
|
+
density = np.asarray(restart_state.variables[density_name], dtype=dtype)
|
|
531
|
+
pressure = np.asarray(restart_state.variables[pressure_name], dtype=dtype)
|
|
532
|
+
time_offset = restart_state.time_offset
|
|
533
|
+
if not np.allclose(np.asarray(density), np.asarray(pressure), rtol=1e-12, atol=1e-12):
|
|
534
|
+
raise NotImplementedError(
|
|
535
|
+
"Native anomalous diffusion currently requires identical density and pressure initial states."
|
|
536
|
+
)
|
|
537
|
+
if not np.allclose(np.asarray(metrics.g23), 0.0, rtol=1e-12, atol=1e-12):
|
|
538
|
+
raise NotImplementedError("Native anomalous diffusion currently supports g23 = 0 structured metrics only.")
|
|
539
|
+
density_boundary = _field_boundary_kind(config, density_name)
|
|
540
|
+
pressure_boundary = _field_boundary_kind(config, pressure_name)
|
|
541
|
+
|
|
542
|
+
if bool(config.parsed(section, "thermal_conduction")) if config.has_option(section, "thermal_conduction") else True:
|
|
543
|
+
raise NotImplementedError("Native one-step anomalous diffusion currently requires thermal_conduction = false.")
|
|
544
|
+
|
|
545
|
+
resolver = NumericResolver(config)
|
|
546
|
+
scalars = resolved_dataset_scalars(run_config)
|
|
547
|
+
anomalous_D = resolver.resolve(section, "anomalous_D") / (scalars["rho_s0"] * scalars["rho_s0"] * scalars["Omega_ci"])
|
|
548
|
+
if config.has_option(section, "anomalous_chi") and abs(resolver.resolve(section, "anomalous_chi")) > 0.0:
|
|
549
|
+
raise NotImplementedError("Native one-step anomalous diffusion does not yet support anomalous_chi.")
|
|
550
|
+
if config.has_option(section, "anomalous_nu") and abs(resolver.resolve(section, "anomalous_nu")) > 0.0:
|
|
551
|
+
raise NotImplementedError("Native one-step anomalous diffusion does not yet support anomalous_nu.")
|
|
552
|
+
|
|
553
|
+
steps = output_steps if output_steps is not None else _effective_output_steps(parity_mode, configured_nout=run_config.time.nout)
|
|
554
|
+
history = advance_anomalous_diffusion_history(
|
|
555
|
+
density,
|
|
556
|
+
pressure,
|
|
557
|
+
mesh=mesh,
|
|
558
|
+
metrics=metrics,
|
|
559
|
+
anomalous_D=anomalous_D,
|
|
560
|
+
density_boundary=density_boundary,
|
|
561
|
+
pressure_boundary=pressure_boundary,
|
|
562
|
+
timestep=run_config.time.timestep,
|
|
563
|
+
steps=steps,
|
|
564
|
+
)
|
|
565
|
+
time_points = tuple(time_offset + run_config.time.timestep * index for index in range(steps + 1))
|
|
566
|
+
return time_points, {
|
|
567
|
+
density_name: np.asarray(history.density_history, dtype=dtype),
|
|
568
|
+
pressure_name: np.asarray(history.pressure_history, dtype=dtype),
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _initialize_species_field(config: BoutConfig, variable_name: str, mesh: StructuredMesh) -> Any:
|
|
573
|
+
if not config.has_section(variable_name):
|
|
574
|
+
raise KeyError(f"Missing initial condition section for {variable_name}.")
|
|
575
|
+
if config.has_option(variable_name, "function"):
|
|
576
|
+
option_name = "function"
|
|
577
|
+
elif config.has_option(variable_name, "solution"):
|
|
578
|
+
option_name = "solution"
|
|
579
|
+
else:
|
|
580
|
+
raise KeyError(f"Missing initial condition function or solution for {variable_name}.")
|
|
581
|
+
evaluator = ArrayExpressionEvaluator(config, local_values=mesh.expression_context())
|
|
582
|
+
field_values = broadcast_to_field_shape(
|
|
583
|
+
evaluator.evaluate(config.raw(variable_name, option_name), current_section=variable_name),
|
|
584
|
+
mesh,
|
|
585
|
+
)
|
|
586
|
+
field_values = apply_field_boundaries(field_values, mesh, x_boundary=_field_boundary_kind(config, variable_name))
|
|
587
|
+
return field_values
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _field_boundary_kind(config: BoutConfig, variable_name: str) -> str:
|
|
591
|
+
if config.has_option(variable_name, "bndry_all"):
|
|
592
|
+
return str(config.parsed(variable_name, "bndry_all")).strip().lower()
|
|
593
|
+
return "dirichlet_zero"
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _is_supported_diffusion_case(run_config: RunConfiguration) -> bool:
|
|
597
|
+
implementations = tuple(component.implementation for component in run_config.components)
|
|
598
|
+
if implementations != ("evolve_density", "evolve_pressure", "anomalous_diffusion"):
|
|
599
|
+
return False
|
|
600
|
+
sections = {component.section for component in run_config.components}
|
|
601
|
+
return len(sections) == 1
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def _execute_periodic_fluid_mms_case(
|
|
605
|
+
config: BoutConfig,
|
|
606
|
+
run_config: RunConfiguration,
|
|
607
|
+
mesh: StructuredMesh,
|
|
608
|
+
metrics: StructuredMetrics,
|
|
609
|
+
*,
|
|
610
|
+
parity_mode: str,
|
|
611
|
+
restart_state: NativeRestartState | None = None,
|
|
612
|
+
output_steps: int | None = None,
|
|
613
|
+
) -> tuple[tuple[float, ...], dict[str, Any]]:
|
|
614
|
+
section = run_config.components[0].section
|
|
615
|
+
atomic_mass = float(config.parsed(section, "AA"))
|
|
616
|
+
if parity_mode == "one_rhs":
|
|
617
|
+
state = initialize_mms_state(config, section=section, mesh=mesh)
|
|
618
|
+
rhs = compute_mms_rhs(config, state, section=section, mesh=mesh, metrics=metrics, atomic_mass=atomic_mass, time=0.0)
|
|
619
|
+
return (0.0,), {
|
|
620
|
+
f"ddt(N{section})": np.asarray(rhs.density[None, ...], dtype=np.float64),
|
|
621
|
+
f"ddt(P{section})": np.asarray(rhs.pressure[None, ...], dtype=np.float64),
|
|
622
|
+
f"ddt(NV{section})": np.asarray(rhs.momentum[None, ...], dtype=np.float64),
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
steps = output_steps if output_steps is not None else _effective_output_steps(parity_mode, configured_nout=run_config.time.nout)
|
|
626
|
+
initial_state = (
|
|
627
|
+
None
|
|
628
|
+
if restart_state is None
|
|
629
|
+
else Fluid1DState(
|
|
630
|
+
density=jnp.asarray(restart_state.variables[f"N{section}"], dtype=jnp.float64),
|
|
631
|
+
pressure=jnp.asarray(restart_state.variables[f"P{section}"], dtype=jnp.float64),
|
|
632
|
+
momentum=jnp.asarray(restart_state.variables[f"NV{section}"], dtype=jnp.float64),
|
|
633
|
+
)
|
|
634
|
+
)
|
|
635
|
+
time_offset = 0.0 if restart_state is None else restart_state.time_offset
|
|
636
|
+
history = advance_mms_history(
|
|
637
|
+
config,
|
|
638
|
+
section=section,
|
|
639
|
+
mesh=mesh,
|
|
640
|
+
metrics=metrics,
|
|
641
|
+
atomic_mass=atomic_mass,
|
|
642
|
+
timestep=run_config.time.timestep,
|
|
643
|
+
steps=steps,
|
|
644
|
+
substeps=20,
|
|
645
|
+
initial_state=initial_state,
|
|
646
|
+
start_time=time_offset,
|
|
647
|
+
)
|
|
648
|
+
time_points = tuple(time_offset + run_config.time.timestep * index for index in range(steps + 1))
|
|
649
|
+
return time_points, {
|
|
650
|
+
f"N{section}": np.asarray(history.density_history, dtype=np.float64),
|
|
651
|
+
f"P{section}": np.asarray(history.pressure_history, dtype=np.float64),
|
|
652
|
+
f"NV{section}": np.asarray(history.momentum_history, dtype=np.float64),
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def _execute_electrostatic_vorticity_case(
|
|
657
|
+
config: BoutConfig,
|
|
658
|
+
run_config: RunConfiguration,
|
|
659
|
+
mesh: StructuredMesh,
|
|
660
|
+
metrics: StructuredMetrics,
|
|
661
|
+
*,
|
|
662
|
+
parity_mode: str,
|
|
663
|
+
restart_state: NativeRestartState | None = None,
|
|
664
|
+
output_steps: int | None = None,
|
|
665
|
+
) -> tuple[tuple[float, ...], dict[str, Any]]:
|
|
666
|
+
initial = (
|
|
667
|
+
apply_vorticity_boundaries(_initialize_species_field(config, "Vort", mesh), mesh)
|
|
668
|
+
if restart_state is None
|
|
669
|
+
else apply_vorticity_boundaries(np.asarray(restart_state.variables["Vort"], dtype=np.float64), mesh)
|
|
670
|
+
)
|
|
671
|
+
time_offset = 0.0 if restart_state is None else restart_state.time_offset
|
|
672
|
+
average_atomic_mass = float(config.parsed("vorticity", "average_atomic_mass")) if config.has_option("vorticity", "average_atomic_mass") else 2.0
|
|
673
|
+
operator = build_vorticity_operator(mesh=mesh, metrics=metrics, average_atomic_mass=average_atomic_mass)
|
|
674
|
+
|
|
675
|
+
if parity_mode == "one_rhs":
|
|
676
|
+
rhs = compute_vorticity_rhs(initial, mesh=mesh, metrics=metrics, operator=operator)
|
|
677
|
+
return (0.0,), {"ddt(Vort)": np.asarray(rhs.vorticity[None, ...], dtype=np.float64)}
|
|
678
|
+
|
|
679
|
+
steps = output_steps if output_steps is not None else _effective_output_steps(parity_mode, configured_nout=run_config.time.nout)
|
|
680
|
+
history = advance_vorticity_history(
|
|
681
|
+
initial,
|
|
682
|
+
mesh=mesh,
|
|
683
|
+
metrics=metrics,
|
|
684
|
+
operator=operator,
|
|
685
|
+
timestep=run_config.time.timestep,
|
|
686
|
+
steps=steps,
|
|
687
|
+
start_time=time_offset,
|
|
688
|
+
rtol=1e-6,
|
|
689
|
+
atol=1e-8,
|
|
690
|
+
mxstep=20000,
|
|
691
|
+
)
|
|
692
|
+
time_points = tuple(time_offset + run_config.time.timestep * index for index in range(steps + 1))
|
|
693
|
+
return time_points, {
|
|
694
|
+
"Vort": np.asarray(history.vorticity_history, dtype=np.float64),
|
|
695
|
+
"phi": np.asarray(history.potential_history, dtype=np.float64),
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _is_supported_periodic_fluid_mms_case(
|
|
700
|
+
config: BoutConfig,
|
|
701
|
+
run_config: RunConfiguration,
|
|
702
|
+
mesh: StructuredMesh,
|
|
703
|
+
metrics: StructuredMetrics,
|
|
704
|
+
) -> bool:
|
|
705
|
+
implementations = tuple(component.implementation for component in run_config.components)
|
|
706
|
+
if implementations != ("evolve_density", "evolve_pressure", "evolve_momentum"):
|
|
707
|
+
return False
|
|
708
|
+
if len({component.section for component in run_config.components}) != 1:
|
|
709
|
+
return False
|
|
710
|
+
section = run_config.components[0].section
|
|
711
|
+
if run_config.mesh.nx != 1 or run_config.mesh.nz != 1:
|
|
712
|
+
return False
|
|
713
|
+
if run_config.solver.mms is not True:
|
|
714
|
+
return False
|
|
715
|
+
if bool(config.parsed(section, "thermal_conduction")) if config.has_option(section, "thermal_conduction") else True:
|
|
716
|
+
return False
|
|
717
|
+
if bool(config.parsed(section, "p_div_v")) if config.has_option(section, "p_div_v") else False:
|
|
718
|
+
return False
|
|
719
|
+
if not _uniform_identity_parallel_metric(mesh, metrics=metrics):
|
|
720
|
+
return False
|
|
721
|
+
return all(
|
|
722
|
+
config.has_section(name) and config.has_option(name, "solution") and config.has_option(name, "source")
|
|
723
|
+
for name in (f"N{section}", f"P{section}", f"NV{section}")
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def _is_supported_electrostatic_vorticity_case(
|
|
728
|
+
config: BoutConfig,
|
|
729
|
+
run_config: RunConfiguration,
|
|
730
|
+
mesh: StructuredMesh,
|
|
731
|
+
metrics: StructuredMetrics,
|
|
732
|
+
) -> bool:
|
|
733
|
+
if tuple(component.implementation for component in run_config.components) != ("vorticity",):
|
|
734
|
+
return False
|
|
735
|
+
if run_config.mesh.ny != 1 or run_config.mesh.myg != 0:
|
|
736
|
+
return False
|
|
737
|
+
if mesh.mxg != 2:
|
|
738
|
+
return False
|
|
739
|
+
if not config.has_section("Vort") or not config.has_option("Vort", "function"):
|
|
740
|
+
return False
|
|
741
|
+
option_defaults = {
|
|
742
|
+
"diamagnetic": False,
|
|
743
|
+
"diamagnetic_polarisation": False,
|
|
744
|
+
"bndry_flux": False,
|
|
745
|
+
"poloidal_flows": False,
|
|
746
|
+
"split_n0": False,
|
|
747
|
+
"phi_dissipation": False,
|
|
748
|
+
"vort_dissipation": False,
|
|
749
|
+
"collisional_friction": False,
|
|
750
|
+
"phi_boundary_relax": False,
|
|
751
|
+
"phi_sheath_dissipation": False,
|
|
752
|
+
"damp_core_vorticity": False,
|
|
753
|
+
}
|
|
754
|
+
for key, expected in option_defaults.items():
|
|
755
|
+
value = bool(config.parsed("vorticity", key)) if config.has_option("vorticity", key) else (True if key == "phi_dissipation" else False)
|
|
756
|
+
if value != expected:
|
|
757
|
+
return False
|
|
758
|
+
exb_advection = bool(config.parsed("vorticity", "exb_advection")) if config.has_option("vorticity", "exb_advection") else True
|
|
759
|
+
exb_advection_simplified = (
|
|
760
|
+
bool(config.parsed("vorticity", "exb_advection_simplified"))
|
|
761
|
+
if config.has_option("vorticity", "exb_advection_simplified")
|
|
762
|
+
else True
|
|
763
|
+
)
|
|
764
|
+
if not exb_advection or not exb_advection_simplified:
|
|
765
|
+
return False
|
|
766
|
+
if not np.allclose(np.asarray(metrics.g23), 0.0, rtol=1e-12, atol=1e-12):
|
|
767
|
+
return False
|
|
768
|
+
return True
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def _uniform_identity_parallel_metric(mesh: StructuredMesh, *, metrics: StructuredMetrics) -> bool:
|
|
772
|
+
if not np.allclose(np.asarray(metrics.J), 1.0, rtol=1e-12, atol=1e-12):
|
|
773
|
+
return False
|
|
774
|
+
if not np.allclose(np.asarray(metrics.g22), 1.0, rtol=1e-12, atol=1e-12):
|
|
775
|
+
return False
|
|
776
|
+
if not np.allclose(np.asarray(metrics.g23), 0.0, rtol=1e-12, atol=1e-12):
|
|
777
|
+
return False
|
|
778
|
+
dy = np.asarray(metrics.dy[:, mesh.ystart : mesh.yend + 1, :], dtype=np.float64)
|
|
779
|
+
return np.allclose(dy, dy[:, :1, :], rtol=1e-12, atol=1e-12)
|