nmag-python-3 0.0.2__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.
- anisotropy/__init__.py +28 -0
- anisotropy/anisotropy.py +13 -0
- anisotropy/evaluation.py +70 -0
- anisotropy/model.py +200 -0
- anisotropy/predefined.py +202 -0
- anisotropy/py.typed +1 -0
- anisotropy/values.py +84 -0
- mag_material/__init__.py +3 -0
- mag_material/mag_material.py +231 -0
- mag_material/parameters.py +150 -0
- mag_material/py.typed +1 -0
- nmag/__init__.py +36 -0
- nmag/backends.py +493 -0
- nmag/checkpoint.py +327 -0
- nmag/config.py +174 -0
- nmag/demag/__init__.py +39 -0
- nmag/demag/bem_operator.py +149 -0
- nmag/demag/geometry.py +134 -0
- nmag/demag/lindholm.py +133 -0
- nmag/demag/lindholm_fast.py +463 -0
- nmag/demag/linear.py +489 -0
- nmag/dynamics/__init__.py +255 -0
- nmag/output.py +27 -0
- nmag/parallel.py +45 -0
- nmag/py.typed +1 -0
- nmag/resources.py +38 -0
- nmag/simulation/__init__.py +519 -0
- nmag/simulation/anisotropy/__init__.py +5 -0
- nmag/simulation/anisotropy/fields.py +56 -0
- nmag/simulation/anisotropy/materials.py +138 -0
- nmag/simulation/demag/__init__.py +1 -0
- nmag/simulation/demag/bem/__init__.py +11 -0
- nmag/simulation/demag/bem/diagnostics.py +73 -0
- nmag/simulation/demag/bem/dirichlet.py +85 -0
- nmag/simulation/demag/bem/hierarchical.py +74 -0
- nmag/simulation/demag/bem/operator.py +292 -0
- nmag/simulation/demag/fem/__init__.py +11 -0
- nmag/simulation/demag/fem/assembly.py +114 -0
- nmag/simulation/demag/fem/charges.py +38 -0
- nmag/simulation/demag/fem/geometry.py +261 -0
- nmag/simulation/demag/fields/__init__.py +11 -0
- nmag/simulation/demag/fields/auxiliary.py +186 -0
- nmag/simulation/demag/fields/probe.py +77 -0
- nmag/simulation/demag/fields/recovery.py +207 -0
- nmag/simulation/demag/solver.py +17 -0
- nmag/simulation/dynamics/__init__.py +92 -0
- nmag/simulation/dynamics/advance.py +199 -0
- nmag/simulation/dynamics/integrator.py +263 -0
- nmag/simulation/exchange/__init__.py +13 -0
- nmag/simulation/exchange/coefficients.py +185 -0
- nmag/simulation/exchange/fields.py +106 -0
- nmag/simulation/exchange/llg_rhs.py +218 -0
- nmag/simulation/fields/__init__.py +19 -0
- nmag/simulation/fields/arrays.py +111 -0
- nmag/simulation/fields/availability.py +188 -0
- nmag/simulation/fields/averages.py +293 -0
- nmag/simulation/fields/derived.py +164 -0
- nmag/simulation/fields/maxangle.py +163 -0
- nmag/simulation/fields/probes.py +106 -0
- nmag/simulation/implicit_dynamics.py +221 -0
- nmag/simulation/mesh/__init__.py +13 -0
- nmag/simulation/mesh/geometry.py +153 -0
- nmag/simulation/mesh/materials.py +299 -0
- nmag/simulation/mesh/probe.py +234 -0
- nmag/simulation/restart.py +103 -0
- nmag/simulation/support.py +224 -0
- nmag_python_3-0.0.2.dist-info/METADATA +157 -0
- nmag_python_3-0.0.2.dist-info/RECORD +147 -0
- nmag_python_3-0.0.2.dist-info/WHEEL +5 -0
- nmag_python_3-0.0.2.dist-info/licenses/LICENSE +339 -0
- nmag_python_3-0.0.2.dist-info/top_level.txt +8 -0
- nmesh/__init__.py +130 -0
- nmesh/backend.py +286 -0
- nmesh/geometry/__init__.py +52 -0
- nmesh/geometry/boolean_operations.py +157 -0
- nmesh/geometry/primitives.py +453 -0
- nmesh/geometry/transform.py +126 -0
- nmesh/io/__init__.py +50 -0
- nmesh/io/ascii.py +132 -0
- nmesh/io/legacy_nmesh_hdf5.py +318 -0
- nmesh/io/meshio_support.py +170 -0
- nmesh/mesh_generation.py +182 -0
- nmesh/mesh_io.py +227 -0
- nmesh/mesh_model.py +147 -0
- nmesh/mesh_utilities.py +79 -0
- nmesh/mesher/__init__.py +21 -0
- nmesh/mesher/driver.py +146 -0
- nmesh/mesher/meshing_defaults.py +252 -0
- nmesh/mesher/meshing_parameters.py +185 -0
- nmesh/mesher/parity.py +21 -0
- nmesh/mesher/parity_canonical.py +142 -0
- nmesh/mesher/parity_comparison.py +191 -0
- nmesh/mesher/parity_metrics.py +114 -0
- nmesh/mesher/periodic.py +97 -0
- nmesh/mesher/relaxation/__init__.py +14 -0
- nmesh/mesher/relaxation/_constants.py +20 -0
- nmesh/mesher/relaxation/_types.py +15 -0
- nmesh/mesher/relaxation/density.py +170 -0
- nmesh/mesher/relaxation/engine/__init__.py +18 -0
- nmesh/mesher/relaxation/engine/state.py +155 -0
- nmesh/mesher/relaxation/engine/steps.py +248 -0
- nmesh/mesher/relaxation/engine/topology.py +230 -0
- nmesh/mesher/relaxation/forces/__init__.py +96 -0
- nmesh/mesher/relaxation/forces/jit.py +102 -0
- nmesh/mesher/relaxation/forces/neighbors.py +186 -0
- nmesh/mesher/relaxation/forces/simplex.py +302 -0
- nmesh/mesher/relaxation/forces/summary.py +207 -0
- nmesh/mesher/relaxation/forces/types.py +92 -0
- nmesh/mesher/relaxation/geometry/__init__.py +6 -0
- nmesh/mesher/relaxation/geometry/builder.py +154 -0
- nmesh/mesher/relaxation/geometry/model.py +194 -0
- nmesh/mesher/relaxation/seeding/__init__.py +74 -0
- nmesh/mesher/relaxation/seeding/periodic.py +88 -0
- nmesh/mesher/relaxation/seeding/points.py +88 -0
- nmesh/mesher/relaxation/seeding/sampling.py +142 -0
- nmesh/mesher/relaxation/topology/__init__.py +297 -0
- nmesh/mesher/relaxation/topology/finalize.py +78 -0
- nmesh/mesher/relaxation/topology/recovery.py +310 -0
- nmesh/mesher/sectioned_config.py +70 -0
- nmesh/nmesh.py +99 -0
- nmesh/py.typed +1 -0
- nmesh/utils/__init__.py +33 -0
- nmesh/utils/array_list_utils.py +128 -0
- nmesh/utils/constants.py +22 -0
- nmesh/utils/timing_memory_utils.py +51 -0
- nmesh/utils/types.py +13 -0
- si/constants.py +49 -0
- si/physical.py +722 -0
- si/py.typed +1 -0
- simulation/__init__.py +1 -0
- simulation/clock.py +237 -0
- simulation/data_writer.py +273 -0
- simulation/data_writer_collection.py +267 -0
- simulation/hysteresis.py +74 -0
- simulation/hysteresis_runner.py +286 -0
- simulation/hysteresis_schedule.py +180 -0
- simulation/inference/__init__.py +3 -0
- simulation/inference/inference.py +95 -0
- simulation/py.typed +1 -0
- simulation/quantity.py +88 -0
- simulation/simulation_core.py +458 -0
- throttler/__init__.py +3 -0
- throttler/py.typed +1 -0
- throttler/throttler.py +55 -0
- when/__init__.py +3 -0
- when/py.typed +1 -0
- when/when.py +416 -0
nmag/backends.py
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
"""Backend selection for Nmag's optional acceleration and memory policies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import os
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
|
|
9
|
+
from .config import DEMAG_BEM_STORAGE_ENV, NmagConfig, RustKernel
|
|
10
|
+
from .resources import available_memory_bytes
|
|
11
|
+
|
|
12
|
+
RUST_ACCELERATOR_API_VERSION = 2
|
|
13
|
+
MEMORY_MODE_ENV = "NMAG_MEMORY_MODE"
|
|
14
|
+
MEMORY_MODES = frozenset({"auto", "low"})
|
|
15
|
+
AUTO_DENSE_MEMORY_FRACTION = 0.2
|
|
16
|
+
|
|
17
|
+
DEMAG_BEM_STORAGE_BACKEND_ENV = DEMAG_BEM_STORAGE_ENV
|
|
18
|
+
DEMAG_BEM_STORAGE_BACKENDS = frozenset({"auto", "dense", "hierarchical", "matrix-free"})
|
|
19
|
+
DEMAG_BEM_AUTO_MATRIX_FREE_MIN_BOUNDARY_NODES_ENV = (
|
|
20
|
+
"NMAG_DEMAG_BEM_AUTO_MATRIX_FREE_MIN_BOUNDARY_NODES"
|
|
21
|
+
)
|
|
22
|
+
DEFAULT_DEMAG_BEM_AUTO_MATRIX_FREE_MIN_BOUNDARY_NODES = 2048
|
|
23
|
+
DEMAG_FEM_MATRIX_BACKEND_ENV = "NMAG_DEMAG_FEM_MATRIX_BACKEND"
|
|
24
|
+
DEMAG_FEM_MATRIX_BACKENDS = frozenset({"auto", "dense", "sparse"})
|
|
25
|
+
DEMAG_FEM_AUTO_SPARSE_MIN_POINTS_ENV = "NMAG_DEMAG_FEM_AUTO_SPARSE_MIN_POINTS"
|
|
26
|
+
DEFAULT_DEMAG_FEM_AUTO_SPARSE_MIN_POINTS = 2048
|
|
27
|
+
DEMAG_LINEAR_SOLVER_BACKEND_ENV = "NMAG_DEMAG_LINEAR_SOLVER_BACKEND"
|
|
28
|
+
DEMAG_LINEAR_SOLVER_BACKENDS = frozenset({"auto", "numpy", "scipy"})
|
|
29
|
+
DEMAG_LINEAR_SOLVER_AUTO_SCIPY_MIN_SIZE_ENV = "NMAG_DEMAG_LINEAR_SOLVER_AUTO_SCIPY_MIN_SIZE"
|
|
30
|
+
DEFAULT_DEMAG_LINEAR_SOLVER_AUTO_SCIPY_MIN_SIZE = 128
|
|
31
|
+
DEMAG_SOLVE_CONDITION_DIAGNOSTICS_ENV = "NMAG_DEMAG_SOLVE_CONDITION_DIAGNOSTICS"
|
|
32
|
+
DEMAG_INTERNAL_TRACE_ENV = "NMAG_DEMAG_INTERNAL_TRACE"
|
|
33
|
+
DEMAG_DENSE_MAX_POINTS_ENV = "NMAG_DEMAG_DENSE_MAX_POINTS"
|
|
34
|
+
LEAST_SQUARES_RELATIVE_RESIDUAL_TOLERANCE = 1.0e-10
|
|
35
|
+
|
|
36
|
+
DEMAG_FEM_GEOMETRY_RUST_MIN_CELLS = 2048
|
|
37
|
+
DEMAG_FEM_ASSEMBLY_RUST_MIN_CELLS = 2048
|
|
38
|
+
DEMAG_NODAL_RECOVERY_RUST_MIN_CELLS = 2048
|
|
39
|
+
DEMAG_CELL_AVERAGE_RUST_MIN_CELLS = 2048
|
|
40
|
+
LLG_RUST_MIN_POINTS = 2048
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _default_config() -> NmagConfig:
|
|
44
|
+
"""Return the environment-derived default for standalone helper calls."""
|
|
45
|
+
|
|
46
|
+
return NmagConfig.from_environment()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _policy(config: NmagConfig | None) -> NmagConfig:
|
|
50
|
+
return _default_config() if config is None else config
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _rust_accelerator_available() -> bool:
|
|
54
|
+
try:
|
|
55
|
+
accelerator = importlib.import_module("nmag_accel")
|
|
56
|
+
except ImportError:
|
|
57
|
+
return False
|
|
58
|
+
return getattr(accelerator, "API_VERSION", None) == RUST_ACCELERATOR_API_VERSION
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _load_rust_accelerator(required_by: str = "NmagConfig.accelerator") -> ModuleType:
|
|
62
|
+
try:
|
|
63
|
+
accelerator = importlib.import_module("nmag_accel")
|
|
64
|
+
except ImportError as exc:
|
|
65
|
+
raise RuntimeError(
|
|
66
|
+
f"{required_by}=rust requires the optional nmag_accel extension. Build it with "
|
|
67
|
+
"`maturin develop --release --manifest-path rust/nmag_accel/Cargo.toml`."
|
|
68
|
+
) from exc
|
|
69
|
+
|
|
70
|
+
actual_version = getattr(accelerator, "API_VERSION", None)
|
|
71
|
+
if actual_version != RUST_ACCELERATOR_API_VERSION:
|
|
72
|
+
raise RuntimeError(
|
|
73
|
+
f"{required_by}=rust found an incompatible nmag_accel extension "
|
|
74
|
+
f"(API_VERSION={actual_version!r}, expected {RUST_ACCELERATOR_API_VERSION}). "
|
|
75
|
+
"Rebuild it with `maturin develop --release --manifest-path "
|
|
76
|
+
"rust/nmag_accel/Cargo.toml`."
|
|
77
|
+
)
|
|
78
|
+
return accelerator
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _select_rust_backend(
|
|
82
|
+
*,
|
|
83
|
+
config: NmagConfig | None,
|
|
84
|
+
kernel: RustKernel,
|
|
85
|
+
fallback: str,
|
|
86
|
+
item_count: int | None = None,
|
|
87
|
+
rust_min_items: int | None = None,
|
|
88
|
+
) -> str:
|
|
89
|
+
"""Resolve one optional Rust kernel without process-global selectors."""
|
|
90
|
+
|
|
91
|
+
mode = _policy(config).accelerator_mode_for(kernel)
|
|
92
|
+
if mode == "off":
|
|
93
|
+
return fallback
|
|
94
|
+
if mode == "rust":
|
|
95
|
+
_load_rust_accelerator(f"NmagConfig.accelerator[{kernel.value!r}]")
|
|
96
|
+
return "rust"
|
|
97
|
+
if not _rust_accelerator_available():
|
|
98
|
+
return fallback
|
|
99
|
+
if (
|
|
100
|
+
item_count is not None
|
|
101
|
+
and rust_min_items is not None
|
|
102
|
+
and item_count < rust_min_items
|
|
103
|
+
):
|
|
104
|
+
return fallback
|
|
105
|
+
return "rust"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _rust_selection_metadata(
|
|
109
|
+
*,
|
|
110
|
+
config: NmagConfig | None,
|
|
111
|
+
kernel: RustKernel,
|
|
112
|
+
selected_backend: str,
|
|
113
|
+
fallback: str,
|
|
114
|
+
item_count: int | None = None,
|
|
115
|
+
rust_min_items: int | None = None,
|
|
116
|
+
) -> dict[str, object]:
|
|
117
|
+
policy = _policy(config)
|
|
118
|
+
requested = policy.accelerator_mode_for(kernel)
|
|
119
|
+
available = _rust_accelerator_available()
|
|
120
|
+
if requested == "off":
|
|
121
|
+
reason = "accelerator_off"
|
|
122
|
+
elif requested == "rust":
|
|
123
|
+
reason = "explicit_rust_request"
|
|
124
|
+
elif not available:
|
|
125
|
+
reason = "rust_accelerator_unavailable"
|
|
126
|
+
elif rust_min_items is not None and item_count is not None and item_count < rust_min_items:
|
|
127
|
+
reason = "item_count_below_rust_min"
|
|
128
|
+
elif selected_backend == fallback:
|
|
129
|
+
reason = "auto_fallback"
|
|
130
|
+
else:
|
|
131
|
+
reason = "rust_accelerator_available"
|
|
132
|
+
metadata: dict[str, object] = {
|
|
133
|
+
"requested": requested,
|
|
134
|
+
"selected": selected_backend,
|
|
135
|
+
"rust_available": available,
|
|
136
|
+
"selection_reason": reason,
|
|
137
|
+
}
|
|
138
|
+
if item_count is not None:
|
|
139
|
+
metadata["item_count"] = int(item_count)
|
|
140
|
+
if rust_min_items is not None:
|
|
141
|
+
metadata["rust_min_items"] = int(rust_min_items)
|
|
142
|
+
return metadata
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _selected_integrator_backend(config: NmagConfig | None = None) -> str:
|
|
146
|
+
policy = _policy(config)
|
|
147
|
+
backend = policy.integrator_backend
|
|
148
|
+
if backend == "diffsol" and _selected_memory_mode() == "low":
|
|
149
|
+
raise ValueError(
|
|
150
|
+
"NmagConfig(integrator_backend='diffsol') constructs a dense affine operator "
|
|
151
|
+
f"and is incompatible with {MEMORY_MODE_ENV}=low; use scipy instead."
|
|
152
|
+
)
|
|
153
|
+
if backend == "diffsol" and policy.accelerator == "off":
|
|
154
|
+
raise ValueError(
|
|
155
|
+
"NmagConfig(integrator_backend='diffsol') requires the Rust accelerator; "
|
|
156
|
+
"accelerator='off' disables it."
|
|
157
|
+
)
|
|
158
|
+
return backend
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _selected_memory_mode() -> str:
|
|
162
|
+
mode = os.environ.get(MEMORY_MODE_ENV, "auto").strip().lower()
|
|
163
|
+
if mode not in MEMORY_MODES:
|
|
164
|
+
choices = ", ".join(sorted(MEMORY_MODES))
|
|
165
|
+
raise ValueError(f"Unsupported {MEMORY_MODE_ENV}={mode!r}; choose one of {choices}.")
|
|
166
|
+
return mode
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _auto_prefers_low_memory_storage(
|
|
170
|
+
*, estimated_dense_bytes: int, item_count: int | None, fallback_min_items: int
|
|
171
|
+
) -> bool:
|
|
172
|
+
available = available_memory_bytes()
|
|
173
|
+
if available is not None:
|
|
174
|
+
return estimated_dense_bytes > available * AUTO_DENSE_MEMORY_FRACTION
|
|
175
|
+
return item_count is not None and item_count >= fallback_min_items
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _selected_lindholm_bem_backend(config: NmagConfig | None = None) -> str:
|
|
179
|
+
return _select_rust_backend(
|
|
180
|
+
config=config, kernel=RustKernel.LINDHOLM_BEM, fallback="numba"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _lindholm_bem_backend_selection_metadata(config: NmagConfig | None = None) -> dict[str, object]:
|
|
185
|
+
return _rust_selection_metadata(
|
|
186
|
+
config=config,
|
|
187
|
+
kernel=RustKernel.LINDHOLM_BEM,
|
|
188
|
+
selected_backend=_selected_lindholm_bem_backend(config),
|
|
189
|
+
fallback="numba",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _selected_probe_geometry_backend(config: NmagConfig | None = None) -> str:
|
|
194
|
+
return _select_rust_backend(
|
|
195
|
+
config=config, kernel=RustKernel.PROBE_GEOMETRY, fallback="python"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _probe_geometry_backend_selection_metadata(config: NmagConfig | None = None) -> dict[str, object]:
|
|
200
|
+
return _rust_selection_metadata(
|
|
201
|
+
config=config,
|
|
202
|
+
kernel=RustKernel.PROBE_GEOMETRY,
|
|
203
|
+
selected_backend=_selected_probe_geometry_backend(config),
|
|
204
|
+
fallback="python",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _selected_demag_fem_geometry_backend(
|
|
209
|
+
cell_count: int | None = None, config: NmagConfig | None = None
|
|
210
|
+
) -> str:
|
|
211
|
+
return _select_rust_backend(
|
|
212
|
+
config=config,
|
|
213
|
+
kernel=RustKernel.FEM_GEOMETRY,
|
|
214
|
+
fallback="python",
|
|
215
|
+
item_count=cell_count,
|
|
216
|
+
rust_min_items=DEMAG_FEM_GEOMETRY_RUST_MIN_CELLS,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _demag_fem_geometry_backend_selection_metadata(
|
|
221
|
+
cell_count: int | None = None, config: NmagConfig | None = None
|
|
222
|
+
) -> dict[str, object]:
|
|
223
|
+
return _rust_selection_metadata(
|
|
224
|
+
config=config,
|
|
225
|
+
kernel=RustKernel.FEM_GEOMETRY,
|
|
226
|
+
selected_backend=_selected_demag_fem_geometry_backend(cell_count, config),
|
|
227
|
+
fallback="python",
|
|
228
|
+
item_count=cell_count,
|
|
229
|
+
rust_min_items=DEMAG_FEM_GEOMETRY_RUST_MIN_CELLS,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _selected_demag_boundary_face_backend(config: NmagConfig | None = None) -> str:
|
|
234
|
+
return _select_rust_backend(
|
|
235
|
+
config=config, kernel=RustKernel.BOUNDARY_FACES, fallback="python"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _demag_boundary_face_backend_selection_metadata(
|
|
240
|
+
config: NmagConfig | None = None,
|
|
241
|
+
) -> dict[str, object]:
|
|
242
|
+
return _rust_selection_metadata(
|
|
243
|
+
config=config,
|
|
244
|
+
kernel=RustKernel.BOUNDARY_FACES,
|
|
245
|
+
selected_backend=_selected_demag_boundary_face_backend(config),
|
|
246
|
+
fallback="python",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _selected_demag_fem_assembly_backend(
|
|
251
|
+
cell_count: int | None = None, config: NmagConfig | None = None
|
|
252
|
+
) -> str:
|
|
253
|
+
return _select_rust_backend(
|
|
254
|
+
config=config,
|
|
255
|
+
kernel=RustKernel.FEM_ASSEMBLY,
|
|
256
|
+
fallback="python",
|
|
257
|
+
item_count=cell_count,
|
|
258
|
+
rust_min_items=DEMAG_FEM_ASSEMBLY_RUST_MIN_CELLS,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _demag_fem_assembly_backend_selection_metadata(
|
|
263
|
+
cell_count: int | None = None, config: NmagConfig | None = None
|
|
264
|
+
) -> dict[str, object]:
|
|
265
|
+
return _rust_selection_metadata(
|
|
266
|
+
config=config,
|
|
267
|
+
kernel=RustKernel.FEM_ASSEMBLY,
|
|
268
|
+
selected_backend=_selected_demag_fem_assembly_backend(cell_count, config),
|
|
269
|
+
fallback="python",
|
|
270
|
+
item_count=cell_count,
|
|
271
|
+
rust_min_items=DEMAG_FEM_ASSEMBLY_RUST_MIN_CELLS,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _selected_demag_nodal_recovery_backend(
|
|
276
|
+
cell_count: int | None = None, config: NmagConfig | None = None
|
|
277
|
+
) -> str:
|
|
278
|
+
return _select_rust_backend(
|
|
279
|
+
config=config,
|
|
280
|
+
kernel=RustKernel.NODAL_RECOVERY,
|
|
281
|
+
fallback="python",
|
|
282
|
+
item_count=cell_count,
|
|
283
|
+
rust_min_items=DEMAG_NODAL_RECOVERY_RUST_MIN_CELLS,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _demag_nodal_recovery_backend_selection_metadata(
|
|
288
|
+
cell_count: int | None = None, config: NmagConfig | None = None
|
|
289
|
+
) -> dict[str, object]:
|
|
290
|
+
return _rust_selection_metadata(
|
|
291
|
+
config=config,
|
|
292
|
+
kernel=RustKernel.NODAL_RECOVERY,
|
|
293
|
+
selected_backend=_selected_demag_nodal_recovery_backend(cell_count, config),
|
|
294
|
+
fallback="python",
|
|
295
|
+
item_count=cell_count,
|
|
296
|
+
rust_min_items=DEMAG_NODAL_RECOVERY_RUST_MIN_CELLS,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _selected_demag_cell_average_backend(
|
|
301
|
+
cell_count: int | None = None, config: NmagConfig | None = None
|
|
302
|
+
) -> str:
|
|
303
|
+
return _select_rust_backend(
|
|
304
|
+
config=config,
|
|
305
|
+
kernel=RustKernel.CELL_AVERAGE,
|
|
306
|
+
fallback="python",
|
|
307
|
+
item_count=cell_count,
|
|
308
|
+
rust_min_items=DEMAG_CELL_AVERAGE_RUST_MIN_CELLS,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _demag_cell_average_backend_selection_metadata(
|
|
313
|
+
cell_count: int | None = None, config: NmagConfig | None = None
|
|
314
|
+
) -> dict[str, object]:
|
|
315
|
+
return _rust_selection_metadata(
|
|
316
|
+
config=config,
|
|
317
|
+
kernel=RustKernel.CELL_AVERAGE,
|
|
318
|
+
selected_backend=_selected_demag_cell_average_backend(cell_count, config),
|
|
319
|
+
fallback="python",
|
|
320
|
+
item_count=cell_count,
|
|
321
|
+
rust_min_items=DEMAG_CELL_AVERAGE_RUST_MIN_CELLS,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _selected_llg_backend(
|
|
326
|
+
point_count: int | None = None, config: NmagConfig | None = None
|
|
327
|
+
) -> str:
|
|
328
|
+
return _select_rust_backend(
|
|
329
|
+
config=config,
|
|
330
|
+
kernel=RustKernel.LLG,
|
|
331
|
+
fallback="python",
|
|
332
|
+
item_count=point_count,
|
|
333
|
+
rust_min_items=LLG_RUST_MIN_POINTS,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _llg_backend_selection_metadata(
|
|
338
|
+
point_count: int | None = None, config: NmagConfig | None = None
|
|
339
|
+
) -> dict[str, object]:
|
|
340
|
+
return _rust_selection_metadata(
|
|
341
|
+
config=config,
|
|
342
|
+
kernel=RustKernel.LLG,
|
|
343
|
+
selected_backend=_selected_llg_backend(point_count, config),
|
|
344
|
+
fallback="python",
|
|
345
|
+
item_count=point_count,
|
|
346
|
+
rust_min_items=LLG_RUST_MIN_POINTS,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _selected_maxangle_backend(config: NmagConfig | None = None) -> str:
|
|
351
|
+
return _select_rust_backend(
|
|
352
|
+
config=config, kernel=RustKernel.MAXANGLE, fallback="python"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _maxangle_backend_selection_metadata(config: NmagConfig | None = None) -> dict[str, object]:
|
|
357
|
+
return _rust_selection_metadata(
|
|
358
|
+
config=config,
|
|
359
|
+
kernel=RustKernel.MAXANGLE,
|
|
360
|
+
selected_backend=_selected_maxangle_backend(config),
|
|
361
|
+
fallback="python",
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _demag_bem_auto_matrix_free_min_boundary_nodes() -> int:
|
|
366
|
+
return _positive_environment_int(
|
|
367
|
+
DEMAG_BEM_AUTO_MATRIX_FREE_MIN_BOUNDARY_NODES_ENV,
|
|
368
|
+
DEFAULT_DEMAG_BEM_AUTO_MATRIX_FREE_MIN_BOUNDARY_NODES,
|
|
369
|
+
"boundary-node count",
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _demag_fem_auto_sparse_min_points() -> int:
|
|
374
|
+
return _positive_environment_int(
|
|
375
|
+
DEMAG_FEM_AUTO_SPARSE_MIN_POINTS_ENV,
|
|
376
|
+
DEFAULT_DEMAG_FEM_AUTO_SPARSE_MIN_POINTS,
|
|
377
|
+
"point count",
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _demag_linear_solver_auto_scipy_min_size() -> int:
|
|
382
|
+
return _positive_environment_int(
|
|
383
|
+
DEMAG_LINEAR_SOLVER_AUTO_SCIPY_MIN_SIZE_ENV,
|
|
384
|
+
DEFAULT_DEMAG_LINEAR_SOLVER_AUTO_SCIPY_MIN_SIZE,
|
|
385
|
+
"dense dimension",
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _positive_environment_int(name: str, default: int, description: str) -> int:
|
|
390
|
+
raw_value = os.environ.get(name, str(default))
|
|
391
|
+
try:
|
|
392
|
+
minimum = int(raw_value)
|
|
393
|
+
except ValueError as exc:
|
|
394
|
+
raise ValueError(
|
|
395
|
+
f"Unsupported {name}={raw_value!r}; expected a positive integer {description}."
|
|
396
|
+
) from exc
|
|
397
|
+
if minimum <= 0:
|
|
398
|
+
raise ValueError(
|
|
399
|
+
f"Unsupported {name}={raw_value!r}; expected a positive integer {description}."
|
|
400
|
+
)
|
|
401
|
+
return minimum
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _selected_demag_bem_storage_backend(
|
|
405
|
+
boundary_node_count: int | None = None,
|
|
406
|
+
config: NmagConfig | None = None,
|
|
407
|
+
) -> str:
|
|
408
|
+
backend = (
|
|
409
|
+
os.environ.get(DEMAG_BEM_STORAGE_BACKEND_ENV, "auto").strip().lower()
|
|
410
|
+
if config is None
|
|
411
|
+
else config.demag_bem_storage
|
|
412
|
+
)
|
|
413
|
+
if backend not in DEMAG_BEM_STORAGE_BACKENDS:
|
|
414
|
+
choices = ", ".join(sorted(DEMAG_BEM_STORAGE_BACKENDS))
|
|
415
|
+
raise ValueError(f"Unsupported {DEMAG_BEM_STORAGE_BACKEND_ENV}={backend!r}; choose one of {choices}.")
|
|
416
|
+
if backend != "auto":
|
|
417
|
+
return backend
|
|
418
|
+
if _selected_memory_mode() == "low":
|
|
419
|
+
return "matrix-free"
|
|
420
|
+
count = boundary_node_count or 0
|
|
421
|
+
if _auto_prefers_low_memory_storage(
|
|
422
|
+
estimated_dense_bytes=count * count * 8,
|
|
423
|
+
item_count=boundary_node_count,
|
|
424
|
+
fallback_min_items=_demag_bem_auto_matrix_free_min_boundary_nodes(),
|
|
425
|
+
):
|
|
426
|
+
mode = _policy(config).accelerator_mode_for(RustKernel.LINDHOLM_BEM)
|
|
427
|
+
if mode == "off" or (mode == "auto" and not _rust_accelerator_available()):
|
|
428
|
+
return "matrix-free"
|
|
429
|
+
if mode == "rust":
|
|
430
|
+
_load_rust_accelerator("NmagConfig.accelerator['lindholm_bem']")
|
|
431
|
+
return "hierarchical"
|
|
432
|
+
return "dense"
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _selected_demag_fem_matrix_backend(point_count: int | None = None) -> str:
|
|
436
|
+
backend = os.environ.get(DEMAG_FEM_MATRIX_BACKEND_ENV, "auto").strip().lower()
|
|
437
|
+
if backend not in DEMAG_FEM_MATRIX_BACKENDS:
|
|
438
|
+
choices = ", ".join(sorted(DEMAG_FEM_MATRIX_BACKENDS))
|
|
439
|
+
raise ValueError(f"Unsupported {DEMAG_FEM_MATRIX_BACKEND_ENV}={backend!r}; choose one of {choices}.")
|
|
440
|
+
if backend != "auto":
|
|
441
|
+
return backend
|
|
442
|
+
if _selected_memory_mode() == "low":
|
|
443
|
+
return "sparse"
|
|
444
|
+
count = point_count or 0
|
|
445
|
+
if _auto_prefers_low_memory_storage(
|
|
446
|
+
estimated_dense_bytes=count * count * 8 * 4,
|
|
447
|
+
item_count=point_count,
|
|
448
|
+
fallback_min_items=_demag_fem_auto_sparse_min_points(),
|
|
449
|
+
):
|
|
450
|
+
return "sparse"
|
|
451
|
+
return "dense"
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _selected_demag_linear_solver_backend(dense_dimension: int | None = None) -> str:
|
|
455
|
+
backend = os.environ.get(DEMAG_LINEAR_SOLVER_BACKEND_ENV, "auto").strip().lower()
|
|
456
|
+
if backend not in DEMAG_LINEAR_SOLVER_BACKENDS:
|
|
457
|
+
choices = ", ".join(sorted(DEMAG_LINEAR_SOLVER_BACKENDS))
|
|
458
|
+
raise ValueError(f"Unsupported {DEMAG_LINEAR_SOLVER_BACKEND_ENV}={backend!r}; choose one of {choices}.")
|
|
459
|
+
if backend != "auto":
|
|
460
|
+
return backend
|
|
461
|
+
if dense_dimension is not None and dense_dimension >= _demag_linear_solver_auto_scipy_min_size():
|
|
462
|
+
return "scipy"
|
|
463
|
+
return "numpy"
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _demag_linear_solver_backend_selection_metadata(
|
|
467
|
+
dense_dimension: int | None = None,
|
|
468
|
+
) -> dict[str, object]:
|
|
469
|
+
selected = _selected_demag_linear_solver_backend(dense_dimension)
|
|
470
|
+
requested = os.environ.get(DEMAG_LINEAR_SOLVER_BACKEND_ENV, "auto").strip().lower()
|
|
471
|
+
metadata: dict[str, object] = {"requested": requested, "selected": selected}
|
|
472
|
+
if dense_dimension is not None:
|
|
473
|
+
metadata["dense_dimension"] = int(dense_dimension)
|
|
474
|
+
metadata["auto_scipy_min_size"] = _demag_linear_solver_auto_scipy_min_size()
|
|
475
|
+
return metadata
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _demag_solve_condition_diagnostics_enabled() -> bool:
|
|
479
|
+
return _boolean_environment(DEMAG_SOLVE_CONDITION_DIAGNOSTICS_ENV)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _demag_internal_trace_enabled() -> bool:
|
|
483
|
+
return _boolean_environment(DEMAG_INTERNAL_TRACE_ENV)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _boolean_environment(name: str) -> bool:
|
|
487
|
+
raw_value = os.environ.get(name, "0")
|
|
488
|
+
value = raw_value.strip().lower()
|
|
489
|
+
if value in {"1", "true", "yes", "on"}:
|
|
490
|
+
return True
|
|
491
|
+
if value in {"0", "false", "no", "off", ""}:
|
|
492
|
+
return False
|
|
493
|
+
raise ValueError(f"Unsupported {name}={raw_value!r}; choose a boolean value.")
|