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
drbx/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .runtime import configure_jax_runtime
|
|
2
|
+
|
|
3
|
+
# Configure the default precision/cache policy as early as possible so user
|
|
4
|
+
# imports that touch JAX arrays through drbx keep the requested dtype.
|
|
5
|
+
configure_jax_runtime()
|
|
6
|
+
|
|
7
|
+
from .config.boutinp import BoutConfig, NumericResolver, ROOT_SECTION, load_bout_input, parse_bout_input
|
|
8
|
+
from .config.normalization import MetricPolicy, ModelNormalization
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"BoutConfig",
|
|
12
|
+
"MetricPolicy",
|
|
13
|
+
"ModelNormalization",
|
|
14
|
+
"NumericResolver",
|
|
15
|
+
"ROOT_SECTION",
|
|
16
|
+
"load_bout_input",
|
|
17
|
+
"parse_bout_input",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
__version__ = "2.0.0.dev0"
|
drbx/__main__.py
ADDED
drbx/cli.py
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import platform
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any, Mapping
|
|
10
|
+
|
|
11
|
+
from .config.boutinp import load_bout_input
|
|
12
|
+
from .runtime import configure_jax_runtime, resolve_runtime_precision
|
|
13
|
+
from .runtime.run_config import RunConfiguration
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(argv: list[str] | None = None) -> int:
|
|
17
|
+
normalized_argv = _normalize_cli_argv(sys.argv[1:] if argv is None else argv)
|
|
18
|
+
parser = _build_parser()
|
|
19
|
+
args = parser.parse_args(normalized_argv)
|
|
20
|
+
return args.command(args)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog="drbx",
|
|
26
|
+
description="Inspect or run JAX-DRB inputs using the native model configuration structure.",
|
|
27
|
+
)
|
|
28
|
+
subparsers = parser.add_subparsers(dest="subcommand", required=False)
|
|
29
|
+
|
|
30
|
+
inspect_parser = subparsers.add_parser(
|
|
31
|
+
"inspect", help="Inspect an input deck and print the resolved plan."
|
|
32
|
+
)
|
|
33
|
+
inspect_parser.add_argument("input_file", type=Path)
|
|
34
|
+
inspect_parser.set_defaults(command=_inspect_command)
|
|
35
|
+
|
|
36
|
+
run_parser = subparsers.add_parser(
|
|
37
|
+
"run",
|
|
38
|
+
help="Run a supported native input, write result artifacts, and optionally continue from a restart bundle.",
|
|
39
|
+
)
|
|
40
|
+
run_parser.add_argument("input_file", type=Path)
|
|
41
|
+
run_parser.add_argument(
|
|
42
|
+
"--dry-run",
|
|
43
|
+
action="store_true",
|
|
44
|
+
help="Only inspect configuration and exit successfully.",
|
|
45
|
+
)
|
|
46
|
+
run_parser.add_argument(
|
|
47
|
+
"--precision",
|
|
48
|
+
choices=("float32", "float64"),
|
|
49
|
+
default=None,
|
|
50
|
+
help="Override runtime floating-point precision for this run.",
|
|
51
|
+
)
|
|
52
|
+
run_parser.add_argument(
|
|
53
|
+
"--case-name",
|
|
54
|
+
type=str,
|
|
55
|
+
default=None,
|
|
56
|
+
help="Optional case label for output metadata.",
|
|
57
|
+
)
|
|
58
|
+
run_parser.add_argument(
|
|
59
|
+
"--output-dir",
|
|
60
|
+
type=Path,
|
|
61
|
+
default=None,
|
|
62
|
+
help="Write standard run artifacts into this directory.",
|
|
63
|
+
)
|
|
64
|
+
run_parser.add_argument(
|
|
65
|
+
"--json-out", type=Path, default=None, help="Write the portable summary JSON."
|
|
66
|
+
)
|
|
67
|
+
run_parser.add_argument(
|
|
68
|
+
"--arrays-out", type=Path, default=None, help="Write the portable array NPZ."
|
|
69
|
+
)
|
|
70
|
+
run_parser.add_argument(
|
|
71
|
+
"--restart-out", type=Path, default=None, help="Write the restart NPZ bundle."
|
|
72
|
+
)
|
|
73
|
+
run_parser.add_argument(
|
|
74
|
+
"--log-out", type=Path, default=None, help="Write a verbose run log JSON."
|
|
75
|
+
)
|
|
76
|
+
run_parser.add_argument(
|
|
77
|
+
"--restart-in",
|
|
78
|
+
type=Path,
|
|
79
|
+
default=None,
|
|
80
|
+
help="Resume from a previously written restart NPZ bundle.",
|
|
81
|
+
)
|
|
82
|
+
run_parser.add_argument(
|
|
83
|
+
"--resume-steps",
|
|
84
|
+
type=int,
|
|
85
|
+
default=None,
|
|
86
|
+
help="Additional output intervals to run after loading --restart-in.",
|
|
87
|
+
)
|
|
88
|
+
run_parser.add_argument(
|
|
89
|
+
"--verbose",
|
|
90
|
+
action="store_true",
|
|
91
|
+
help="Emit detailed staged terminal output for this run.",
|
|
92
|
+
)
|
|
93
|
+
run_parser.add_argument(
|
|
94
|
+
"--quiet", action="store_true", help="Suppress the pretty terminal run summary."
|
|
95
|
+
)
|
|
96
|
+
run_parser.set_defaults(command=_run_command)
|
|
97
|
+
|
|
98
|
+
parser.set_defaults(command=_default_command)
|
|
99
|
+
return parser
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _default_command(args: argparse.Namespace) -> int:
|
|
103
|
+
if getattr(args, "subcommand", None) is None:
|
|
104
|
+
raise SystemExit(
|
|
105
|
+
"Use `drbx inspect <input>` or `drbx <input> --dry-run`."
|
|
106
|
+
)
|
|
107
|
+
return args.command(args)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _normalize_cli_argv(argv: list[str]) -> list[str]:
|
|
111
|
+
if not argv:
|
|
112
|
+
return argv
|
|
113
|
+
known_subcommands = {
|
|
114
|
+
"inspect",
|
|
115
|
+
"run",
|
|
116
|
+
}
|
|
117
|
+
head = argv[0]
|
|
118
|
+
if head in known_subcommands or head.startswith("-"):
|
|
119
|
+
return argv
|
|
120
|
+
return ["run", *argv]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _inspect_command(args: argparse.Namespace) -> int:
|
|
124
|
+
config = load_bout_input(args.input_file)
|
|
125
|
+
configure_jax_runtime(precision=resolve_runtime_precision(config=config))
|
|
126
|
+
run_config = RunConfiguration.from_config(config)
|
|
127
|
+
|
|
128
|
+
print(f"input: {args.input_file}")
|
|
129
|
+
print(f"sections: {', '.join(config.section_names())}")
|
|
130
|
+
print(f"time: nout={run_config.time.nout}, timestep={run_config.time.timestep:g}")
|
|
131
|
+
print(
|
|
132
|
+
"mesh: "
|
|
133
|
+
f"nx={run_config.mesh.nx}, ny={run_config.mesh.ny}, nz={run_config.mesh.nz}, "
|
|
134
|
+
f"MXG={run_config.mesh.mxg}, MYG={run_config.mesh.myg}, "
|
|
135
|
+
f"parallel_transform={run_config.mesh.parallel_transform.type}"
|
|
136
|
+
)
|
|
137
|
+
print(
|
|
138
|
+
f"scheduled components: {', '.join(request.label for request in run_config.components)}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if run_config.normalization is not None:
|
|
142
|
+
normalization = run_config.normalization
|
|
143
|
+
print(
|
|
144
|
+
"normalization: "
|
|
145
|
+
f"Nnorm={normalization.Nnorm:g}, "
|
|
146
|
+
f"Tnorm={normalization.Tnorm:g}, "
|
|
147
|
+
f"Bnorm={normalization.Bnorm:g}, "
|
|
148
|
+
f"Cs0={normalization.Cs0:.8e}, "
|
|
149
|
+
f"Omega_ci={normalization.Omega_ci:.8e}, "
|
|
150
|
+
f"rho_s0={normalization.rho_s0:.8e}"
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
print("normalization: unresolved (missing one or more of Nnorm, Tnorm, Bnorm)")
|
|
154
|
+
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _run_command(args: argparse.Namespace) -> int:
|
|
159
|
+
if args.dry_run:
|
|
160
|
+
return _inspect_command(args)
|
|
161
|
+
config = load_bout_input(args.input_file)
|
|
162
|
+
run_config = RunConfiguration.from_config(config)
|
|
163
|
+
resolved_precision = resolve_runtime_precision(
|
|
164
|
+
requested=args.precision, config=config
|
|
165
|
+
)
|
|
166
|
+
cache_dir = configure_jax_runtime(precision=resolved_precision)
|
|
167
|
+
import jax
|
|
168
|
+
from .native import run_input_case
|
|
169
|
+
from .native.deck_runner import (
|
|
170
|
+
NativeRestartState,
|
|
171
|
+
build_portable_array_payload,
|
|
172
|
+
build_restart_state,
|
|
173
|
+
write_portable_array_payload,
|
|
174
|
+
write_portable_summary_payload,
|
|
175
|
+
)
|
|
176
|
+
from .runtime import (
|
|
177
|
+
build_run_log_payload,
|
|
178
|
+
load_restart_bundle,
|
|
179
|
+
print_run_log,
|
|
180
|
+
write_restart_bundle,
|
|
181
|
+
write_run_log_payload,
|
|
182
|
+
)
|
|
183
|
+
from .runtime.output import build_run_event, print_run_event
|
|
184
|
+
|
|
185
|
+
command_started_at = time.perf_counter()
|
|
186
|
+
output_dir = args.output_dir or _config_path(config, "output", "directory")
|
|
187
|
+
case_name = (
|
|
188
|
+
args.case_name
|
|
189
|
+
or _config_string(config, "output", "case_name")
|
|
190
|
+
or args.input_file.stem
|
|
191
|
+
)
|
|
192
|
+
restart_in = args.restart_in or _config_path(config, "restart", "input")
|
|
193
|
+
resume_steps = (
|
|
194
|
+
args.resume_steps
|
|
195
|
+
if args.resume_steps is not None
|
|
196
|
+
else _config_int(config, "restart", "resume_steps")
|
|
197
|
+
)
|
|
198
|
+
logging_quiet = _config_bool(config, "runtime:logging", "quiet", default=False)
|
|
199
|
+
logging_verbose = _config_optional_bool(config, "runtime:logging", "verbose")
|
|
200
|
+
logging_verbosity = _config_string(config, "runtime:logging", "verbosity")
|
|
201
|
+
if logging_verbosity is None:
|
|
202
|
+
logging_verbosity = "detailed" if logging_verbose else "summary"
|
|
203
|
+
if args.verbose:
|
|
204
|
+
logging_verbosity = "detailed"
|
|
205
|
+
emit_terminal_log = not args.quiet and not logging_quiet
|
|
206
|
+
write_summary = _config_bool(config, "output", "write_summary", default=True)
|
|
207
|
+
write_arrays = _config_bool(config, "output", "write_arrays", default=True)
|
|
208
|
+
write_restart = _config_bool(config, "output", "write_restart", default=True)
|
|
209
|
+
write_log = _config_bool(config, "output", "write_log", default=True)
|
|
210
|
+
if args.json_out is None:
|
|
211
|
+
args.json_out = _config_path(config, "output", "summary_json")
|
|
212
|
+
if args.arrays_out is None:
|
|
213
|
+
args.arrays_out = _config_path(config, "output", "arrays_npz")
|
|
214
|
+
if args.restart_out is None:
|
|
215
|
+
args.restart_out = _config_path(config, "output", "restart_npz")
|
|
216
|
+
if args.log_out is None:
|
|
217
|
+
args.log_out = _config_path(config, "output", "run_log_json")
|
|
218
|
+
events: list[dict[str, Any]] = []
|
|
219
|
+
|
|
220
|
+
def record_event(stage: str, message: str, **details: Any) -> None:
|
|
221
|
+
event = build_run_event(
|
|
222
|
+
stage=stage,
|
|
223
|
+
message=message,
|
|
224
|
+
elapsed_seconds=time.perf_counter() - command_started_at,
|
|
225
|
+
details=details or None,
|
|
226
|
+
)
|
|
227
|
+
events.append(event)
|
|
228
|
+
if emit_terminal_log:
|
|
229
|
+
print_run_event(event, verbosity=logging_verbosity)
|
|
230
|
+
|
|
231
|
+
record_event(
|
|
232
|
+
"configuration",
|
|
233
|
+
"Loaded input configuration",
|
|
234
|
+
input_file=args.input_file,
|
|
235
|
+
case_name=case_name,
|
|
236
|
+
capability_tier="native_exact",
|
|
237
|
+
precision=resolved_precision,
|
|
238
|
+
nout=run_config.time.nout,
|
|
239
|
+
timestep=run_config.time.timestep,
|
|
240
|
+
output_dir=output_dir if output_dir is not None else "(none)",
|
|
241
|
+
verbosity=logging_verbosity,
|
|
242
|
+
verbose=logging_verbosity == "detailed",
|
|
243
|
+
)
|
|
244
|
+
restart_state = None
|
|
245
|
+
bundle = None
|
|
246
|
+
if restart_in is not None:
|
|
247
|
+
bundle = load_restart_bundle(restart_in)
|
|
248
|
+
restart_state = NativeRestartState(
|
|
249
|
+
time_offset=bundle.current_time,
|
|
250
|
+
completed_steps=bundle.completed_steps,
|
|
251
|
+
configured_timestep=bundle.configured_timestep,
|
|
252
|
+
variables=bundle.state_variables,
|
|
253
|
+
)
|
|
254
|
+
record_event(
|
|
255
|
+
"restart",
|
|
256
|
+
"Loaded restart bundle",
|
|
257
|
+
restart_in=restart_in,
|
|
258
|
+
current_time=bundle.current_time,
|
|
259
|
+
completed_steps=bundle.completed_steps,
|
|
260
|
+
variables=",".join(sorted(bundle.state_variables)),
|
|
261
|
+
requested_resume_steps=resume_steps
|
|
262
|
+
if resume_steps is not None
|
|
263
|
+
else "(default)",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def relay_native_event(event: Mapping[str, Any]) -> None:
|
|
267
|
+
if str(event.get("stage", "")) != "progress":
|
|
268
|
+
return
|
|
269
|
+
details = event.get("details")
|
|
270
|
+
if isinstance(details, Mapping):
|
|
271
|
+
record_event(
|
|
272
|
+
str(event.get("stage", "progress")),
|
|
273
|
+
str(event.get("message", "Native progress update")),
|
|
274
|
+
**dict(details),
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
record_event(
|
|
278
|
+
str(event.get("stage", "progress")),
|
|
279
|
+
str(event.get("message", "Native progress update")),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
started_at = time.perf_counter()
|
|
283
|
+
record_event(
|
|
284
|
+
"run", "Launching native run", mode="run", restart=restart_state is not None
|
|
285
|
+
)
|
|
286
|
+
result = run_input_case(
|
|
287
|
+
args.input_file,
|
|
288
|
+
case_name=case_name,
|
|
289
|
+
parity_mode="run",
|
|
290
|
+
restart_state=restart_state,
|
|
291
|
+
output_steps=resume_steps,
|
|
292
|
+
verbose=False,
|
|
293
|
+
event_logger=relay_native_event,
|
|
294
|
+
)
|
|
295
|
+
elapsed_seconds = time.perf_counter() - started_at
|
|
296
|
+
record_event(
|
|
297
|
+
"run",
|
|
298
|
+
"Native run completed",
|
|
299
|
+
elapsed_seconds=f"{elapsed_seconds:.3f}",
|
|
300
|
+
stored_states=len(result.time_points),
|
|
301
|
+
compare_variables=",".join(result.variables),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
output_paths: dict[str, str] = {}
|
|
305
|
+
if output_dir is not None:
|
|
306
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
307
|
+
if args.json_out is None and write_summary:
|
|
308
|
+
args.json_out = output_dir / f"{case_name}_summary.json"
|
|
309
|
+
if args.arrays_out is None and write_arrays:
|
|
310
|
+
args.arrays_out = output_dir / f"{case_name}_arrays.npz"
|
|
311
|
+
if args.restart_out is None and write_restart:
|
|
312
|
+
args.restart_out = output_dir / f"{case_name}_restart.npz"
|
|
313
|
+
if args.log_out is None and write_log:
|
|
314
|
+
args.log_out = output_dir / f"{case_name}_run_log.json"
|
|
315
|
+
record_event(
|
|
316
|
+
"artifacts",
|
|
317
|
+
"Resolved artifact destinations",
|
|
318
|
+
summary_json=args.json_out if args.json_out is not None else "(disabled)",
|
|
319
|
+
arrays_npz=args.arrays_out if args.arrays_out is not None else "(disabled)",
|
|
320
|
+
restart_npz=args.restart_out
|
|
321
|
+
if args.restart_out is not None
|
|
322
|
+
else "(disabled)",
|
|
323
|
+
run_log_json=args.log_out if args.log_out is not None else "(disabled)",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if args.json_out is not None:
|
|
327
|
+
path = write_portable_summary_payload(result.payload, args.json_out)
|
|
328
|
+
output_paths["summary_json"] = _sanitize_logged_path(path) or str(path)
|
|
329
|
+
record_event("artifacts", "Wrote summary JSON", path=path)
|
|
330
|
+
if args.arrays_out is not None:
|
|
331
|
+
array_payload = build_portable_array_payload(
|
|
332
|
+
case_name=str(result.payload["case_name"]),
|
|
333
|
+
parity_mode=str(result.payload["parity_mode"]),
|
|
334
|
+
capability_tier=str(result.payload.get("capability_tier", "native_exact")),
|
|
335
|
+
compare_variables=tuple(str(name) for name in result.variables),
|
|
336
|
+
component_labels=tuple(result.payload.get("component_labels", [])),
|
|
337
|
+
dimensions=result.payload.get("dimensions", {}),
|
|
338
|
+
time_points=tuple(float(value) for value in result.time_points),
|
|
339
|
+
dataset_scalars=result.payload.get("dataset_scalars", {}),
|
|
340
|
+
variables=result.variables,
|
|
341
|
+
overrides=tuple(result.payload.get("overrides", [])),
|
|
342
|
+
configured_nout=result.payload.get("configured_nout"),
|
|
343
|
+
configured_timestep=result.payload.get("configured_timestep"),
|
|
344
|
+
producer=str(result.payload.get("producer", "drbx")),
|
|
345
|
+
)
|
|
346
|
+
path = write_portable_array_payload(array_payload, args.arrays_out)
|
|
347
|
+
output_paths["arrays_npz"] = _sanitize_logged_path(path) or str(path)
|
|
348
|
+
record_event(
|
|
349
|
+
"artifacts", "Wrote arrays NPZ", path=path, variables=len(result.variables)
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
restart_bundle = build_restart_state(result, parity_mode="run")
|
|
353
|
+
if args.restart_out is not None and restart_bundle is not None:
|
|
354
|
+
path = write_restart_bundle(restart_bundle, args.restart_out)
|
|
355
|
+
output_paths["restart_npz"] = _sanitize_logged_path(path) or str(path)
|
|
356
|
+
record_event(
|
|
357
|
+
"artifacts",
|
|
358
|
+
"Wrote restart bundle",
|
|
359
|
+
path=path,
|
|
360
|
+
completed_steps=restart_bundle.completed_steps,
|
|
361
|
+
current_time=restart_bundle.current_time,
|
|
362
|
+
)
|
|
363
|
+
elif args.restart_out is not None and restart_bundle is None:
|
|
364
|
+
output_paths["restart_npz"] = "(unsupported for this component set)"
|
|
365
|
+
record_event(
|
|
366
|
+
"artifacts",
|
|
367
|
+
"Restart bundle unsupported for this run",
|
|
368
|
+
path=args.restart_out,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if args.log_out is not None:
|
|
372
|
+
output_paths["run_log_json"] = _sanitize_logged_path(args.log_out) or str(
|
|
373
|
+
args.log_out
|
|
374
|
+
)
|
|
375
|
+
if output_paths:
|
|
376
|
+
record_event("artifacts", "Planned run artifacts", **output_paths)
|
|
377
|
+
|
|
378
|
+
log_payload = build_run_log_payload(
|
|
379
|
+
input_file=_sanitize_logged_path(args.input_file) or args.input_file,
|
|
380
|
+
case_name=case_name,
|
|
381
|
+
parity_mode="run",
|
|
382
|
+
capability_tier=str(result.payload.get("capability_tier", "native_exact")),
|
|
383
|
+
component_labels=tuple(result.payload.get("component_labels", [])),
|
|
384
|
+
time_points=tuple(float(value) for value in result.time_points),
|
|
385
|
+
dimensions=result.payload.get("dimensions", {}),
|
|
386
|
+
compare_variables=tuple(result.payload.get("compare_variables", [])),
|
|
387
|
+
restart_supported=restart_bundle is not None,
|
|
388
|
+
outputs=output_paths,
|
|
389
|
+
variable_summaries=result.payload.get("variable_summaries", {}),
|
|
390
|
+
run_configuration=_serialize_run_configuration(
|
|
391
|
+
run_config,
|
|
392
|
+
precision=resolved_precision,
|
|
393
|
+
backend=jax.default_backend(),
|
|
394
|
+
device=str(jax.devices()[0]) if jax.devices() else None,
|
|
395
|
+
jax_version=getattr(jax, "__version__", None),
|
|
396
|
+
cache_dir=cache_dir,
|
|
397
|
+
elapsed_seconds=elapsed_seconds,
|
|
398
|
+
output_directory=output_dir,
|
|
399
|
+
logging_verbosity=logging_verbosity,
|
|
400
|
+
logging_quiet=emit_terminal_log is False,
|
|
401
|
+
restart_in=restart_in,
|
|
402
|
+
resume_steps=resume_steps,
|
|
403
|
+
working_directory=Path.cwd(),
|
|
404
|
+
),
|
|
405
|
+
restart_info=_serialize_restart_info(
|
|
406
|
+
restart_in=restart_in,
|
|
407
|
+
loaded_bundle=bundle if restart_in is not None else None,
|
|
408
|
+
requested_additional_steps=resume_steps,
|
|
409
|
+
saved_bundle=restart_bundle,
|
|
410
|
+
),
|
|
411
|
+
events=tuple(events),
|
|
412
|
+
)
|
|
413
|
+
if args.log_out is not None:
|
|
414
|
+
record_event(
|
|
415
|
+
"artifacts",
|
|
416
|
+
"Writing verbose run log JSON",
|
|
417
|
+
path=args.log_out,
|
|
418
|
+
event_count=len(events),
|
|
419
|
+
)
|
|
420
|
+
log_payload["events"] = list(events)
|
|
421
|
+
log_payload["event_count"] = len(events)
|
|
422
|
+
log_payload["event_stages"] = [str(event.get("stage", "")) for event in events]
|
|
423
|
+
path = write_run_log_payload(log_payload, args.log_out)
|
|
424
|
+
output_paths["run_log_json"] = _sanitize_logged_path(path) or str(path)
|
|
425
|
+
log_payload["outputs"] = output_paths
|
|
426
|
+
log_payload["events"] = list(events)
|
|
427
|
+
log_payload["event_count"] = len(events)
|
|
428
|
+
log_payload["event_stages"] = [str(event.get("stage", "")) for event in events]
|
|
429
|
+
|
|
430
|
+
if emit_terminal_log:
|
|
431
|
+
print_run_log(log_payload, verbosity=logging_verbosity)
|
|
432
|
+
return 0
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _serialize_run_configuration(
|
|
436
|
+
run_config: RunConfiguration,
|
|
437
|
+
*,
|
|
438
|
+
precision: str,
|
|
439
|
+
backend: str | None = None,
|
|
440
|
+
device: str | None = None,
|
|
441
|
+
cache_dir: Path | None = None,
|
|
442
|
+
elapsed_seconds: float | None = None,
|
|
443
|
+
output_directory: Path | None = None,
|
|
444
|
+
logging_verbosity: str | None = None,
|
|
445
|
+
logging_quiet: bool | None = None,
|
|
446
|
+
restart_in: Path | None = None,
|
|
447
|
+
resume_steps: int | None = None,
|
|
448
|
+
jax_version: str | None = None,
|
|
449
|
+
working_directory: Path | None = None,
|
|
450
|
+
) -> dict[str, object]:
|
|
451
|
+
return {
|
|
452
|
+
"time": {
|
|
453
|
+
"nout": run_config.time.nout,
|
|
454
|
+
"timestep": run_config.time.timestep,
|
|
455
|
+
},
|
|
456
|
+
"mesh": {
|
|
457
|
+
"nx": run_config.mesh.nx,
|
|
458
|
+
"ny": run_config.mesh.ny,
|
|
459
|
+
"nz": run_config.mesh.nz,
|
|
460
|
+
"mxg": run_config.mesh.mxg,
|
|
461
|
+
"myg": run_config.mesh.myg,
|
|
462
|
+
"file": run_config.mesh.file,
|
|
463
|
+
"parallel_transform": run_config.mesh.parallel_transform.type,
|
|
464
|
+
},
|
|
465
|
+
"solver": {
|
|
466
|
+
"type": run_config.solver.type,
|
|
467
|
+
"mxstep": run_config.solver.mxstep,
|
|
468
|
+
"rtol": run_config.solver.rtol,
|
|
469
|
+
"atol": run_config.solver.atol,
|
|
470
|
+
"use_precon": run_config.solver.use_precon,
|
|
471
|
+
"cvode_max_order": run_config.solver.cvode_max_order,
|
|
472
|
+
},
|
|
473
|
+
"runtime": {
|
|
474
|
+
"precision": precision,
|
|
475
|
+
"backend": backend,
|
|
476
|
+
"device": device,
|
|
477
|
+
"jax_version": jax_version,
|
|
478
|
+
"python_version": platform.python_version(),
|
|
479
|
+
"platform": platform.platform(),
|
|
480
|
+
"process_id": os.getpid(),
|
|
481
|
+
"compilation_cache_dir": _sanitize_logged_path(cache_dir),
|
|
482
|
+
"elapsed_seconds": elapsed_seconds,
|
|
483
|
+
"logging": {
|
|
484
|
+
"verbosity": logging_verbosity,
|
|
485
|
+
"verbose": logging_verbosity == "detailed",
|
|
486
|
+
"quiet": logging_quiet,
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
"output": {
|
|
490
|
+
"directory": _sanitize_logged_path(output_directory),
|
|
491
|
+
"working_directory": _sanitize_logged_path(working_directory),
|
|
492
|
+
},
|
|
493
|
+
"restart_request": {
|
|
494
|
+
"restart_in": _sanitize_logged_path(restart_in),
|
|
495
|
+
"resume_steps": resume_steps,
|
|
496
|
+
},
|
|
497
|
+
"components": [request.label for request in run_config.components],
|
|
498
|
+
"root_scalars": dict(run_config.root_scalars),
|
|
499
|
+
"model_scalars": dict(run_config.model_scalars),
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _serialize_restart_info(
|
|
504
|
+
*,
|
|
505
|
+
restart_in: Path | None,
|
|
506
|
+
loaded_bundle,
|
|
507
|
+
requested_additional_steps: int | None,
|
|
508
|
+
saved_bundle,
|
|
509
|
+
) -> dict[str, object]:
|
|
510
|
+
payload: dict[str, object] = {}
|
|
511
|
+
if restart_in is not None and loaded_bundle is not None:
|
|
512
|
+
payload["loaded_from"] = _sanitize_logged_path(restart_in)
|
|
513
|
+
payload["start_time"] = loaded_bundle.current_time
|
|
514
|
+
payload["input_completed_steps"] = loaded_bundle.completed_steps
|
|
515
|
+
payload["loaded_state_variables"] = sorted(loaded_bundle.state_variables)
|
|
516
|
+
if requested_additional_steps is not None:
|
|
517
|
+
payload["requested_additional_steps"] = requested_additional_steps
|
|
518
|
+
if saved_bundle is not None:
|
|
519
|
+
payload["saved_completed_steps"] = saved_bundle.completed_steps
|
|
520
|
+
payload["saved_current_time"] = saved_bundle.current_time
|
|
521
|
+
payload["saved_state_variables"] = sorted(saved_bundle.state_variables)
|
|
522
|
+
return payload
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _sanitize_logged_path(path: str | Path | None) -> str | None:
|
|
526
|
+
if path is None:
|
|
527
|
+
return None
|
|
528
|
+
resolved = Path(path).expanduser()
|
|
529
|
+
try:
|
|
530
|
+
resolved = resolved.resolve()
|
|
531
|
+
except FileNotFoundError:
|
|
532
|
+
resolved = resolved.absolute()
|
|
533
|
+
try:
|
|
534
|
+
return resolved.relative_to(Path.cwd().resolve()).as_posix()
|
|
535
|
+
except ValueError:
|
|
536
|
+
pass
|
|
537
|
+
try:
|
|
538
|
+
return f"~/{resolved.relative_to(Path.home()).as_posix()}"
|
|
539
|
+
except ValueError:
|
|
540
|
+
return resolved.as_posix()
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _config_value(config, section: str, key: str, default: Any = None) -> Any:
|
|
544
|
+
if config.has_option(section, key):
|
|
545
|
+
return config.parsed(section, key)
|
|
546
|
+
return default
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _config_string(
|
|
550
|
+
config, section: str, key: str, default: str | None = None
|
|
551
|
+
) -> str | None:
|
|
552
|
+
value = _config_value(config, section, key, default)
|
|
553
|
+
if value is None:
|
|
554
|
+
return None
|
|
555
|
+
return str(value)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def _config_int(
|
|
559
|
+
config, section: str, key: str, default: int | None = None
|
|
560
|
+
) -> int | None:
|
|
561
|
+
value = _config_value(config, section, key, default)
|
|
562
|
+
if value is None:
|
|
563
|
+
return None
|
|
564
|
+
return int(value)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def _config_bool(config, section: str, key: str, default: bool = False) -> bool:
|
|
568
|
+
value = _config_value(config, section, key, default)
|
|
569
|
+
return bool(value)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _config_optional_bool(config, section: str, key: str) -> bool | None:
|
|
573
|
+
if not config.has_option(section, key):
|
|
574
|
+
return None
|
|
575
|
+
return bool(config.parsed(section, key))
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def _config_path(config, section: str, key: str) -> Path | None:
|
|
579
|
+
value = _config_value(config, section, key)
|
|
580
|
+
if value in (None, ""):
|
|
581
|
+
return None
|
|
582
|
+
return Path(str(value))
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
if __name__ == "__main__":
|
|
586
|
+
raise SystemExit(main())
|
drbx/config/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .boutinp import BoutConfig, NumericResolver, OptionEntry, OptionSection, OptionValue, ROOT_SECTION, load_bout_input, parse_bout_input, rewrite_input_precision
|
|
2
|
+
from .model import PUBLIC_MODEL_SECTION, has_model_section, locate_model_section
|
|
3
|
+
from .normalization import MetricPolicy, ModelNormalization
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"BoutConfig",
|
|
7
|
+
"MetricPolicy",
|
|
8
|
+
"ModelNormalization",
|
|
9
|
+
"NumericResolver",
|
|
10
|
+
"OptionEntry",
|
|
11
|
+
"OptionSection",
|
|
12
|
+
"OptionValue",
|
|
13
|
+
"PUBLIC_MODEL_SECTION",
|
|
14
|
+
"ROOT_SECTION",
|
|
15
|
+
"has_model_section",
|
|
16
|
+
"load_bout_input",
|
|
17
|
+
"locate_model_section",
|
|
18
|
+
"parse_bout_input",
|
|
19
|
+
"rewrite_input_precision",
|
|
20
|
+
]
|