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
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Module which defines the SimulationCore class, an abstract class from which
|
|
3
|
+
the real simulation objects are derived (by inheritance).
|
|
4
|
+
Such a design has the goal of separating the parts of the Simulation object
|
|
5
|
+
which depend on the specific discretisation (FD, FE) from the parts which
|
|
6
|
+
do not depend on it (such as the hysteresis logic).
|
|
7
|
+
Here is the structure we have in mind:
|
|
8
|
+
|
|
9
|
+
/----> FDSimulation ----\
|
|
10
|
+
SimulationCore ---| |---> Simulation
|
|
11
|
+
\----> FESimulation ----/
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from collections.abc import Callable, Sequence
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from nmag.config import NmagConfig
|
|
23
|
+
from nmag.output import prepare_output_files
|
|
24
|
+
from si.physical import SI
|
|
25
|
+
|
|
26
|
+
from . import hysteresis as hysteresis_m
|
|
27
|
+
from .clock import SimulationClock
|
|
28
|
+
from .data_writer import DataWriter
|
|
29
|
+
from .quantity import known_field_quantities, known_quantities, known_quantities_by_name
|
|
30
|
+
|
|
31
|
+
Action = Callable[[Any], Any]
|
|
32
|
+
|
|
33
|
+
log = logging.getLogger("nmag")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SimulationCore(ABC):
|
|
37
|
+
"""
|
|
38
|
+
Abstract base class for simulations.
|
|
39
|
+
|
|
40
|
+
Handles simulation state, clock, data saving, and hysteresis logic
|
|
41
|
+
that is independent of the specific discretisation (FD/FE).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
name: str | None = None,
|
|
47
|
+
do_demag: bool = True,
|
|
48
|
+
sim_id: str = "Generic Simulation class",
|
|
49
|
+
config: NmagConfig | None = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
|
|
52
|
+
self.class_id: str = sim_id # String identifying the kind of Simulation class
|
|
53
|
+
self.units: Any | None = None # Simulation units used by this class
|
|
54
|
+
self.do_demag: bool = do_demag # Whether we should include the demag field
|
|
55
|
+
# List of all the materials used by the Simulation object
|
|
56
|
+
self.materials: list[Any] | None = None
|
|
57
|
+
|
|
58
|
+
# Dictionary used by the hysteresis method to find abbreviations for
|
|
59
|
+
# frequently used things to save or do.
|
|
60
|
+
# Example: for ``sim.hysteresis(..., save=[('averages', at(...))])``
|
|
61
|
+
# the string 'averages' needs to be a key in this dictionary.
|
|
62
|
+
# The corresponding value is the function to call.
|
|
63
|
+
self.action_abbreviations: dict[str, Action] = {}
|
|
64
|
+
|
|
65
|
+
# Every quantity the user may want to save needs to be listed here used for data IO
|
|
66
|
+
self.known_quantities = known_quantities
|
|
67
|
+
self.known_quantities_by_name = known_quantities_by_name
|
|
68
|
+
self.known_field_quantities = known_field_quantities
|
|
69
|
+
|
|
70
|
+
self.config = NmagConfig.from_environment() if config is None else config
|
|
71
|
+
self.name = name or self.config.default_name
|
|
72
|
+
log.info(f"Simulation(name={self.name}) object created")
|
|
73
|
+
|
|
74
|
+
self._restarting: bool = False
|
|
75
|
+
data_filenames: list[Path] = [self._ndtfilename(), self._h5filename()]
|
|
76
|
+
self._manage_output_files(data_filenames)
|
|
77
|
+
|
|
78
|
+
self.clock: SimulationClock = SimulationClock()
|
|
79
|
+
|
|
80
|
+
# The advance_time method does not allow to carry on the simulation
|
|
81
|
+
# up to t = infinite. Sometimes we want to simulate for n steps,
|
|
82
|
+
# without any time limits. However we must give a time limit.
|
|
83
|
+
# This is then how we approximate t = infinite.
|
|
84
|
+
# For now, we do not provide any function to set or change it.
|
|
85
|
+
# The user should just use:
|
|
86
|
+
# sim = Simulation()
|
|
87
|
+
# sim.max_time_reached = SI(1000, "s")
|
|
88
|
+
self.max_time_reached: SI = SI(1, "s")
|
|
89
|
+
|
|
90
|
+
# Add abbreviations so that things can be saved just by giving
|
|
91
|
+
# corresponding ID strings.
|
|
92
|
+
# Example: hysteresis(..., save=[('averages', ...)])
|
|
93
|
+
self.add_save_abbrev("save_averages", lambda sim: sim.save_data(avoid_same_step=True))
|
|
94
|
+
self.add_save_abbrev(
|
|
95
|
+
"save_fields", lambda sim: sim.save_data(fields="all", avoid_same_step=True)
|
|
96
|
+
)
|
|
97
|
+
self.add_save_abbrev(
|
|
98
|
+
"save_field_m", lambda sim: sim.save_data(fields=["m"], avoid_same_step=True)
|
|
99
|
+
)
|
|
100
|
+
self.add_save_abbrev("save_restart", lambda sim: sim.save_restart_file())
|
|
101
|
+
self.add_do_abbrev("do_next_stage", SimulationCore.hysteresis_next_stage)
|
|
102
|
+
self.add_do_abbrev("do_exit", SimulationCore.hysteresis_exit)
|
|
103
|
+
|
|
104
|
+
self.writer = DataWriter(
|
|
105
|
+
ndt_filename=self._ndtfilename(),
|
|
106
|
+
h5_filename=self._h5filename(),
|
|
107
|
+
append=self.config.output_policy == "append",
|
|
108
|
+
)
|
|
109
|
+
self.last_spatial_save_timings_seconds: dict[str, float] = {}
|
|
110
|
+
self.last_maxangle_timings_seconds: dict[str, float] = {}
|
|
111
|
+
|
|
112
|
+
# The following list contains a description of the physics components
|
|
113
|
+
# which are included in the physical model For example,
|
|
114
|
+
# ["exch", "demag"] indicates that exchange and demag are included.
|
|
115
|
+
# In this case, spin transfer torque is not. This information
|
|
116
|
+
# is used to understand which fields are relevant and which are not
|
|
117
|
+
# (so that we do not save empty fields). Following the previous
|
|
118
|
+
# example, dm_dcurrent, current_density won't be saved.
|
|
119
|
+
self._components: list[str] | None = None
|
|
120
|
+
|
|
121
|
+
def _manage_output_files(self, data_filenames: list[Path]) -> None:
|
|
122
|
+
"""Apply this simulation's explicit NDT/HDF5 output policy."""
|
|
123
|
+
|
|
124
|
+
prepare_output_files(data_filenames, self.config.output_policy)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def id(self) -> int:
|
|
128
|
+
"""ID."""
|
|
129
|
+
return self.clock.id
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def stage(self) -> int:
|
|
133
|
+
"""Stage number."""
|
|
134
|
+
return self.clock.stage
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def step(self) -> int:
|
|
138
|
+
"""Global step number (always increases)."""
|
|
139
|
+
return self.clock.step
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def time(self) -> SI:
|
|
143
|
+
"""Global time reached (always increases)."""
|
|
144
|
+
return self.clock.time
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def stage_step(self) -> int:
|
|
148
|
+
"""Step number counted from the beginning of the current stage."""
|
|
149
|
+
return self.clock.stage_step
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def stage_time(self) -> SI:
|
|
153
|
+
"""Time reached counted from the beginning of the current stage."""
|
|
154
|
+
return self.clock.stage_time
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def real_time(self) -> SI:
|
|
158
|
+
"""Time passed in the 'real' world."""
|
|
159
|
+
return self.clock.real_time
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def last_step_dt(self) -> SI:
|
|
163
|
+
"""Length of the last simulation step."""
|
|
164
|
+
return self.clock.last_step_dt_si
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def components(self) -> list[str]:
|
|
168
|
+
"""Get the physical components included in the model."""
|
|
169
|
+
if self._components is not None:
|
|
170
|
+
return self._components
|
|
171
|
+
|
|
172
|
+
else:
|
|
173
|
+
components = ["exch"]
|
|
174
|
+
if self.do_demag:
|
|
175
|
+
components.append("demag")
|
|
176
|
+
self._components = components
|
|
177
|
+
return components
|
|
178
|
+
|
|
179
|
+
def get_all_field_names(self) -> list[str]:
|
|
180
|
+
"""Get all field names relevant to the enabled components."""
|
|
181
|
+
return [
|
|
182
|
+
q.name
|
|
183
|
+
for q in self.known_field_quantities
|
|
184
|
+
if q.context is None or q.context in self.components
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def hysteresis_next_stage(sim: SimulationCore) -> None:
|
|
189
|
+
"""
|
|
190
|
+
Terminate the current stage of the hysteresis computation
|
|
191
|
+
and start the next one.
|
|
192
|
+
"""
|
|
193
|
+
sim.clock.stage_end = True
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def hysteresis_exit(sim: SimulationCore) -> None:
|
|
197
|
+
"""
|
|
198
|
+
Exit from the running hysteresis computation.
|
|
199
|
+
"""
|
|
200
|
+
sim.clock.exit_hysteresis = True
|
|
201
|
+
sim.clock.stage_end = True
|
|
202
|
+
|
|
203
|
+
simulation_relax = hysteresis_m.simulation_relax
|
|
204
|
+
relax = simulation_relax
|
|
205
|
+
|
|
206
|
+
simulation_hysteresis = hysteresis_m.simulation_hysteresis
|
|
207
|
+
hysteresis = simulation_hysteresis
|
|
208
|
+
|
|
209
|
+
def add_action_abbrev(
|
|
210
|
+
self,
|
|
211
|
+
abbreviation: str,
|
|
212
|
+
function: Action,
|
|
213
|
+
prefix: str | None = None,
|
|
214
|
+
) -> None:
|
|
215
|
+
"""Add an abbreviation for a 'save' or 'do' action."""
|
|
216
|
+
if prefix is None:
|
|
217
|
+
self.action_abbreviations[abbreviation] = function
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
else:
|
|
221
|
+
valid_prefixes = ["save", "do"]
|
|
222
|
+
if prefix not in valid_prefixes:
|
|
223
|
+
raise ValueError(
|
|
224
|
+
f"Valid prefixes for action abbreviations "
|
|
225
|
+
f"are {valid_prefixes}, you gave '{prefix}'!"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if abbreviation.startswith(prefix):
|
|
229
|
+
self.action_abbreviations[abbreviation] = function
|
|
230
|
+
else:
|
|
231
|
+
full_abbreviation = f"{prefix}_{abbreviation}"
|
|
232
|
+
self.action_abbreviations[full_abbreviation] = function
|
|
233
|
+
|
|
234
|
+
def add_save_abbrev(self, abbreviation: str, function: Action) -> None:
|
|
235
|
+
"""Add an abbreviation to be used in the 'save' argument of the
|
|
236
|
+
hysteresis method. For example, if you use the following:
|
|
237
|
+
|
|
238
|
+
def funky_function(sim): print "Hello, I'm Funky!"
|
|
239
|
+
sim.add_save_abbrev('funky', funky_function)
|
|
240
|
+
|
|
241
|
+
Then you can call:
|
|
242
|
+
|
|
243
|
+
sim.hysteresis(Hs, save=[('funky', at('convergence'))])
|
|
244
|
+
|
|
245
|
+
and this will be equivalent to:
|
|
246
|
+
|
|
247
|
+
sim.hysteresis(Hs, save=[(funky_function, at('convergence')])
|
|
248
|
+
"""
|
|
249
|
+
self.add_action_abbrev(abbreviation, function, prefix="save")
|
|
250
|
+
|
|
251
|
+
def add_do_abbrev(self, abbreviation: str, function: Action) -> None:
|
|
252
|
+
"""Add an abbreviation for the 'do' argument of hysteresis."""
|
|
253
|
+
self.add_action_abbrev(abbreviation, function, prefix="do")
|
|
254
|
+
|
|
255
|
+
def do_next_stage(self, stage: int | None = None) -> None:
|
|
256
|
+
"""Increment the simulation stage."""
|
|
257
|
+
self.clock.inc_stage(stage=stage)
|
|
258
|
+
|
|
259
|
+
def is_converged(self) -> bool:
|
|
260
|
+
"""Returns True when convergence has been reached."""
|
|
261
|
+
return self.clock.convergence
|
|
262
|
+
|
|
263
|
+
def _get_filename(self, ext: str) -> Path:
|
|
264
|
+
"""Get the full, absolute path for an output file."""
|
|
265
|
+
basename = self.name + ext
|
|
266
|
+
return self.config.output_directory / basename
|
|
267
|
+
|
|
268
|
+
def _ndtfilename(self) -> Path:
|
|
269
|
+
return self._get_filename("_dat.ndt")
|
|
270
|
+
|
|
271
|
+
def _h5filename(self) -> Path:
|
|
272
|
+
return self._get_filename("_dat.h5")
|
|
273
|
+
|
|
274
|
+
def _statfilename(self) -> Path:
|
|
275
|
+
return self._get_filename("_cvode.log")
|
|
276
|
+
|
|
277
|
+
def _tolfilename(self) -> Path:
|
|
278
|
+
return self._get_filename("_tol.log")
|
|
279
|
+
|
|
280
|
+
def get_restart_file_name(self) -> Path:
|
|
281
|
+
"""Return the default name for the restart file."""
|
|
282
|
+
return self.config.output_directory / f"{self.name}_restart.h5"
|
|
283
|
+
|
|
284
|
+
def get_materials_of_field(self, field_name: str) -> list[Any]:
|
|
285
|
+
"""
|
|
286
|
+
Returns all materials for a per-material field.
|
|
287
|
+
Returns an empty list if the field is not per-material or
|
|
288
|
+
if materials have not been defined yet.
|
|
289
|
+
"""
|
|
290
|
+
quantity = self.known_quantities_by_name[field_name]
|
|
291
|
+
|
|
292
|
+
if "?" in (quantity.signature or ""):
|
|
293
|
+
return self.materials if self.materials is not None else []
|
|
294
|
+
|
|
295
|
+
return []
|
|
296
|
+
|
|
297
|
+
@abstractmethod
|
|
298
|
+
def save_spatial_fields(
|
|
299
|
+
self,
|
|
300
|
+
filename: str | None = None,
|
|
301
|
+
fieldnames: list[str] | None = None,
|
|
302
|
+
) -> None:
|
|
303
|
+
"""Abstract method to save spatially-resolved fields."""
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
def save_data(
|
|
307
|
+
self,
|
|
308
|
+
fields: str | list[str] | None = None,
|
|
309
|
+
avoid_same_step: bool = False,
|
|
310
|
+
) -> None:
|
|
311
|
+
"""
|
|
312
|
+
Save simulation data.
|
|
313
|
+
|
|
314
|
+
- Averages are saved to the *.ndt file (TSV format).
|
|
315
|
+
- Spatially resolved fields are saved to the *.h5 file.
|
|
316
|
+
|
|
317
|
+
:Parameters:
|
|
318
|
+
`fields` : None, 'all' or list of fieldnames
|
|
319
|
+
If None, only saves averages.
|
|
320
|
+
If 'all', saves all available fields.
|
|
321
|
+
If a list (e.g., ['m', 'H_demag']), saves only those fields.
|
|
322
|
+
|
|
323
|
+
`avoid_same_step` : bool
|
|
324
|
+
If True, only save if clock.step has changed since last save.
|
|
325
|
+
This prevents duplicate data points during hysteresis loops.
|
|
326
|
+
"""
|
|
327
|
+
self.writer.save(self, fields, avoid_same_step)
|
|
328
|
+
|
|
329
|
+
@abstractmethod
|
|
330
|
+
def save_mesh(self, filename: str) -> None:
|
|
331
|
+
"""Save the mesh to a file."""
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
@abstractmethod
|
|
335
|
+
def load_mesh(
|
|
336
|
+
self,
|
|
337
|
+
filename: str,
|
|
338
|
+
region_names_and_mag_mats: Any,
|
|
339
|
+
unit_length: float,
|
|
340
|
+
do_reorder: bool = False,
|
|
341
|
+
manual_distribution: Any = None,
|
|
342
|
+
) -> None:
|
|
343
|
+
"""Load a mesh from a file."""
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
@abstractmethod
|
|
347
|
+
def create_mesh(
|
|
348
|
+
self,
|
|
349
|
+
cell_nums: Sequence[int],
|
|
350
|
+
cell_sizes: Sequence[float],
|
|
351
|
+
materials: Any,
|
|
352
|
+
regions: Callable[..., Any] | None = None,
|
|
353
|
+
origin: tuple[float, float, float] = (0.0, 0.0, 0.0),
|
|
354
|
+
) -> None:
|
|
355
|
+
"""Create a new mesh."""
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
@abstractmethod
|
|
359
|
+
def set_params(
|
|
360
|
+
self,
|
|
361
|
+
stopping_dm_dt: SI | None = None,
|
|
362
|
+
ts_rel_tol: float | None = None,
|
|
363
|
+
ts_abs_tol: float | None = None,
|
|
364
|
+
) -> None:
|
|
365
|
+
"""Set simulation parameters."""
|
|
366
|
+
pass
|
|
367
|
+
|
|
368
|
+
@abstractmethod
|
|
369
|
+
def reinitialise(self, initial_time: float | None = None) -> None:
|
|
370
|
+
"""Re-initialise the simulation."""
|
|
371
|
+
pass
|
|
372
|
+
|
|
373
|
+
@abstractmethod
|
|
374
|
+
def set_local_magnetic_coupling(self, mat1: Any, mat2: Any, coupling: Any) -> None:
|
|
375
|
+
"""Set local magnetic coupling between materials."""
|
|
376
|
+
pass
|
|
377
|
+
|
|
378
|
+
@abstractmethod
|
|
379
|
+
def set_H_ext(self, values: Any, unit: SI | None = None) -> None:
|
|
380
|
+
"""Set the external magnetic field."""
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
@abstractmethod
|
|
384
|
+
def set_m(self, values: Any, subfieldname: str | None = None) -> None:
|
|
385
|
+
"""Set the magnetization."""
|
|
386
|
+
pass
|
|
387
|
+
|
|
388
|
+
@abstractmethod
|
|
389
|
+
def set_pinning(self, values: Any) -> None:
|
|
390
|
+
"""Set pinning sites."""
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
@abstractmethod
|
|
394
|
+
def set_current_density(self, values: Any, unit: SI | None = None) -> None:
|
|
395
|
+
"""Set current density."""
|
|
396
|
+
pass
|
|
397
|
+
|
|
398
|
+
@abstractmethod
|
|
399
|
+
def advance_time(
|
|
400
|
+
self,
|
|
401
|
+
target_time: SI,
|
|
402
|
+
max_it: int = -1,
|
|
403
|
+
exact_tstop: bool | None = None,
|
|
404
|
+
) -> SI:
|
|
405
|
+
"""Advance the simulation time."""
|
|
406
|
+
pass
|
|
407
|
+
|
|
408
|
+
@abstractmethod
|
|
409
|
+
def save_restart_file(
|
|
410
|
+
self, filename: str | None = None, fieldnames: list[str] | None = None, all: bool = False
|
|
411
|
+
) -> None:
|
|
412
|
+
"""Save a restart file."""
|
|
413
|
+
pass
|
|
414
|
+
|
|
415
|
+
@abstractmethod
|
|
416
|
+
def load_restart_file(self, filename: str | None = None) -> None:
|
|
417
|
+
"""Load a restart file."""
|
|
418
|
+
pass
|
|
419
|
+
|
|
420
|
+
@abstractmethod
|
|
421
|
+
def save_m_to_file(self, filename: str, format: str | None = None) -> None:
|
|
422
|
+
"""Save magnetization to a file."""
|
|
423
|
+
pass
|
|
424
|
+
|
|
425
|
+
@abstractmethod
|
|
426
|
+
def load_m_from_file(self, filename: str, format: str | None = None) -> None:
|
|
427
|
+
"""Load magnetization from a file."""
|
|
428
|
+
pass
|
|
429
|
+
|
|
430
|
+
@abstractmethod
|
|
431
|
+
def probe_subfield(self, subfieldname: str, pos: SI, unit: SI | None = None) -> Any:
|
|
432
|
+
"""Probe a subfield at a specific position."""
|
|
433
|
+
pass
|
|
434
|
+
|
|
435
|
+
@abstractmethod
|
|
436
|
+
def probe_subfield_siv(
|
|
437
|
+
self,
|
|
438
|
+
subfieldname: str,
|
|
439
|
+
pos: list[float],
|
|
440
|
+
unit: SI | None = None,
|
|
441
|
+
) -> Any:
|
|
442
|
+
"""Probe a subfield returning SI values."""
|
|
443
|
+
pass
|
|
444
|
+
|
|
445
|
+
@abstractmethod
|
|
446
|
+
def get_subfield(self, subfieldname: str, units: SI | None = None) -> Any:
|
|
447
|
+
"""Get the entire array for a subfield."""
|
|
448
|
+
pass
|
|
449
|
+
|
|
450
|
+
@abstractmethod
|
|
451
|
+
def get_subfield_average(self, subfieldname: str, mat_name: str | None = None) -> Any:
|
|
452
|
+
"""Get the average value of a subfield."""
|
|
453
|
+
pass
|
|
454
|
+
|
|
455
|
+
@abstractmethod
|
|
456
|
+
def get_maxangle_average(self, field_name: str) -> float | None:
|
|
457
|
+
"""Get the average maximum angle for a field."""
|
|
458
|
+
pass
|
throttler/__init__.py
ADDED
throttler/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker for PEP 561 inline type information.
|
throttler/throttler.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from collections.abc import Hashable
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
This replaces the reporttools.py from the original nmag repo.
|
|
6
|
+
https://github.com/nmag-project/nmag-src/blob/master/interface/nsim/reporttools.py.
|
|
7
|
+
It is converted into a class to avoid global variables and uses monotonic time instead.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Throttler:
|
|
12
|
+
"""
|
|
13
|
+
Manages state to allow actions at most every N seconds per key.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
last_called (dict[Hashable, float]): A dictionary mapping
|
|
17
|
+
a unique key to the timestamp (from time.monotonic())
|
|
18
|
+
of its last allowed call.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
"""Initializes the throttle state."""
|
|
23
|
+
# Stores the time of the last successful call for each key
|
|
24
|
+
self.last_called: dict[Hashable, float] = {}
|
|
25
|
+
|
|
26
|
+
def is_allowed(self, key: Hashable, report_delay: float) -> bool:
|
|
27
|
+
"""
|
|
28
|
+
Checks if an action for a given key should be allowed.
|
|
29
|
+
|
|
30
|
+
Returns True if 'report_delay' seconds have passed since the
|
|
31
|
+
last time this method returned True for the same 'key',
|
|
32
|
+
or if this is the first call for this 'key'.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
key: A unique, hashable identifier for the action
|
|
36
|
+
being rate-limited (e.g., a string, a tuple).
|
|
37
|
+
report_delay: The minimum number of seconds that must
|
|
38
|
+
pass between True returns.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
True if the action is allowed, False otherwise.
|
|
42
|
+
"""
|
|
43
|
+
now = time.monotonic()
|
|
44
|
+
|
|
45
|
+
# Get the last time this key was allowed.
|
|
46
|
+
# Default to 0.0, ensuring the first call always passes
|
|
47
|
+
last_time = self.last_called.get(key, 0.0)
|
|
48
|
+
|
|
49
|
+
if now - last_time >= report_delay:
|
|
50
|
+
# Time has elapsed. Allow the action and update the timestamp.
|
|
51
|
+
self.last_called[key] = now
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
# Not enough time has passed. Deny the action.
|
|
55
|
+
return False
|
when/__init__.py
ADDED
when/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker for PEP 561 inline type information.
|