llg3d 1.4.0__tar.gz → 2.0.0__tar.gz
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.
- {llg3d-1.4.0/src/llg3d.egg-info → llg3d-2.0.0}/PKG-INFO +13 -20
- {llg3d-1.4.0 → llg3d-2.0.0}/README.md +1 -1
- {llg3d-1.4.0 → llg3d-2.0.0}/pyproject.toml +32 -15
- llg3d-2.0.0/src/llg3d/__init__.py +4 -0
- llg3d-2.0.0/src/llg3d/__main__.py +6 -0
- llg3d-2.0.0/src/llg3d/element.py +128 -0
- llg3d-2.0.0/src/llg3d/grid.py +126 -0
- llg3d-2.0.0/src/llg3d/main.py +66 -0
- llg3d-2.0.0/src/llg3d/output.py +108 -0
- llg3d-2.0.0/src/llg3d/parameters.py +75 -0
- llg3d-2.0.0/src/llg3d/post/plot_results.py +65 -0
- {llg3d-1.4.0 → llg3d-2.0.0}/src/llg3d/post/temperature.py +0 -1
- llg3d-2.0.0/src/llg3d/simulation.py +104 -0
- llg3d-2.0.0/src/llg3d/solver/__init__.py +45 -0
- llg3d-2.0.0/src/llg3d/solver/jax.py +383 -0
- llg3d-2.0.0/src/llg3d/solver/mpi.py +449 -0
- llg3d-2.0.0/src/llg3d/solver/numpy.py +210 -0
- llg3d-2.0.0/src/llg3d/solver/opencl.py +329 -0
- llg3d-2.0.0/src/llg3d/solver/solver.py +93 -0
- {llg3d-1.4.0 → llg3d-2.0.0/src/llg3d.egg-info}/PKG-INFO +13 -20
- llg3d-2.0.0/src/llg3d.egg-info/SOURCES.txt +32 -0
- llg3d-2.0.0/src/llg3d.egg-info/entry_points.txt +4 -0
- llg3d-2.0.0/src/llg3d.egg-info/requires.txt +18 -0
- llg3d-2.0.0/tests/test_element.py +53 -0
- llg3d-2.0.0/tests/test_grid.py +62 -0
- llg3d-2.0.0/tests/test_main.py +50 -0
- llg3d-2.0.0/tests/test_parameters.py +39 -0
- llg3d-1.4.0/src/llg3d/__init__.py +0 -1
- llg3d-1.4.0/src/llg3d/llg3d.py +0 -742
- llg3d-1.4.0/src/llg3d/llg3d_seq.py +0 -447
- llg3d-1.4.0/src/llg3d.egg-info/SOURCES.txt +0 -18
- llg3d-1.4.0/src/llg3d.egg-info/entry_points.txt +0 -3
- llg3d-1.4.0/src/llg3d.egg-info/requires.txt +0 -21
- llg3d-1.4.0/tests/test_llg3d.py +0 -339
- llg3d-1.4.0/tests/test_llg3d_seq.py +0 -50
- {llg3d-1.4.0 → llg3d-2.0.0}/AUTHORS +0 -0
- {llg3d-1.4.0 → llg3d-2.0.0}/LICENSE +0 -0
- {llg3d-1.4.0 → llg3d-2.0.0}/setup.cfg +0 -0
- {llg3d-1.4.0 → llg3d-2.0.0}/src/llg3d/post/__init__.py +0 -0
- {llg3d-1.4.0 → llg3d-2.0.0}/src/llg3d/post/process.py +0 -0
- {llg3d-1.4.0 → llg3d-2.0.0}/src/llg3d.egg-info/dependency_links.txt +0 -0
- {llg3d-1.4.0 → llg3d-2.0.0}/src/llg3d.egg-info/top_level.txt +0 -0
|
@@ -1,35 +1,28 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: llg3d
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Solveur pour l'équation de Landau-Lifshitz-Gilbert stochastique en 3D
|
|
5
5
|
Author-email: Clémentine Courtès <clementine.courtes@math.unistra.fr>, Matthieu Boileau <matthieu.boileau@math.unistra.fr>
|
|
6
6
|
Project-URL: Homepage, https://gitlab.math.unistra.fr/llg3d/llg3d
|
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
|
8
8
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
License-File: AUTHORS
|
|
14
14
|
Requires-Dist: numpy
|
|
15
|
-
Requires-Dist: mpi4py
|
|
16
15
|
Requires-Dist: matplotlib
|
|
17
16
|
Requires-Dist: scipy
|
|
18
|
-
Provides-Extra:
|
|
19
|
-
Requires-Dist:
|
|
20
|
-
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
|
|
27
|
-
Requires-Dist: sphinx-copybutton; extra == "doc"
|
|
28
|
-
Requires-Dist: sphinx-autobuild; extra == "doc"
|
|
29
|
-
Requires-Dist: sphinx-prompt; extra == "doc"
|
|
30
|
-
Requires-Dist: sphinx-last-updated-by-git; extra == "doc"
|
|
31
|
-
Requires-Dist: sphinxcontrib-programoutput; extra == "doc"
|
|
32
|
-
Requires-Dist: sphinxcontrib-bibtex; extra == "doc"
|
|
17
|
+
Provides-Extra: mpi
|
|
18
|
+
Requires-Dist: mpi4py; extra == "mpi"
|
|
19
|
+
Provides-Extra: opencl
|
|
20
|
+
Requires-Dist: pyopencl; extra == "opencl"
|
|
21
|
+
Requires-Dist: mako; extra == "opencl"
|
|
22
|
+
Provides-Extra: jax
|
|
23
|
+
Requires-Dist: jax[cuda]; sys_platform != "darwin" and extra == "jax"
|
|
24
|
+
Requires-Dist: jax[cpu]; sys_platform == "darwin" and extra == "jax"
|
|
25
|
+
Dynamic: license-file
|
|
33
26
|
|
|
34
27
|
# LLG3D: A solver for the stochastic Landau-Lifshitz-Gilbert equation in 3D
|
|
35
28
|
|
|
@@ -39,6 +32,6 @@ Requires-Dist: sphinxcontrib-bibtex; extra == "doc"
|
|
|
39
32
|
[](https://llg3d.pages.math.unistra.fr/llg3d/)
|
|
40
33
|
[](https://archive.softwareheritage.org/browse/origin/?origin_url=https://gitlab.math.unistra.fr/llg3d/llg3d)
|
|
41
34
|
|
|
42
|
-
LLG3D is written in Python and
|
|
35
|
+
LLG3D is written in Python and may run in parallel using MPI, OpenCL or JAX.
|
|
43
36
|
|
|
44
37
|
See the [documentation](https://llg3d.pages.math.unistra.fr/llg3d/).
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
[](https://llg3d.pages.math.unistra.fr/llg3d/)
|
|
7
7
|
[](https://archive.softwareheritage.org/browse/origin/?origin_url=https://gitlab.math.unistra.fr/llg3d/llg3d)
|
|
8
8
|
|
|
9
|
-
LLG3D is written in Python and
|
|
9
|
+
LLG3D is written in Python and may run in parallel using MPI, OpenCL or JAX.
|
|
10
10
|
|
|
11
11
|
See the [documentation](https://llg3d.pages.math.unistra.fr/llg3d/).
|
|
@@ -14,8 +14,8 @@ classifiers = [
|
|
|
14
14
|
"License :: OSI Approved :: MIT License",
|
|
15
15
|
"Operating System :: OS Independent",
|
|
16
16
|
]
|
|
17
|
-
requires-python = ">=3.
|
|
18
|
-
dependencies = ["numpy", "
|
|
17
|
+
requires-python = ">=3.9"
|
|
18
|
+
dependencies = ["numpy", "matplotlib", "scipy"]
|
|
19
19
|
dynamic = ["version"]
|
|
20
20
|
|
|
21
21
|
|
|
@@ -27,22 +27,16 @@ content-type = "text/markdown"
|
|
|
27
27
|
Homepage = "https://gitlab.math.unistra.fr/llg3d/llg3d"
|
|
28
28
|
|
|
29
29
|
[project.scripts]
|
|
30
|
-
llg3d = "llg3d.
|
|
30
|
+
llg3d = "llg3d.main:main"
|
|
31
31
|
"llg3d.post" = "llg3d.post.temperature:main"
|
|
32
|
+
"llg3d.plot_results" = "llg3d.post.plot_results:main"
|
|
32
33
|
|
|
33
34
|
[project.optional-dependencies]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"nbsphinx",
|
|
40
|
-
"sphinx-copybutton",
|
|
41
|
-
"sphinx-autobuild",
|
|
42
|
-
"sphinx-prompt",
|
|
43
|
-
"sphinx-last-updated-by-git",
|
|
44
|
-
"sphinxcontrib-programoutput",
|
|
45
|
-
"sphinxcontrib-bibtex",
|
|
35
|
+
mpi = ["mpi4py"]
|
|
36
|
+
opencl = ["pyopencl", "mako"]
|
|
37
|
+
jax = [
|
|
38
|
+
"jax[cuda] ; sys_platform != 'darwin'",
|
|
39
|
+
"jax[cpu] ; sys_platform == 'darwin'",
|
|
46
40
|
]
|
|
47
41
|
|
|
48
42
|
[tool.setuptools]
|
|
@@ -61,3 +55,26 @@ attr = "llg3d.__version__"
|
|
|
61
55
|
[tool.coverage.run]
|
|
62
56
|
parallel = true
|
|
63
57
|
source = ["src/"]
|
|
58
|
+
|
|
59
|
+
[dependency-groups]
|
|
60
|
+
dev = [
|
|
61
|
+
{ include-group = "lint" },
|
|
62
|
+
{ include-group = "test" },
|
|
63
|
+
{ include-group = "doc" },
|
|
64
|
+
]
|
|
65
|
+
test = ["pytest", "pytest-cov", "pytest-mpi"]
|
|
66
|
+
lint = ["ruff"]
|
|
67
|
+
doc = [
|
|
68
|
+
"Sphinx >= 7.2.2",
|
|
69
|
+
"sphinx-design",
|
|
70
|
+
"myst-parser",
|
|
71
|
+
"furo",
|
|
72
|
+
"nbsphinx",
|
|
73
|
+
"sphinx-copybutton",
|
|
74
|
+
"sphinx-autobuild",
|
|
75
|
+
"sphinx-prompt",
|
|
76
|
+
"sphinx-last-updated-by-git",
|
|
77
|
+
"sphinxcontrib-programoutput",
|
|
78
|
+
"sphinxcontrib-bibtex",
|
|
79
|
+
"ipython",
|
|
80
|
+
]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Module containing the definition of the chemical elements
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from .grid import Grid
|
|
8
|
+
|
|
9
|
+
k_B = 1.38e-23 #: Boltzmann constant :math:`[J.K^{-1}]`
|
|
10
|
+
mu_0 = 4 * np.pi * 1.0e-7 #: Vacuum permeability :math:`[H.m^{-1}]`
|
|
11
|
+
gamma = 1.76e11 #: Gyromagnetic ratio :math:`[rad.s^{-1}.T^{-1}]`
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Element:
|
|
15
|
+
"""Abstract class for chemical elements"""
|
|
16
|
+
|
|
17
|
+
A = 0.0
|
|
18
|
+
K = 0.0
|
|
19
|
+
lambda_G = 0.0
|
|
20
|
+
M_s = 0.0
|
|
21
|
+
a_eff = 0.0
|
|
22
|
+
anisotropy: str = ""
|
|
23
|
+
|
|
24
|
+
def __init__(self, T: float, H_ext: float, g: Grid, dt: float) -> None:
|
|
25
|
+
"""Initializes the Element class with given parameters
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
T: Temperature in Kelvin
|
|
29
|
+
H_ext: External magnetic field strength
|
|
30
|
+
g: Grid object representing the simulation grid
|
|
31
|
+
dt: Time step for the simulation
|
|
32
|
+
"""
|
|
33
|
+
self.H_ext = H_ext
|
|
34
|
+
self.g = g
|
|
35
|
+
self.dt = dt
|
|
36
|
+
self.gamma_0 = gamma * mu_0 #: Rescaled gyromagnetic ratio [mA^-1.s^-1]
|
|
37
|
+
|
|
38
|
+
# --- Characteristic Scales ---
|
|
39
|
+
self.coeff_1 = self.gamma_0 * 2.0 * self.A / (mu_0 * self.M_s)
|
|
40
|
+
self.coeff_2 = self.gamma_0 * 2.0 * self.K / (mu_0 * self.M_s)
|
|
41
|
+
self.coeff_3 = self.gamma_0 * H_ext
|
|
42
|
+
|
|
43
|
+
# corresponds to the temperature actually put into the random field
|
|
44
|
+
T_simu = T * self.g.dx / self.a_eff
|
|
45
|
+
# calculation of the random field related to temperature
|
|
46
|
+
# (we only take the volume over one mesh)
|
|
47
|
+
h_alea = np.sqrt(
|
|
48
|
+
2 * self.lambda_G * k_B / (self.gamma_0 * mu_0 * self.M_s * self.g.dV)
|
|
49
|
+
)
|
|
50
|
+
H_alea = h_alea * np.sqrt(T_simu) * np.sqrt(1.0 / self.dt)
|
|
51
|
+
self.coeff_4 = H_alea * self.gamma_0
|
|
52
|
+
|
|
53
|
+
def get_CFL(self) -> float:
|
|
54
|
+
"""
|
|
55
|
+
Returns the value of the CFL
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
float: The CFL value
|
|
59
|
+
"""
|
|
60
|
+
return self.dt * self.coeff_1 / self.g.dx**2
|
|
61
|
+
|
|
62
|
+
def to_dict(self) -> dict:
|
|
63
|
+
"""
|
|
64
|
+
Export element parameters to a dictionary for JAX JIT compatibility
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Dictionary containing element parameters needed for computations
|
|
68
|
+
"""
|
|
69
|
+
# Map anisotropy string to integer for JIT compatibility
|
|
70
|
+
aniso_map = {"uniaxial": 0, "cubic": 1}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"coeff_1": self.coeff_1,
|
|
74
|
+
"coeff_2": self.coeff_2,
|
|
75
|
+
"coeff_3": self.coeff_3,
|
|
76
|
+
"coeff_4": self.coeff_4,
|
|
77
|
+
"lambda_G": self.lambda_G,
|
|
78
|
+
"anisotropy": aniso_map[self.anisotropy],
|
|
79
|
+
"gamma_0": self.gamma_0,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Cobalt(Element):
|
|
84
|
+
A = 30.0e-12 #: Exchange constant :math:`[J.m^{-1}]`
|
|
85
|
+
K = 520.0e3 #: Anisotropy constant :math:`[J.m^{-3}]`
|
|
86
|
+
lambda_G = 0.5 #: Damping parameter :math:`[1]`
|
|
87
|
+
M_s = 1400.0e3 #: Saturation magnetization :math:`[A.m^{-1}]`
|
|
88
|
+
a_eff = 0.25e-9 #: Effective lattice constant :math:`[m]`
|
|
89
|
+
anisotropy = "uniaxial" #: Type of anisotropy (e.g., "uniaxial", "cubic")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Iron(Element):
|
|
93
|
+
A = 21.0e-12 #: Exchange constant :math:`[J.m^{-1}]`
|
|
94
|
+
K = 48.0e3 #: Anisotropy constant :math:`[J.m^{-3}]`
|
|
95
|
+
lambda_G = 0.5 #: Damping parameter :math:`[1]`
|
|
96
|
+
M_s = 1700.0e3 #: Saturation magnetization :math:`[A.m^{-1}]`
|
|
97
|
+
a_eff = 0.286e-9 #: Effective lattice constant :math:`[m]`
|
|
98
|
+
anisotropy = "cubic" #: Type of anisotropy (e.g., "uniaxial", "cubic")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Nickel(Element):
|
|
102
|
+
A = 9.0e-12 #: Exchange constant :math:`[J.m^{-1}]`
|
|
103
|
+
K = -5.7e3 #: Anisotropy constant :math:`[J.m^{-3}]`
|
|
104
|
+
lambda_G = 0.5 #: Damping parameter :math:`[1]`
|
|
105
|
+
M_s = 490.0e3 #: Saturation magnetization :math:`[A.m^{-1}]`
|
|
106
|
+
a_eff = 0.345e-9 #: Effective lattice constant :math:`[m]`
|
|
107
|
+
anisotropy = "cubic" #: Type of anisotropy (e.g., "uniaxial", "cubic")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_element_class(element_name: str | type[Element]) -> type[Element]:
|
|
111
|
+
"""
|
|
112
|
+
Get the class of the chemical element by its name.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
element_name: The name of the element or its class
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
The class of the element
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: If the element is not found
|
|
122
|
+
"""
|
|
123
|
+
if isinstance(element_name, type):
|
|
124
|
+
return element_name
|
|
125
|
+
for cls in Element.__subclasses__():
|
|
126
|
+
if cls.__name__ == element_name:
|
|
127
|
+
return cls
|
|
128
|
+
raise ValueError(f"Element '{element_name}' not found in {__file__}.")
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module to define the grid for the simulation
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from .solver import rank, size
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Grid:
|
|
13
|
+
"""Stores grid data"""
|
|
14
|
+
|
|
15
|
+
# Parameter values correspond to the global grid
|
|
16
|
+
Jx: int #: number of points in x direction
|
|
17
|
+
Jy: int #: number of points in y direction
|
|
18
|
+
Jz: int #: number of points in z direction
|
|
19
|
+
dx: float #: grid spacing in x direction
|
|
20
|
+
|
|
21
|
+
def __post_init__(self) -> None:
|
|
22
|
+
"""Compute grid characteristics"""
|
|
23
|
+
self.dy = self.dz = self.dx # Setting dx = dy = dz
|
|
24
|
+
self.Lx = (self.Jx - 1) * self.dx
|
|
25
|
+
self.Ly = (self.Jy - 1) * self.dy
|
|
26
|
+
self.Lz = (self.Jz - 1) * self.dz
|
|
27
|
+
# shape of the local array to the process
|
|
28
|
+
self.dims = self.Jx // size, self.Jy, self.Jz
|
|
29
|
+
# elemental volume of a cell
|
|
30
|
+
self.dV = self.dx * self.dy * self.dz
|
|
31
|
+
# total volume
|
|
32
|
+
self.V = self.Lx * self.Ly * self.Lz
|
|
33
|
+
# total number of points
|
|
34
|
+
self.ntot = self.Jx * self.Jy * self.Jz
|
|
35
|
+
self.ncell = (self.Jx - 1) * (self.Jy - 1) * (self.Jz - 1)
|
|
36
|
+
|
|
37
|
+
def __str__(self) -> str:
|
|
38
|
+
"""Print grid information"""
|
|
39
|
+
|
|
40
|
+
header = "\t\t".join(("x", "y", "z"))
|
|
41
|
+
s = f"""\
|
|
42
|
+
\t{header}
|
|
43
|
+
J\t{self.Jx}\t\t{self.Jy}\t\t{self.Jz}
|
|
44
|
+
L\t{self.Lx:.08e}\t{self.Ly:.08e}\t{self.Lz:.08e}
|
|
45
|
+
d\t{self.dx:.08e}\t{self.dy:.08e}\t{self.dz:.08e}
|
|
46
|
+
|
|
47
|
+
dV = {self.dV:.08e}
|
|
48
|
+
V = {self.V:.08e}
|
|
49
|
+
ntot = {self.ntot:d}
|
|
50
|
+
ncell = {self.ncell:d}
|
|
51
|
+
"""
|
|
52
|
+
return s
|
|
53
|
+
|
|
54
|
+
def get_filename(
|
|
55
|
+
self, T: float, name: str = "m1_mean", extension: str = "txt"
|
|
56
|
+
) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Returns the output file name for a given temperature
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
T: temperature
|
|
62
|
+
name: file name
|
|
63
|
+
extension: file extension
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
file name
|
|
67
|
+
|
|
68
|
+
>>> g = Grid(Jx=300, Jy=21, Jz=21, dx=1.e-9)
|
|
69
|
+
>>> g.get_filename(1100)
|
|
70
|
+
'm1_mean_T1100_300x21x21.txt'
|
|
71
|
+
"""
|
|
72
|
+
suffix = f"T{int(T)}_{self.Jx}x{self.Jy}x{self.Jz}"
|
|
73
|
+
return f"{name}_{suffix}.{extension}"
|
|
74
|
+
|
|
75
|
+
def get_mesh(
|
|
76
|
+
self, local: bool = True, dtype=np.float64
|
|
77
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
78
|
+
"""
|
|
79
|
+
Returns a meshgrid of the coordinates.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
local: if True, returns the local coordinates,
|
|
83
|
+
otherwise the global coordinates
|
|
84
|
+
dtype: data type of the coordinates)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
tuple of 3D arrays with the coordinates
|
|
88
|
+
"""
|
|
89
|
+
x_global = np.linspace(0, self.Lx, self.Jx, dtype=dtype) # global coordinates
|
|
90
|
+
y = np.linspace(0, self.Ly, self.Jy, dtype=dtype)
|
|
91
|
+
z = np.linspace(0, self.Lz, self.Jz, dtype=dtype)
|
|
92
|
+
if local:
|
|
93
|
+
x_local = np.split(x_global, size)[rank] # local coordinates
|
|
94
|
+
return np.meshgrid(x_local, y, z, indexing="ij")
|
|
95
|
+
else:
|
|
96
|
+
return np.meshgrid(x_global, y, z, indexing="ij")
|
|
97
|
+
|
|
98
|
+
def to_dict(self) -> dict:
|
|
99
|
+
"""
|
|
100
|
+
Export grid parameters to a dictionary for JAX JIT compatibility
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Dictionary containing grid parameters needed for computations
|
|
104
|
+
"""
|
|
105
|
+
return {
|
|
106
|
+
"dx": self.dx,
|
|
107
|
+
"dy": self.dy,
|
|
108
|
+
"dz": self.dz,
|
|
109
|
+
"Jx": self.Jx,
|
|
110
|
+
"Jy": self.Jy,
|
|
111
|
+
"Jz": self.Jz,
|
|
112
|
+
"dV": self.dV,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def get_laplacian_coeff(self) -> tuple[float, float, float, float]:
|
|
116
|
+
"""
|
|
117
|
+
Returns the coefficients for the laplacian computation
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Tuple of coefficients (dx2_inv, dy2_inv, dz2_inv, center_coeff)
|
|
121
|
+
"""
|
|
122
|
+
dx2_inv = 1 / self.dx**2
|
|
123
|
+
dy2_inv = 1 / self.dy**2
|
|
124
|
+
dz2_inv = 1 / self.dz**2
|
|
125
|
+
center_coeff = -2 * (dx2_inv + dy2_inv + dz2_inv)
|
|
126
|
+
return dx2_inv, dy2_inv, dz2_inv, center_coeff
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Define a CLI for the llg3d package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from . import rank, size, LIB_AVAILABLE
|
|
9
|
+
from .parameters import parameters, get_parameter_list
|
|
10
|
+
from .simulation import Simulation
|
|
11
|
+
|
|
12
|
+
if LIB_AVAILABLE["mpi4py"]:
|
|
13
|
+
# Use the MPI version of the ArgumentParser
|
|
14
|
+
from .solver.mpi import ArgumentParser
|
|
15
|
+
else:
|
|
16
|
+
# Use the original version of the ArgumentParser
|
|
17
|
+
from argparse import ArgumentParser
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse_args(args: list[str] | None) -> argparse.Namespace:
|
|
21
|
+
"""
|
|
22
|
+
Argument parser for llg3d.
|
|
23
|
+
Automatically adds arguments from the parameter dictionary.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
argparse.Namespace: Parsed arguments
|
|
27
|
+
"""
|
|
28
|
+
parser = ArgumentParser(
|
|
29
|
+
description=__doc__,
|
|
30
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if size > 1:
|
|
34
|
+
parameters["solver"]["default"] = "mpi"
|
|
35
|
+
|
|
36
|
+
# Automatically add arguments from the parameter dictionary
|
|
37
|
+
for name, parameter in parameters.items():
|
|
38
|
+
if "action" not in parameter:
|
|
39
|
+
parameter["type"] = type(parameter["default"])
|
|
40
|
+
parser.add_argument(f"--{name}", **parameter)
|
|
41
|
+
|
|
42
|
+
return parser.parse_args(args)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main(arg_list: list[str] = None):
|
|
46
|
+
"""
|
|
47
|
+
Evaluates the command line and runs the simulation
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
args = parse_args(arg_list)
|
|
51
|
+
|
|
52
|
+
if size > 1 and args.solver != "mpi":
|
|
53
|
+
raise ValueError(f"Solver method {args.solver} is not compatible with MPI.")
|
|
54
|
+
if args.solver == "mpi" and not LIB_AVAILABLE["mpi4py"]:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"The MPI solver method requires to install the mpi4py package, "
|
|
57
|
+
"for example using pip: pip install mpi4py"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if rank == 0:
|
|
61
|
+
# Display parameters as a list
|
|
62
|
+
print(get_parameter_list(vars(args)))
|
|
63
|
+
|
|
64
|
+
simulation = Simulation(vars(args))
|
|
65
|
+
simulation.run()
|
|
66
|
+
simulation.save()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for LLG3D
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Iterable, TextIO
|
|
8
|
+
|
|
9
|
+
from .solver import rank
|
|
10
|
+
from .grid import Grid
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def progress_bar(it: Iterable, prefix: str = "", size: int = 60, out: TextIO = sys.stdout):
|
|
14
|
+
"""
|
|
15
|
+
Displays a progress bar
|
|
16
|
+
|
|
17
|
+
(Source: https://stackoverflow.com/a/34482761/16593179)
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
it: Iterable object to iterate over
|
|
21
|
+
prefix: Prefix string for the progress bar
|
|
22
|
+
size: Size of the progress bar (number of characters)
|
|
23
|
+
out: Output stream (default is sys.stdout)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
count = len(it)
|
|
27
|
+
|
|
28
|
+
def show(j):
|
|
29
|
+
x = int(size * j / count)
|
|
30
|
+
if rank == 0:
|
|
31
|
+
print(
|
|
32
|
+
f"{prefix}[{u'█'*x}{('.'*(size-x))}] {j}/{count}",
|
|
33
|
+
end="\r",
|
|
34
|
+
file=out,
|
|
35
|
+
flush=True,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
show(0)
|
|
39
|
+
for i, item in enumerate(it):
|
|
40
|
+
yield item
|
|
41
|
+
# To avoid slowing down the computation, we do not display at every iteration
|
|
42
|
+
if i % 5 == 0:
|
|
43
|
+
show(i + 1)
|
|
44
|
+
show(i + 1)
|
|
45
|
+
if rank == 0:
|
|
46
|
+
print("\n", flush=True, file=out)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def write_json(json_file: str, run: dict):
|
|
50
|
+
"""
|
|
51
|
+
Writes the run dictionary to a JSON file
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
json_file: Name of the JSON file
|
|
55
|
+
run: Dictionary containing the run information
|
|
56
|
+
"""
|
|
57
|
+
with open(json_file, "w") as f:
|
|
58
|
+
json.dump(run, f, indent=4)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_output_files(g: Grid, T: float, n_mean: int, n_profile: int) -> tuple:
|
|
62
|
+
"""
|
|
63
|
+
Open files and list them
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
g: Grid object
|
|
67
|
+
T: temperature
|
|
68
|
+
n_mean: Number of iterations for integral output
|
|
69
|
+
n_profile: Number of iterations for profile output
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
- a file handler for storing m space integral over time
|
|
73
|
+
- a file handler for storing x-profiles of m_i
|
|
74
|
+
- a list of output filenames
|
|
75
|
+
"""
|
|
76
|
+
f_mean = None
|
|
77
|
+
f_profiles = None
|
|
78
|
+
output_filenames = []
|
|
79
|
+
if n_mean != 0:
|
|
80
|
+
output_filenames.append(g.get_filename(T, extension="txt"))
|
|
81
|
+
if n_profile != 0:
|
|
82
|
+
output_filenames.extend(
|
|
83
|
+
[g.get_filename(T, name=f"m{i + 1}", extension="npy") for i in range(3)]
|
|
84
|
+
)
|
|
85
|
+
if rank == 0:
|
|
86
|
+
if n_mean != 0:
|
|
87
|
+
f_mean = open(output_filenames[0], "w") # integral of m1
|
|
88
|
+
if n_profile != 0:
|
|
89
|
+
f_profiles = [
|
|
90
|
+
open(output_filename, "wb") for output_filename in output_filenames[1:]
|
|
91
|
+
] # x profiles of m_i
|
|
92
|
+
|
|
93
|
+
return f_mean, f_profiles, output_filenames
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def close_output_files(f_mean: TextIO, f_profiles: list[TextIO] = None):
|
|
97
|
+
"""
|
|
98
|
+
Close all output files
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
f_mean: file handler for storing m space integral over time
|
|
102
|
+
f_profiles: file handlers for storing x-profiles of m_i
|
|
103
|
+
"""
|
|
104
|
+
if f_mean is not None:
|
|
105
|
+
f_mean.close()
|
|
106
|
+
if f_profiles is not None:
|
|
107
|
+
for f_profile in f_profiles:
|
|
108
|
+
f_profile.close()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Parameters for the LLG3D simulation."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
# Parameters: default value and description
|
|
6
|
+
Parameter = dict[str, str | int | float | bool] #: Type for a parameter
|
|
7
|
+
|
|
8
|
+
parameters: dict[str, Parameter] = {
|
|
9
|
+
"element": {
|
|
10
|
+
"help": "Chemical element of the sample",
|
|
11
|
+
"default": "Cobalt",
|
|
12
|
+
"choices": ["Cobalt", "Iron", "Nickel"],
|
|
13
|
+
},
|
|
14
|
+
"N": {"help": "Number of time iterations", "default": 5000},
|
|
15
|
+
"dt": {"help": "Time step", "default": 1.0e-14},
|
|
16
|
+
"Jx": {"help": "Number of points in x", "default": 300},
|
|
17
|
+
"Jy": {"help": "Number of points in y", "default": 21},
|
|
18
|
+
"Jz": {"help": "Number of points in z", "default": 21},
|
|
19
|
+
"dx": {"help": "Step in x", "default": 1.0e-9},
|
|
20
|
+
"T": {"help": "Temperature (K)", "default": 0.0},
|
|
21
|
+
"H_ext": {"help": "External field (A/m)", "default": 0.0 / (4 * np.pi * 1.0e-7)},
|
|
22
|
+
"start_averaging": {"help": "Start index of time average", "default": 4000},
|
|
23
|
+
"n_mean": {
|
|
24
|
+
"help": "Spatial average frequency (number of iterations)",
|
|
25
|
+
"default": 1,
|
|
26
|
+
},
|
|
27
|
+
"n_profile": {
|
|
28
|
+
"help": "x-profile save frequency (number of iterations)",
|
|
29
|
+
"default": 0,
|
|
30
|
+
},
|
|
31
|
+
"solver": {
|
|
32
|
+
"help": "Solver to use for the simulation",
|
|
33
|
+
"default": "numpy",
|
|
34
|
+
"choices": ["opencl", "mpi", "numpy", "jax"],
|
|
35
|
+
},
|
|
36
|
+
"precision": {
|
|
37
|
+
"help": "Precision of the simulation (single or double)",
|
|
38
|
+
"default": "double",
|
|
39
|
+
"choices": ["single", "double"],
|
|
40
|
+
},
|
|
41
|
+
"blocking": {
|
|
42
|
+
"help": "Use blocking communications",
|
|
43
|
+
"default": False,
|
|
44
|
+
"action": "store_true",
|
|
45
|
+
},
|
|
46
|
+
"seed": {
|
|
47
|
+
"help": "Random seed for temperature fluctuations",
|
|
48
|
+
"default": 12345,
|
|
49
|
+
"type": int,
|
|
50
|
+
},
|
|
51
|
+
"device": {
|
|
52
|
+
"help": "Device to use ('cpu', 'gpu', or 'auto')",
|
|
53
|
+
"default": "auto",
|
|
54
|
+
"type": str,
|
|
55
|
+
},
|
|
56
|
+
} #: simulation parameters
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_parameter_list(parameters: dict) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Returns parameter values as a string
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
d: Dictionary of parameters parsed by argparse
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
str: Formatted string of parameters
|
|
68
|
+
"""
|
|
69
|
+
width = max([len(name) for name in parameters])
|
|
70
|
+
s = ""
|
|
71
|
+
for name, value in parameters.items():
|
|
72
|
+
# the seprator is ":" for strings and "=" for others
|
|
73
|
+
sep = ":" if isinstance(value, str) else "="
|
|
74
|
+
s += "{0:<{1}} {2} {3}\n".format(name, width, sep, value)
|
|
75
|
+
return s
|