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,184 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Mapping
5
+
6
+ from ..config.boutinp import BoutConfig, NumericResolver, ROOT_SECTION
7
+ from ..config.model import has_model_section, locate_model_section
8
+ from ..config.normalization import ModelNormalization
9
+ from .scheduler import ComponentRequest, expand_component_requests
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class TimeConfig:
14
+ nout: int
15
+ timestep: float
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ParallelTransformConfig:
20
+ type: str
21
+ options: Mapping[str, bool | int | float | str | tuple[str, ...]]
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class MeshScalarConfig:
26
+ nx: int | None
27
+ ny: int | None
28
+ nz: int | None
29
+ mxg: int
30
+ myg: int
31
+ mz: int | None
32
+ zperiod: float | None
33
+ file: str | None
34
+ extrapolate_y: bool | None
35
+ parallel_transform: ParallelTransformConfig
36
+ resolved_scalars: Mapping[str, float]
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class SolverConfig:
41
+ type: str | None
42
+ mxstep: int | None
43
+ rtol: float | None
44
+ atol: float | None
45
+ use_precon: bool | None
46
+ cvode_max_order: int | None
47
+ mms: bool | None
48
+ raw_options: Mapping[str, bool | int | float | str | tuple[str, ...]]
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class RunConfiguration:
53
+ time: TimeConfig
54
+ mesh: MeshScalarConfig
55
+ solver: SolverConfig
56
+ normalization: ModelNormalization | None
57
+ components: tuple[ComponentRequest, ...]
58
+ root_scalars: Mapping[str, float]
59
+ model_scalars: Mapping[str, float]
60
+
61
+ @classmethod
62
+ def from_config(cls, config: BoutConfig) -> "RunConfiguration":
63
+ resolver = NumericResolver(config)
64
+ time = TimeConfig(
65
+ nout=int(round(_resolve_required_scalar(config, resolver, ROOT_SECTION, "nout"))),
66
+ timestep=_resolve_required_scalar(config, resolver, ROOT_SECTION, "timestep"),
67
+ )
68
+ mesh = _build_mesh_config(config, resolver)
69
+ solver = _build_solver_config(config, resolver)
70
+ normalization = ModelNormalization.from_config(config) if _has_normalization(config) else None
71
+ model_section = locate_model_section(config) if has_model_section(config) else None
72
+ return cls(
73
+ time=time,
74
+ mesh=mesh,
75
+ solver=solver,
76
+ normalization=normalization,
77
+ components=expand_component_requests(config) if model_section is not None else (),
78
+ root_scalars=_resolved_scalars(config, resolver, ROOT_SECTION),
79
+ model_scalars=_resolved_scalars(config, resolver, model_section) if model_section is not None else {},
80
+ )
81
+
82
+
83
+ def _build_mesh_config(config: BoutConfig, resolver: NumericResolver) -> MeshScalarConfig:
84
+ mesh_scalars = _resolved_scalars(config, resolver, "mesh") if config.has_section("mesh") else {}
85
+ parallel_section = "mesh:paralleltransform"
86
+ parallel_options = dict(_resolved_parsed_options(config, parallel_section)) if config.has_section(parallel_section) else {}
87
+ parallel_type = str(parallel_options.get("type", "identity"))
88
+ file_value = config.parsed("mesh", "file") if config.has_option("mesh", "file") else None
89
+ extrapolate_y = bool(config.parsed("mesh", "extrapolate_y")) if config.has_option("mesh", "extrapolate_y") else None
90
+ return MeshScalarConfig(
91
+ nx=_optional_int(mesh_scalars.get("nx")),
92
+ ny=_optional_int(mesh_scalars.get("ny")),
93
+ nz=_optional_int(mesh_scalars.get("nz")),
94
+ mxg=_with_default(_optional_int(_optional_root_scalar(config, resolver, "MXG")), 2),
95
+ myg=_with_default(_optional_int(_optional_root_scalar(config, resolver, "MYG")), 2),
96
+ mz=_optional_int(_optional_root_scalar(config, resolver, "MZ")),
97
+ zperiod=_optional_root_scalar(config, resolver, "zperiod"),
98
+ file=file_value if isinstance(file_value, str) else None,
99
+ extrapolate_y=extrapolate_y,
100
+ parallel_transform=ParallelTransformConfig(type=parallel_type, options=parallel_options),
101
+ resolved_scalars=mesh_scalars,
102
+ )
103
+
104
+
105
+ def _build_solver_config(config: BoutConfig, resolver: NumericResolver) -> SolverConfig:
106
+ raw_options = dict(_resolved_parsed_options(config, "solver")) if config.has_section("solver") else {}
107
+ return SolverConfig(
108
+ type=str(raw_options["type"]) if "type" in raw_options else None,
109
+ mxstep=_optional_int(raw_options.get("mxstep")),
110
+ rtol=_optional_float(raw_options.get("rtol")),
111
+ atol=_optional_float(raw_options.get("atol")),
112
+ use_precon=_optional_bool(raw_options.get("use_precon")),
113
+ cvode_max_order=_optional_int(raw_options.get("cvode_max_order")),
114
+ mms=_optional_bool(raw_options.get("mms")),
115
+ raw_options={**raw_options, **_resolved_scalars(config, resolver, "solver")} if config.has_section("solver") else {},
116
+ )
117
+
118
+
119
+ def _resolved_parsed_options(config: BoutConfig, section: str) -> Mapping[str, bool | int | float | str | tuple[str, ...]]:
120
+ if not config.has_section(section):
121
+ return {}
122
+ options: dict[str, bool | int | float | str | tuple[str, ...]] = {}
123
+ for key, entry in config.section(section).items():
124
+ options[key] = entry.value.raw if entry.value.kind == "expression" else entry.value.parsed
125
+ return options
126
+
127
+
128
+ def _resolved_scalars(config: BoutConfig, resolver: NumericResolver, section: str) -> Mapping[str, float]:
129
+ if not config.has_section(section):
130
+ return {}
131
+ scalars: dict[str, float] = {}
132
+ for key, entry in config.section(section).items():
133
+ if entry.value.kind in {"string", "list"}:
134
+ continue
135
+ try:
136
+ scalars[key] = resolver.resolve(section, key)
137
+ except (KeyError, TypeError, ValueError, SyntaxError):
138
+ continue
139
+ return scalars
140
+
141
+
142
+ def _resolve_required_scalar(config: BoutConfig, resolver: NumericResolver, section: str, key: str) -> float:
143
+ if not config.has_option(section, key):
144
+ raise KeyError(f"Missing required scalar option {section}:{key}")
145
+ return resolver.resolve(section, key)
146
+
147
+
148
+ def _optional_root_scalar(config: BoutConfig, resolver: NumericResolver, key: str) -> float | None:
149
+ if not config.has_option(ROOT_SECTION, key):
150
+ return None
151
+ return resolver.resolve(ROOT_SECTION, key)
152
+
153
+
154
+ def _optional_int(value: Any) -> int | None:
155
+ if value is None:
156
+ return None
157
+ return int(round(float(value)))
158
+
159
+
160
+ def _optional_float(value: Any) -> float | None:
161
+ if value is None:
162
+ return None
163
+ return float(value)
164
+
165
+
166
+ def _optional_bool(value: Any) -> bool | None:
167
+ if value is None:
168
+ return None
169
+ if isinstance(value, bool):
170
+ return value
171
+ return bool(value)
172
+
173
+
174
+ def _with_default(value: Any, default: Any) -> Any:
175
+ if value is None:
176
+ return default
177
+ return value
178
+
179
+
180
+ def _has_normalization(config: BoutConfig) -> bool:
181
+ if not has_model_section(config):
182
+ return False
183
+ model_section = locate_model_section(config)
184
+ return all(config.has_option(model_section, key) for key in ("Nnorm", "Tnorm", "Bnorm"))
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import re
5
+ from typing import Any, Protocol
6
+
7
+ from ..config.boutinp import BoutConfig
8
+ from ..config.model import locate_model_section
9
+
10
+ _COMPONENT_TYPE_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class ComponentRequest:
15
+ label: str
16
+ section: str
17
+ implementation: str
18
+ source_component: str
19
+ typed: bool
20
+
21
+
22
+ class SupportsSchedulerHooks(Protocol):
23
+ name: str
24
+
25
+ def transform(self, state: Any, time: float) -> Any:
26
+ ...
27
+
28
+ def finalize(self, state: Any, time: float) -> Any:
29
+ ...
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class Scheduler:
34
+ components: tuple[SupportsSchedulerHooks, ...]
35
+
36
+ def execute_cycle(self, state: Any, time: float) -> Any:
37
+ current = state
38
+ for component in self.components:
39
+ current = component.transform(current, time)
40
+ for component in self.components:
41
+ current = component.finalize(current, time)
42
+ return current
43
+
44
+
45
+ def expand_component_requests(config: BoutConfig) -> tuple[ComponentRequest, ...]:
46
+ model_section = locate_model_section(config)
47
+ component_value = config.get(model_section, "components")
48
+ component_names = _as_tuple(component_value.parsed)
49
+ requests: list[ComponentRequest] = []
50
+
51
+ for name in component_names:
52
+ if config.has_section(name) and config.has_option(name, "type") and _is_species_component(config, name):
53
+ implementations = list(_as_tuple(config.parsed(name, "type")))
54
+ if _has_anomalous_diffusion_component(config, name) and "anomalous_diffusion" not in implementations:
55
+ implementations.append("anomalous_diffusion")
56
+ for implementation in implementations:
57
+ requests.append(
58
+ ComponentRequest(
59
+ label=f"{name}:{implementation}",
60
+ section=name,
61
+ implementation=implementation,
62
+ source_component=name,
63
+ typed=True,
64
+ )
65
+ )
66
+ continue
67
+
68
+ requests.append(
69
+ ComponentRequest(
70
+ label=name,
71
+ section=name,
72
+ implementation=name,
73
+ source_component=name,
74
+ typed=False,
75
+ )
76
+ )
77
+
78
+ return tuple(requests)
79
+
80
+
81
+ def _as_tuple(value: bool | int | float | str | tuple[str, ...]) -> tuple[str, ...]:
82
+ if isinstance(value, tuple):
83
+ return value
84
+ return (str(value),)
85
+
86
+
87
+ def _is_species_component(config: BoutConfig, section: str) -> bool:
88
+ type_value = config.parsed(section, "type")
89
+ return all(_COMPONENT_TYPE_PATTERN.fullmatch(item) for item in _as_tuple(type_value))
90
+
91
+
92
+ def _has_anomalous_diffusion_component(config: BoutConfig, section: str) -> bool:
93
+ for option_name in ("anomalous_D", "anomalous_chi", "anomalous_nu"):
94
+ if not config.has_option(section, option_name):
95
+ continue
96
+ raw_value = str(config.get(section, option_name).raw).strip()
97
+ if raw_value not in {"0", "0.0", "0.", "false", "False"}:
98
+ return True
99
+ return False
drbx/runtime/state.py ADDED
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Mapping
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class SimulationState:
9
+ time: float = 0.0
10
+ fields: Mapping[str, Any] = field(default_factory=dict)
11
+ diagnostics: Mapping[str, Any] = field(default_factory=dict)
12
+ metadata: Mapping[str, Any] = field(default_factory=dict)
13
+
14
+ def with_field(self, name: str, value: Any) -> "SimulationState":
15
+ updated_fields = dict(self.fields)
16
+ updated_fields[name] = value
17
+ return SimulationState(
18
+ time=self.time,
19
+ fields=updated_fields,
20
+ diagnostics=self.diagnostics,
21
+ metadata=self.metadata,
22
+ )
23
+
24
+ def with_diagnostic(self, name: str, value: Any) -> "SimulationState":
25
+ updated_diagnostics = dict(self.diagnostics)
26
+ updated_diagnostics[name] = value
27
+ return SimulationState(
28
+ time=self.time,
29
+ fields=self.fields,
30
+ diagnostics=updated_diagnostics,
31
+ metadata=self.metadata,
32
+ )
33
+
34
+ def advance_time(self, dt: float) -> "SimulationState":
35
+ return SimulationState(
36
+ time=self.time + dt,
37
+ fields=self.fields,
38
+ diagnostics=self.diagnostics,
39
+ metadata=self.metadata,
40
+ )