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,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ....backends import (
|
|
8
|
+
_load_rust_accelerator,
|
|
9
|
+
_selected_demag_fem_assembly_backend,
|
|
10
|
+
)
|
|
11
|
+
from ...support import _simulation_compatibility_binding
|
|
12
|
+
from .charges import volume_charge_scaling_correction
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SimulationDemagFemAssemblyMixin:
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
|
|
18
|
+
def __getattr__(self, name: str) -> Any: ...
|
|
19
|
+
|
|
20
|
+
def _assemble_demag_fem_system(
|
|
21
|
+
self,
|
|
22
|
+
points: np.ndarray,
|
|
23
|
+
simplices: np.ndarray,
|
|
24
|
+
m: np.ndarray,
|
|
25
|
+
ms_values: np.ndarray,
|
|
26
|
+
volume_charge_scales: np.ndarray,
|
|
27
|
+
) -> tuple[Any, np.ndarray, np.ndarray]:
|
|
28
|
+
point_count = len(points)
|
|
29
|
+
stiffness, gradients_by_cell, volumes = self._demag_fem_geometry_for_mesh(
|
|
30
|
+
points,
|
|
31
|
+
simplices,
|
|
32
|
+
)
|
|
33
|
+
assembly_backend = _selected_demag_fem_assembly_backend(
|
|
34
|
+
len(simplices), getattr(self, "config", None)
|
|
35
|
+
)
|
|
36
|
+
with self._record_active_subfield_array_timing_block(
|
|
37
|
+
f"demag_auxiliary:assemble_fem:divergence:{assembly_backend}",
|
|
38
|
+
):
|
|
39
|
+
if assembly_backend == "rust":
|
|
40
|
+
divergence = self._assemble_demag_fem_divergence_rust(
|
|
41
|
+
simplices,
|
|
42
|
+
gradients_by_cell,
|
|
43
|
+
volumes,
|
|
44
|
+
m,
|
|
45
|
+
ms_values,
|
|
46
|
+
point_count,
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
divergence = self._assemble_demag_fem_divergence_python(
|
|
50
|
+
simplices,
|
|
51
|
+
gradients_by_cell,
|
|
52
|
+
volumes,
|
|
53
|
+
m,
|
|
54
|
+
ms_values,
|
|
55
|
+
point_count,
|
|
56
|
+
)
|
|
57
|
+
divergence += volume_charge_scaling_correction(
|
|
58
|
+
simplices,
|
|
59
|
+
gradients_by_cell,
|
|
60
|
+
volumes,
|
|
61
|
+
m,
|
|
62
|
+
ms_values,
|
|
63
|
+
volume_charge_scales,
|
|
64
|
+
point_count,
|
|
65
|
+
)
|
|
66
|
+
return stiffness, divergence, volumes
|
|
67
|
+
|
|
68
|
+
def _assemble_demag_fem_divergence_python(
|
|
69
|
+
self,
|
|
70
|
+
simplices: np.ndarray,
|
|
71
|
+
gradients_by_cell: np.ndarray,
|
|
72
|
+
volumes: np.ndarray,
|
|
73
|
+
m: np.ndarray,
|
|
74
|
+
ms_values: np.ndarray,
|
|
75
|
+
point_count: int,
|
|
76
|
+
) -> np.ndarray:
|
|
77
|
+
divergence = np.zeros(point_count, dtype=float)
|
|
78
|
+
if len(simplices) == 0:
|
|
79
|
+
return divergence
|
|
80
|
+
|
|
81
|
+
cell_m = m[simplices] * ms_values[:, np.newaxis, np.newaxis]
|
|
82
|
+
average_m = np.mean(cell_m, axis=1)
|
|
83
|
+
cell_divergence = volumes[:, np.newaxis] * np.einsum(
|
|
84
|
+
"cij,cj->ci",
|
|
85
|
+
gradients_by_cell,
|
|
86
|
+
average_m,
|
|
87
|
+
)
|
|
88
|
+
np.add.at(divergence, simplices, cell_divergence)
|
|
89
|
+
return divergence
|
|
90
|
+
|
|
91
|
+
def _assemble_demag_fem_divergence_rust(
|
|
92
|
+
self,
|
|
93
|
+
simplices: np.ndarray,
|
|
94
|
+
gradients_by_cell: np.ndarray,
|
|
95
|
+
volumes: np.ndarray,
|
|
96
|
+
m: np.ndarray,
|
|
97
|
+
ms_values: np.ndarray,
|
|
98
|
+
point_count: int,
|
|
99
|
+
) -> np.ndarray:
|
|
100
|
+
rust_accel = _simulation_compatibility_binding(
|
|
101
|
+
"_load_rust_accelerator",
|
|
102
|
+
_load_rust_accelerator,
|
|
103
|
+
)("NmagConfig.accelerator['fem_assembly']")
|
|
104
|
+
return np.asarray(
|
|
105
|
+
rust_accel.build_demag_fem_divergence(
|
|
106
|
+
np.asarray(simplices, dtype=np.int64),
|
|
107
|
+
np.asarray(gradients_by_cell, dtype=np.float64),
|
|
108
|
+
np.asarray(volumes, dtype=np.float64),
|
|
109
|
+
np.asarray(m, dtype=np.float64),
|
|
110
|
+
np.asarray(ms_values, dtype=np.float64),
|
|
111
|
+
int(point_count),
|
|
112
|
+
),
|
|
113
|
+
dtype=float,
|
|
114
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Finite-element separation of demagnetization volume charges."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def volume_charge_scaling_correction(
|
|
9
|
+
simplices: np.ndarray,
|
|
10
|
+
gradients_by_cell: np.ndarray,
|
|
11
|
+
volumes: np.ndarray,
|
|
12
|
+
m: np.ndarray,
|
|
13
|
+
ms_values: np.ndarray,
|
|
14
|
+
volume_charge_scales: np.ndarray,
|
|
15
|
+
point_count: int,
|
|
16
|
+
) -> np.ndarray:
|
|
17
|
+
"""Return the weak-source correction for material volume-charge scales.
|
|
18
|
+
|
|
19
|
+
The standard FEM assembly is the combined surface and volume source
|
|
20
|
+
``integral(grad(test) . M)``. Integration by parts identifies the volume
|
|
21
|
+
part on a linear tetrahedron as ``-volume * div(M) / 4`` at every local
|
|
22
|
+
node. Scaling that term separately preserves the unscaled surface charge.
|
|
23
|
+
"""
|
|
24
|
+
correction = np.zeros(point_count, dtype=float)
|
|
25
|
+
if len(simplices) == 0 or np.all(volume_charge_scales == 1.0):
|
|
26
|
+
return correction
|
|
27
|
+
|
|
28
|
+
cell_m = m[simplices] * ms_values[:, np.newaxis, np.newaxis]
|
|
29
|
+
cell_divergence = np.einsum("cij,cij->c", gradients_by_cell, cell_m)
|
|
30
|
+
local_correction = (
|
|
31
|
+
-(volume_charge_scales - 1.0) * volumes * cell_divergence / simplices.shape[1]
|
|
32
|
+
)
|
|
33
|
+
np.add.at(
|
|
34
|
+
correction,
|
|
35
|
+
simplices,
|
|
36
|
+
np.broadcast_to(local_correction[:, np.newaxis], simplices.shape),
|
|
37
|
+
)
|
|
38
|
+
return correction
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ....backends import (
|
|
8
|
+
_load_rust_accelerator,
|
|
9
|
+
_selected_demag_boundary_face_backend,
|
|
10
|
+
_selected_demag_fem_geometry_backend,
|
|
11
|
+
_selected_demag_fem_matrix_backend,
|
|
12
|
+
)
|
|
13
|
+
from ....demag import (
|
|
14
|
+
_oriented_boundary_faces,
|
|
15
|
+
_validate_dense_demag_size,
|
|
16
|
+
_validate_tetrahedral_cells,
|
|
17
|
+
)
|
|
18
|
+
from ...support import _simulation_compatibility_binding
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SimulationDemagFemGeometryMixin:
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
_demag_fem_geometry_cache: Any | None
|
|
24
|
+
_demag_boundary_faces_cache: Any | None
|
|
25
|
+
|
|
26
|
+
def __getattr__(self, name: str) -> Any: ...
|
|
27
|
+
|
|
28
|
+
def _demag_fem_geometry_for_mesh(
|
|
29
|
+
self,
|
|
30
|
+
points: np.ndarray,
|
|
31
|
+
simplices: np.ndarray,
|
|
32
|
+
) -> tuple[Any, np.ndarray, np.ndarray]:
|
|
33
|
+
token = self._mesh_geometry_token()
|
|
34
|
+
self._ensure_demag_geometry_cache_token(token)
|
|
35
|
+
matrix_backend = _selected_demag_fem_matrix_backend(len(points))
|
|
36
|
+
if self._demag_fem_geometry_cache is not None:
|
|
37
|
+
cached_backend, stiffness, gradients, volumes = self._demag_fem_geometry_cache
|
|
38
|
+
if cached_backend == matrix_backend:
|
|
39
|
+
return stiffness, gradients, volumes
|
|
40
|
+
|
|
41
|
+
if matrix_backend == "sparse":
|
|
42
|
+
stiffness, gradients, volumes = self._demag_fem_geometry_for_mesh_sparse(
|
|
43
|
+
points,
|
|
44
|
+
simplices,
|
|
45
|
+
)
|
|
46
|
+
self._demag_fem_geometry_cache = (
|
|
47
|
+
matrix_backend,
|
|
48
|
+
stiffness,
|
|
49
|
+
gradients,
|
|
50
|
+
volumes,
|
|
51
|
+
)
|
|
52
|
+
return stiffness, gradients, volumes
|
|
53
|
+
|
|
54
|
+
backend = _selected_demag_fem_geometry_backend(
|
|
55
|
+
len(simplices), getattr(self, "config", None)
|
|
56
|
+
)
|
|
57
|
+
with self._record_active_subfield_array_timing_block(
|
|
58
|
+
f"demag_auxiliary:assemble_fem:geometry:{backend}",
|
|
59
|
+
):
|
|
60
|
+
if backend == "rust":
|
|
61
|
+
geometry = self._demag_fem_geometry_for_mesh_rust(
|
|
62
|
+
points,
|
|
63
|
+
simplices,
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
geometry = self._demag_fem_geometry_for_mesh_python(
|
|
67
|
+
points,
|
|
68
|
+
simplices,
|
|
69
|
+
)
|
|
70
|
+
stiffness, gradients, volumes = geometry
|
|
71
|
+
self._demag_fem_geometry_cache = (matrix_backend, stiffness, gradients, volumes)
|
|
72
|
+
return stiffness, gradients, volumes
|
|
73
|
+
|
|
74
|
+
def _demag_fem_geometry_for_mesh_sparse(
|
|
75
|
+
self,
|
|
76
|
+
points: np.ndarray,
|
|
77
|
+
simplices: np.ndarray,
|
|
78
|
+
) -> tuple[Any, np.ndarray, np.ndarray]:
|
|
79
|
+
from scipy import sparse
|
|
80
|
+
|
|
81
|
+
_validate_tetrahedral_cells(points, simplices)
|
|
82
|
+
point_count = len(points)
|
|
83
|
+
if len(simplices) == 0:
|
|
84
|
+
return (
|
|
85
|
+
sparse.csr_matrix((point_count, point_count), dtype=float),
|
|
86
|
+
np.empty((0, 4, 3), dtype=float),
|
|
87
|
+
np.empty(0, dtype=float),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
cell_points = points[simplices]
|
|
91
|
+
matrices = np.concatenate(
|
|
92
|
+
[np.ones((len(simplices), 4, 1), dtype=float), cell_points],
|
|
93
|
+
axis=2,
|
|
94
|
+
)
|
|
95
|
+
inverses = np.linalg.inv(matrices)
|
|
96
|
+
gradients = np.transpose(inverses[:, 1:, :], (0, 2, 1))
|
|
97
|
+
volumes = np.abs(np.linalg.det(cell_points[:, 1:] - cell_points[:, [0]])) / 6.0
|
|
98
|
+
local_stiffness = volumes[:, np.newaxis, np.newaxis] * np.einsum(
|
|
99
|
+
"cik,cjk->cij",
|
|
100
|
+
gradients,
|
|
101
|
+
gradients,
|
|
102
|
+
)
|
|
103
|
+
rows = np.broadcast_to(simplices[:, :, np.newaxis], local_stiffness.shape).ravel()
|
|
104
|
+
columns = np.broadcast_to(simplices[:, np.newaxis, :], local_stiffness.shape).ravel()
|
|
105
|
+
stiffness = sparse.coo_matrix(
|
|
106
|
+
(local_stiffness.ravel(), (rows, columns)),
|
|
107
|
+
shape=(point_count, point_count),
|
|
108
|
+
).tocsr()
|
|
109
|
+
stiffness.sum_duplicates()
|
|
110
|
+
return stiffness, gradients, volumes
|
|
111
|
+
|
|
112
|
+
def _demag_fem_geometry_for_mesh_python(
|
|
113
|
+
self,
|
|
114
|
+
points: np.ndarray,
|
|
115
|
+
simplices: np.ndarray,
|
|
116
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
117
|
+
point_count = len(points)
|
|
118
|
+
_validate_dense_demag_size(point_count)
|
|
119
|
+
_validate_tetrahedral_cells(points, simplices)
|
|
120
|
+
stiffness = np.zeros((point_count, point_count), dtype=float)
|
|
121
|
+
gradients_by_cell = np.zeros((len(simplices), 4, 3), dtype=float)
|
|
122
|
+
volumes = np.zeros(len(simplices), dtype=float)
|
|
123
|
+
|
|
124
|
+
if len(simplices) == 0:
|
|
125
|
+
return stiffness, gradients_by_cell, volumes
|
|
126
|
+
|
|
127
|
+
cell_points = points[simplices]
|
|
128
|
+
matrices = np.concatenate(
|
|
129
|
+
[
|
|
130
|
+
np.ones((len(simplices), 4, 1), dtype=float),
|
|
131
|
+
cell_points,
|
|
132
|
+
],
|
|
133
|
+
axis=2,
|
|
134
|
+
)
|
|
135
|
+
try:
|
|
136
|
+
inverses = np.linalg.inv(matrices)
|
|
137
|
+
except np.linalg.LinAlgError:
|
|
138
|
+
return self._demag_fem_geometry_for_mesh_loop(
|
|
139
|
+
points,
|
|
140
|
+
simplices,
|
|
141
|
+
stiffness,
|
|
142
|
+
gradients_by_cell,
|
|
143
|
+
volumes,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
gradients_by_cell[:] = np.transpose(inverses[:, 1:, :], (0, 2, 1))
|
|
147
|
+
volumes[:] = np.abs(np.linalg.det(cell_points[:, 1:] - cell_points[:, [0]])) / 6.0
|
|
148
|
+
positive = volumes > 0.0
|
|
149
|
+
if np.any(positive):
|
|
150
|
+
local_stiffness = volumes[positive, np.newaxis, np.newaxis] * np.einsum(
|
|
151
|
+
"cik,cjk->cij",
|
|
152
|
+
gradients_by_cell[positive],
|
|
153
|
+
gradients_by_cell[positive],
|
|
154
|
+
)
|
|
155
|
+
positive_simplices = simplices[positive]
|
|
156
|
+
np.add.at(
|
|
157
|
+
stiffness,
|
|
158
|
+
(
|
|
159
|
+
positive_simplices[:, :, np.newaxis],
|
|
160
|
+
positive_simplices[:, np.newaxis, :],
|
|
161
|
+
),
|
|
162
|
+
local_stiffness,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return stiffness, gradients_by_cell, volumes
|
|
166
|
+
|
|
167
|
+
def _demag_fem_geometry_for_mesh_rust(
|
|
168
|
+
self,
|
|
169
|
+
points: np.ndarray,
|
|
170
|
+
simplices: np.ndarray,
|
|
171
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
172
|
+
_validate_dense_demag_size(len(points))
|
|
173
|
+
_validate_tetrahedral_cells(points, simplices)
|
|
174
|
+
rust_accel = _simulation_compatibility_binding(
|
|
175
|
+
"_load_rust_accelerator",
|
|
176
|
+
_load_rust_accelerator,
|
|
177
|
+
)("NmagConfig.accelerator['fem_geometry']")
|
|
178
|
+
stiffness, gradients_by_cell, volumes = rust_accel.build_demag_fem_geometry(
|
|
179
|
+
np.asarray(points, dtype=np.float64),
|
|
180
|
+
np.asarray(simplices, dtype=np.int64),
|
|
181
|
+
)
|
|
182
|
+
return (
|
|
183
|
+
np.asarray(stiffness, dtype=float),
|
|
184
|
+
np.asarray(gradients_by_cell, dtype=float),
|
|
185
|
+
np.asarray(volumes, dtype=float),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def _demag_fem_geometry_for_mesh_loop(
|
|
189
|
+
self,
|
|
190
|
+
points: np.ndarray,
|
|
191
|
+
simplices: np.ndarray,
|
|
192
|
+
stiffness: np.ndarray,
|
|
193
|
+
gradients_by_cell: np.ndarray,
|
|
194
|
+
volumes: np.ndarray,
|
|
195
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
196
|
+
for cell_index, simplex in enumerate(simplices):
|
|
197
|
+
cell_points = points[simplex]
|
|
198
|
+
matrix = np.column_stack([np.ones(4), cell_points])
|
|
199
|
+
try:
|
|
200
|
+
inverse = np.asarray(np.linalg.inv(matrix), dtype=float)
|
|
201
|
+
except np.linalg.LinAlgError as error:
|
|
202
|
+
raise ValueError(f"Demag FEM cell {cell_index} is singular.") from error
|
|
203
|
+
gradients = np.transpose(inverse[1:, :])
|
|
204
|
+
volume = abs(float(np.linalg.det(cell_points[1:] - cell_points[[0]]))) / 6.0
|
|
205
|
+
if volume <= 0.0:
|
|
206
|
+
raise ValueError(f"Demag FEM cell {cell_index} has zero volume.")
|
|
207
|
+
volumes[cell_index] = volume
|
|
208
|
+
gradients_by_cell[cell_index] = gradients
|
|
209
|
+
stiffness[np.ix_(simplex, simplex)] += volume * (gradients @ np.transpose(gradients))
|
|
210
|
+
|
|
211
|
+
return stiffness, gradients_by_cell, volumes
|
|
212
|
+
|
|
213
|
+
def _boundary_faces_for_demag_mesh(
|
|
214
|
+
self,
|
|
215
|
+
points: np.ndarray,
|
|
216
|
+
simplices: np.ndarray,
|
|
217
|
+
) -> list[tuple[int, tuple[int, int, int]]]:
|
|
218
|
+
token = self._mesh_geometry_token()
|
|
219
|
+
self._ensure_demag_geometry_cache_token(token)
|
|
220
|
+
if self._demag_boundary_faces_cache is not None:
|
|
221
|
+
return self._demag_boundary_faces_cache
|
|
222
|
+
|
|
223
|
+
backend = _selected_demag_boundary_face_backend(getattr(self, "config", None))
|
|
224
|
+
with self._record_active_subfield_array_timing_block(
|
|
225
|
+
f"demag_auxiliary:boundary_faces:{backend}",
|
|
226
|
+
):
|
|
227
|
+
if backend == "rust":
|
|
228
|
+
boundary_faces = self._boundary_faces_for_demag_mesh_rust(
|
|
229
|
+
points,
|
|
230
|
+
simplices,
|
|
231
|
+
)
|
|
232
|
+
else:
|
|
233
|
+
boundary_faces = _simulation_compatibility_binding(
|
|
234
|
+
"_oriented_boundary_faces",
|
|
235
|
+
_oriented_boundary_faces,
|
|
236
|
+
)(points, simplices)
|
|
237
|
+
self._demag_boundary_faces_cache = boundary_faces
|
|
238
|
+
self._demag_bem_cache = None
|
|
239
|
+
self._last_bem_operator_stats = None
|
|
240
|
+
return boundary_faces
|
|
241
|
+
|
|
242
|
+
def _boundary_faces_for_demag_mesh_rust(
|
|
243
|
+
self,
|
|
244
|
+
points: np.ndarray,
|
|
245
|
+
simplices: np.ndarray,
|
|
246
|
+
) -> list[tuple[int, tuple[int, int, int]]]:
|
|
247
|
+
rust_accel = _simulation_compatibility_binding(
|
|
248
|
+
"_load_rust_accelerator",
|
|
249
|
+
_load_rust_accelerator,
|
|
250
|
+
)("NmagConfig.accelerator['boundary_faces']")
|
|
251
|
+
owners, faces = rust_accel.build_oriented_boundary_faces(
|
|
252
|
+
np.asarray(points, dtype=np.float64),
|
|
253
|
+
np.asarray(simplices, dtype=np.int64),
|
|
254
|
+
)
|
|
255
|
+
owner_array = np.asarray(owners, dtype=int).flatten()
|
|
256
|
+
face_values = np.asarray(faces, dtype=int).flatten()
|
|
257
|
+
face_array = np.column_stack((face_values[0::3], face_values[1::3], face_values[2::3]))
|
|
258
|
+
return [
|
|
259
|
+
(int(owner), (int(face[0]), int(face[1]), int(face[2])))
|
|
260
|
+
for owner, face in zip(owner_array, face_array, strict=True)
|
|
261
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Demag field compatibility façade composed from recovery and auxiliary mixins."""
|
|
2
|
+
|
|
3
|
+
from .auxiliary import SimulationDemagAuxiliaryMixin
|
|
4
|
+
from .recovery import SimulationDemagRecoveryMixin
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SimulationDemagFieldsMixin(
|
|
8
|
+
SimulationDemagRecoveryMixin,
|
|
9
|
+
SimulationDemagAuxiliaryMixin,
|
|
10
|
+
):
|
|
11
|
+
"""Provide recovered demag fields and FEM/BEM scalar-potential solves."""
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from ....backends import (
|
|
9
|
+
_demag_internal_trace_enabled,
|
|
10
|
+
_selected_demag_linear_solver_backend,
|
|
11
|
+
)
|
|
12
|
+
from ....demag import _demag_solve_diagnostics, _solve_gauge_fixed
|
|
13
|
+
from ....demag.linear import _factorize_gauge_fixed, _is_sparse_matrix, _solve_sparse_gauge_fixed
|
|
14
|
+
from ...support import LEGACY_RHO_BOXED_VOLUME_M3
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SimulationDemagAuxiliaryMixin:
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
_demag_phi_cache: Any | None
|
|
20
|
+
_demag_rho_cache: Any | None
|
|
21
|
+
_demag_volumes_cache: Any | None
|
|
22
|
+
_demag_gauge_factorization_cache: Any | None
|
|
23
|
+
|
|
24
|
+
def __getattr__(self, name: str) -> Any: ...
|
|
25
|
+
|
|
26
|
+
def _get_demag_auxiliary_fields(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
27
|
+
cache_started = time.perf_counter()
|
|
28
|
+
token = self._demag_token()
|
|
29
|
+
if (
|
|
30
|
+
self._demag_aux_cache_token == token
|
|
31
|
+
and self._demag_phi_cache is not None
|
|
32
|
+
and self._demag_rho_cache is not None
|
|
33
|
+
and self._demag_volumes_cache is not None
|
|
34
|
+
):
|
|
35
|
+
self._record_active_subfield_array_timing(
|
|
36
|
+
"demag_auxiliary:cache_hit",
|
|
37
|
+
time.perf_counter() - cache_started,
|
|
38
|
+
)
|
|
39
|
+
return self._demag_phi_cache, self._demag_rho_cache, self._demag_volumes_cache
|
|
40
|
+
|
|
41
|
+
with self._record_active_subfield_array_timing_block(
|
|
42
|
+
"demag_auxiliary:mesh_inputs",
|
|
43
|
+
):
|
|
44
|
+
points = self._mesh_points()
|
|
45
|
+
simplices = np.asarray(self._require_mesh().simplices, dtype=int)
|
|
46
|
+
if simplices.size == 0:
|
|
47
|
+
phi = np.zeros(len(points), dtype=float)
|
|
48
|
+
rho = np.zeros(len(points), dtype=float)
|
|
49
|
+
volumes = np.zeros(0, dtype=float)
|
|
50
|
+
else:
|
|
51
|
+
if simplices.ndim != 2 or simplices.shape[1] != 4:
|
|
52
|
+
raise NotImplementedError("The demag MVP currently supports tetrahedral 3D meshes.")
|
|
53
|
+
with self._record_active_subfield_array_timing_block(
|
|
54
|
+
"demag_auxiliary:boundary_faces",
|
|
55
|
+
):
|
|
56
|
+
boundary_faces = self._boundary_faces_for_demag_mesh(points, simplices)
|
|
57
|
+
if not boundary_faces:
|
|
58
|
+
phi = np.zeros(len(points), dtype=float)
|
|
59
|
+
rho = np.zeros(len(points), dtype=float)
|
|
60
|
+
volumes = np.zeros(len(simplices), dtype=float)
|
|
61
|
+
else:
|
|
62
|
+
with self._record_active_subfield_array_timing_block(
|
|
63
|
+
"demag_auxiliary:material_inputs",
|
|
64
|
+
):
|
|
65
|
+
m = np.asarray(self._fields["m"], dtype=float)
|
|
66
|
+
regions = list(self._require_mesh().regions or [1] * len(simplices))
|
|
67
|
+
ms_values = self._simplex_material_ms_values(regions)
|
|
68
|
+
volume_charge_scales = self._simplex_volume_charge_scales(regions)
|
|
69
|
+
with self._record_active_subfield_array_timing_block(
|
|
70
|
+
"demag_auxiliary:fem_bem_total",
|
|
71
|
+
):
|
|
72
|
+
phi, rho, volumes = self._fem_bem_auxiliary_at_nodes(
|
|
73
|
+
points,
|
|
74
|
+
simplices,
|
|
75
|
+
boundary_faces,
|
|
76
|
+
m,
|
|
77
|
+
ms_values,
|
|
78
|
+
volume_charge_scales,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self._demag_aux_cache_token = token
|
|
82
|
+
self._demag_phi_cache = phi
|
|
83
|
+
self._demag_rho_cache = rho
|
|
84
|
+
self._demag_volumes_cache = volumes
|
|
85
|
+
return phi, rho, volumes
|
|
86
|
+
|
|
87
|
+
def _fem_bem_auxiliary_at_nodes(
|
|
88
|
+
self,
|
|
89
|
+
points: np.ndarray,
|
|
90
|
+
simplices: np.ndarray,
|
|
91
|
+
boundary_faces: list[tuple[int, tuple[int, int, int]]],
|
|
92
|
+
m: np.ndarray,
|
|
93
|
+
ms_values: np.ndarray,
|
|
94
|
+
volume_charge_scales: np.ndarray,
|
|
95
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
96
|
+
with self._record_active_subfield_array_timing_block(
|
|
97
|
+
"demag_auxiliary:assemble_fem",
|
|
98
|
+
):
|
|
99
|
+
stiffness, divergence, volumes = self._assemble_demag_fem_system(
|
|
100
|
+
points,
|
|
101
|
+
simplices,
|
|
102
|
+
m,
|
|
103
|
+
ms_values,
|
|
104
|
+
volume_charge_scales,
|
|
105
|
+
)
|
|
106
|
+
with self._record_active_subfield_array_timing_block("demag_auxiliary:rho_box"):
|
|
107
|
+
rho = self._scalar_cofield_to_legacy_boxed_field(
|
|
108
|
+
divergence,
|
|
109
|
+
len(points),
|
|
110
|
+
)
|
|
111
|
+
with self._record_active_subfield_array_timing_block(
|
|
112
|
+
"demag_auxiliary:gauge_solve",
|
|
113
|
+
):
|
|
114
|
+
if _is_sparse_matrix(stiffness):
|
|
115
|
+
phi1 = _solve_sparse_gauge_fixed(
|
|
116
|
+
stiffness,
|
|
117
|
+
divergence,
|
|
118
|
+
record_timing=self._record_active_subfield_array_timing,
|
|
119
|
+
timing_prefix="demag_auxiliary:gauge_solve",
|
|
120
|
+
)
|
|
121
|
+
elif _selected_demag_linear_solver_backend(stiffness.shape[0]) == "scipy":
|
|
122
|
+
if self._demag_gauge_factorization_cache is None:
|
|
123
|
+
self._demag_gauge_factorization_cache = _factorize_gauge_fixed(stiffness)
|
|
124
|
+
augmented_rhs = np.concatenate([divergence, [0.0]])
|
|
125
|
+
phi1 = self._demag_gauge_factorization_cache.solve(augmented_rhs)[:-1]
|
|
126
|
+
else:
|
|
127
|
+
phi1 = _solve_gauge_fixed(
|
|
128
|
+
stiffness,
|
|
129
|
+
divergence,
|
|
130
|
+
record_timing=self._record_active_subfield_array_timing,
|
|
131
|
+
timing_prefix="demag_auxiliary:gauge_solve",
|
|
132
|
+
)
|
|
133
|
+
with self._record_active_subfield_array_timing_block(
|
|
134
|
+
"demag_auxiliary:lindholm_bem",
|
|
135
|
+
):
|
|
136
|
+
boundary_nodes, bem = self._lindholm_bem_for_demag_mesh(
|
|
137
|
+
points,
|
|
138
|
+
simplices,
|
|
139
|
+
boundary_faces,
|
|
140
|
+
)
|
|
141
|
+
phi2 = np.zeros(len(points), dtype=float)
|
|
142
|
+
phi2_boundary = np.zeros(len(boundary_nodes), dtype=float)
|
|
143
|
+
if len(boundary_nodes) > 0:
|
|
144
|
+
with self._record_active_subfield_array_timing_block(
|
|
145
|
+
"demag_auxiliary:bem_multiply",
|
|
146
|
+
):
|
|
147
|
+
phi2_boundary = bem @ phi1[boundary_nodes]
|
|
148
|
+
with self._record_active_subfield_array_timing_block(
|
|
149
|
+
"demag_auxiliary:dirichlet_extension",
|
|
150
|
+
):
|
|
151
|
+
phi2 = self._dirichlet_extension_from_boundary(
|
|
152
|
+
stiffness,
|
|
153
|
+
boundary_nodes,
|
|
154
|
+
phi2_boundary,
|
|
155
|
+
)
|
|
156
|
+
self.last_demag_solve_diagnostics = _demag_solve_diagnostics(
|
|
157
|
+
stiffness,
|
|
158
|
+
divergence,
|
|
159
|
+
boundary_nodes,
|
|
160
|
+
phi1,
|
|
161
|
+
phi2_boundary,
|
|
162
|
+
phi2,
|
|
163
|
+
phi1 + phi2,
|
|
164
|
+
)
|
|
165
|
+
if _demag_internal_trace_enabled():
|
|
166
|
+
self.last_demag_internal_vectors = {
|
|
167
|
+
"rho": np.array(rho, copy=True),
|
|
168
|
+
"phi1": np.array(phi1, copy=True),
|
|
169
|
+
"phi2": np.array(phi2, copy=True),
|
|
170
|
+
"phi": np.array(phi1 + phi2, copy=True),
|
|
171
|
+
}
|
|
172
|
+
return phi1 + phi2, rho, volumes
|
|
173
|
+
|
|
174
|
+
def _scalar_cofield_to_legacy_boxed_field(
|
|
175
|
+
self,
|
|
176
|
+
cofield: np.ndarray,
|
|
177
|
+
point_count: int,
|
|
178
|
+
) -> np.ndarray:
|
|
179
|
+
if cofield.size == 0:
|
|
180
|
+
return np.zeros(point_count, dtype=float)
|
|
181
|
+
|
|
182
|
+
nodal = np.zeros(point_count, dtype=float)
|
|
183
|
+
# Legacy Nmag's simulation unit system always boxes rho with the
|
|
184
|
+
# fixed 1 nm base volume, rather than the loaded mesh's unit_length.
|
|
185
|
+
nodal[: len(cofield)] = cofield / LEGACY_RHO_BOXED_VOLUME_M3
|
|
186
|
+
return nodal
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from ....demag import _mean_edge_length, _simplex_volumes
|
|
9
|
+
from ...support import MU0_OVER_4PI, _simulation_compatibility_binding
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SimulationDemagProbeMixin:
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
_demag_dipoles: tuple[np.ndarray, np.ndarray, float] | None
|
|
15
|
+
|
|
16
|
+
def __getattr__(self, name: str) -> Any: ...
|
|
17
|
+
|
|
18
|
+
def _cell_demag_from_potential(
|
|
19
|
+
self,
|
|
20
|
+
points: np.ndarray,
|
|
21
|
+
simplices: np.ndarray,
|
|
22
|
+
phi: np.ndarray,
|
|
23
|
+
) -> np.ndarray:
|
|
24
|
+
_, gradients_by_cell, _ = self._demag_fem_geometry_for_mesh(points, simplices)
|
|
25
|
+
return -np.einsum(
|
|
26
|
+
"ci,cij->cj",
|
|
27
|
+
phi[simplices],
|
|
28
|
+
gradients_by_cell,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def _build_demag_dipoles(self) -> tuple[np.ndarray, np.ndarray, float]:
|
|
32
|
+
if "m" not in self._fields:
|
|
33
|
+
raise RuntimeError("Magnetisation must be set before using demag fields.")
|
|
34
|
+
|
|
35
|
+
points = self._mesh_points()
|
|
36
|
+
mesh = self._require_mesh()
|
|
37
|
+
simplices = np.asarray(mesh.simplices, dtype=int)
|
|
38
|
+
if simplices.size == 0:
|
|
39
|
+
return np.empty((0, 3)), np.empty((0, 3)), 1.0
|
|
40
|
+
if simplices.ndim != 2 or simplices.shape[1] != 4:
|
|
41
|
+
raise NotImplementedError("The demag MVP currently supports tetrahedral 3D meshes.")
|
|
42
|
+
|
|
43
|
+
token = (id(mesh.raw_mesh), id(self._fields["m"]))
|
|
44
|
+
if self._demag_cache_token == token and self._demag_dipoles is not None:
|
|
45
|
+
return self._demag_dipoles
|
|
46
|
+
|
|
47
|
+
m = np.asarray(self._fields["m"], dtype=float)
|
|
48
|
+
centroids = np.mean(points[simplices], axis=1)
|
|
49
|
+
volumes = _simulation_compatibility_binding(
|
|
50
|
+
"_simplex_volumes",
|
|
51
|
+
_simplex_volumes,
|
|
52
|
+
)(points, simplices)
|
|
53
|
+
regions = list(mesh.regions or [1] * len(simplices))
|
|
54
|
+
ms_values = np.asarray(
|
|
55
|
+
[self._simplex_material_ms(region) for region in regions], dtype=float
|
|
56
|
+
)
|
|
57
|
+
cell_m = np.mean(m[simplices], axis=1)
|
|
58
|
+
dipole_moments = cell_m * (ms_values * volumes)[:, np.newaxis]
|
|
59
|
+
softening = 0.25 * _mean_edge_length(points, simplices)
|
|
60
|
+
self._demag_cache_token = token
|
|
61
|
+
self._demag_dipoles = (centroids, dipole_moments, softening)
|
|
62
|
+
return self._demag_dipoles
|
|
63
|
+
|
|
64
|
+
def _probe_demag_at(self, pos: Sequence[float]) -> np.ndarray:
|
|
65
|
+
centroids, dipole_moments, softening = self._build_demag_dipoles()
|
|
66
|
+
if len(centroids) == 0:
|
|
67
|
+
return np.zeros(3, dtype=float)
|
|
68
|
+
|
|
69
|
+
r = np.asarray(pos, dtype=float) - centroids
|
|
70
|
+
r2 = np.sum(r * r, axis=1) + softening * softening
|
|
71
|
+
r3 = r2**1.5
|
|
72
|
+
r5 = r2**2.5
|
|
73
|
+
mdotr = np.sum(dipole_moments * r, axis=1)
|
|
74
|
+
terms = (3.0 * r * mdotr[:, np.newaxis] / r5[:, np.newaxis]) - (
|
|
75
|
+
dipole_moments / r3[:, np.newaxis]
|
|
76
|
+
)
|
|
77
|
+
return MU0_OVER_4PI * np.sum(terms, axis=0)
|