llg3d 1.4.1__tar.gz → 2.0.1__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.
Files changed (44) hide show
  1. {llg3d-1.4.1/src/llg3d.egg-info → llg3d-2.0.1}/PKG-INFO +14 -22
  2. {llg3d-1.4.1 → llg3d-2.0.1}/README.md +1 -1
  3. llg3d-2.0.1/pyproject.toml +113 -0
  4. llg3d-2.0.1/src/llg3d/__init__.py +6 -0
  5. llg3d-2.0.1/src/llg3d/__main__.py +6 -0
  6. llg3d-2.0.1/src/llg3d/element.py +134 -0
  7. llg3d-2.0.1/src/llg3d/grid.py +123 -0
  8. llg3d-2.0.1/src/llg3d/main.py +67 -0
  9. llg3d-2.0.1/src/llg3d/output.py +107 -0
  10. llg3d-2.0.1/src/llg3d/parameters.py +75 -0
  11. llg3d-2.0.1/src/llg3d/post/__init__.py +1 -0
  12. llg3d-2.0.1/src/llg3d/post/plot_results.py +61 -0
  13. {llg3d-1.4.1 → llg3d-2.0.1}/src/llg3d/post/process.py +18 -13
  14. {llg3d-1.4.1 → llg3d-2.0.1}/src/llg3d/post/temperature.py +6 -14
  15. llg3d-2.0.1/src/llg3d/simulation.py +95 -0
  16. llg3d-2.0.1/src/llg3d/solver/__init__.py +45 -0
  17. llg3d-2.0.1/src/llg3d/solver/jax.py +387 -0
  18. llg3d-2.0.1/src/llg3d/solver/mpi.py +450 -0
  19. llg3d-2.0.1/src/llg3d/solver/numpy.py +207 -0
  20. llg3d-2.0.1/src/llg3d/solver/opencl.py +330 -0
  21. llg3d-2.0.1/src/llg3d/solver/solver.py +89 -0
  22. {llg3d-1.4.1 → llg3d-2.0.1/src/llg3d.egg-info}/PKG-INFO +14 -22
  23. llg3d-2.0.1/src/llg3d.egg-info/SOURCES.txt +32 -0
  24. llg3d-2.0.1/src/llg3d.egg-info/entry_points.txt +4 -0
  25. llg3d-2.0.1/src/llg3d.egg-info/requires.txt +18 -0
  26. llg3d-2.0.1/tests/test_element.py +51 -0
  27. llg3d-2.0.1/tests/test_grid.py +65 -0
  28. llg3d-2.0.1/tests/test_main.py +53 -0
  29. llg3d-2.0.1/tests/test_parameters.py +39 -0
  30. llg3d-1.4.1/pyproject.toml +0 -64
  31. llg3d-1.4.1/src/llg3d/__init__.py +0 -1
  32. llg3d-1.4.1/src/llg3d/llg3d.py +0 -742
  33. llg3d-1.4.1/src/llg3d/llg3d_seq.py +0 -447
  34. llg3d-1.4.1/src/llg3d/post/__init__.py +0 -1
  35. llg3d-1.4.1/src/llg3d.egg-info/SOURCES.txt +0 -18
  36. llg3d-1.4.1/src/llg3d.egg-info/entry_points.txt +0 -3
  37. llg3d-1.4.1/src/llg3d.egg-info/requires.txt +0 -22
  38. llg3d-1.4.1/tests/test_llg3d.py +0 -339
  39. llg3d-1.4.1/tests/test_llg3d_seq.py +0 -50
  40. {llg3d-1.4.1 → llg3d-2.0.1}/AUTHORS +0 -0
  41. {llg3d-1.4.1 → llg3d-2.0.1}/LICENSE +0 -0
  42. {llg3d-1.4.1 → llg3d-2.0.1}/setup.cfg +0 -0
  43. {llg3d-1.4.1 → llg3d-2.0.1}/src/llg3d.egg-info/dependency_links.txt +0 -0
  44. {llg3d-1.4.1 → llg3d-2.0.1}/src/llg3d.egg-info/top_level.txt +0 -0
@@ -1,36 +1,28 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: llg3d
3
- Version: 1.4.1
4
- Summary: Solveur pour l'équation de Landau-Lifshitz-Gilbert stochastique en 3D
3
+ Version: 2.0.1
4
+ Summary: A solver for the stochastic Landau-Lifshitz-Gilbert equation in 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.6
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: test
19
- Requires-Dist: pytest; extra == "test"
20
- Requires-Dist: pytest-cov; extra == "test"
21
- Requires-Dist: pytest-mpi; extra == "test"
22
- Provides-Extra: doc
23
- Requires-Dist: Sphinx>=7.2.2; extra == "doc"
24
- Requires-Dist: myst-parser; extra == "doc"
25
- Requires-Dist: furo; extra == "doc"
26
- Requires-Dist: nbsphinx; extra == "doc"
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"
33
- Requires-Dist: ipython; 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
34
26
 
35
27
  # LLG3D: A solver for the stochastic Landau-Lifshitz-Gilbert equation in 3D
36
28
 
@@ -40,6 +32,6 @@ Requires-Dist: ipython; extra == "doc"
40
32
  [![Doc](https://img.shields.io/badge/doc-sphinx-blue)](https://llg3d.pages.math.unistra.fr/llg3d/)
41
33
  [![SWH](https://archive.softwareheritage.org/badge/origin/https://gitlab.math.unistra.fr/llg3d/llg3d/)](https://archive.softwareheritage.org/browse/origin/?origin_url=https://gitlab.math.unistra.fr/llg3d/llg3d)
42
34
 
43
- LLG3D is written in Python and utilizes the MPI library for parallelizing computations.
35
+ LLG3D is written in Python and may run in parallel using MPI, OpenCL or JAX.
44
36
 
45
37
  See the [documentation](https://llg3d.pages.math.unistra.fr/llg3d/).
@@ -6,6 +6,6 @@
6
6
  [![Doc](https://img.shields.io/badge/doc-sphinx-blue)](https://llg3d.pages.math.unistra.fr/llg3d/)
7
7
  [![SWH](https://archive.softwareheritage.org/badge/origin/https://gitlab.math.unistra.fr/llg3d/llg3d/)](https://archive.softwareheritage.org/browse/origin/?origin_url=https://gitlab.math.unistra.fr/llg3d/llg3d)
8
8
 
9
- LLG3D is written in Python and utilizes the MPI library for parallelizing computations.
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/).
@@ -0,0 +1,113 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.2"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "llg3d"
7
+ authors = [
8
+ { name = "Clémentine Courtès", email = "clementine.courtes@math.unistra.fr" },
9
+ { name = "Matthieu Boileau", email = "matthieu.boileau@math.unistra.fr" },
10
+ ]
11
+ description = "A solver for the stochastic Landau-Lifshitz-Gilbert equation in 3D"
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+ requires-python = ">=3.9"
18
+ dependencies = ["numpy", "matplotlib", "scipy"]
19
+ dynamic = ["version"]
20
+
21
+
22
+ [project.readme]
23
+ file = "README.md"
24
+ content-type = "text/markdown"
25
+
26
+ [project.urls]
27
+ Homepage = "https://gitlab.math.unistra.fr/llg3d/llg3d"
28
+
29
+ [project.scripts]
30
+ llg3d = "llg3d.main:main"
31
+ "llg3d.post" = "llg3d.post.temperature:main"
32
+ "llg3d.plot_results" = "llg3d.post.plot_results:main"
33
+
34
+ [project.optional-dependencies]
35
+ mpi = ["mpi4py"]
36
+ opencl = ["pyopencl", "mako"]
37
+ jax = [
38
+ "jax[cuda] ; sys_platform != 'darwin'",
39
+ "jax[cpu] ; sys_platform == 'darwin'",
40
+ ]
41
+
42
+ [tool.setuptools]
43
+ include-package-data = true
44
+
45
+ [tool.setuptools.package-dir]
46
+ "" = "src"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+ namespaces = false
51
+
52
+ [tool.setuptools.dynamic.version]
53
+ attr = "llg3d.__version__"
54
+
55
+ [tool.coverage.run]
56
+ parallel = true
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
+ ]
81
+
82
+ [tool.ruff]
83
+ # Set line length to 88 (compatible with Black)
84
+ line-length = 88
85
+ exclude = ["src/llg3d/process_tmp/"]
86
+
87
+ [tool.ruff.lint.per-file-ignores]
88
+ # Ignore all directories named `tests`.
89
+ "tests/**" = ["D"]
90
+
91
+ [tool.ruff.lint]
92
+ # Enable pydocstyle (D) rules along with existing rules
93
+ select = ["E", "F", "W", "D"]
94
+ extend-select = ["D213"]
95
+ ignore = [
96
+ "D107", # Missing docstring in __init__()
97
+ # Style preferences - ignore D212 to enable D213 (Google convention)
98
+ "D212", # Multi-line docstring summary should start at the first line (we prefer D213)
99
+ ]
100
+
101
+ [tool.ruff.lint.pydocstyle]
102
+ # Use Google convention for docstrings
103
+ convention = "google"
104
+
105
+ [tool.ruff.format]
106
+ # Use double quotes for strings (compatible with Black)
107
+ quote-style = "double"
108
+ # Use 4 spaces for indentation (compatible with Black)
109
+ indent-style = "space"
110
+ # Respect magic trailing comma (compatible with Black)
111
+ skip-magic-trailing-comma = false
112
+ # Line length compatible with Black
113
+ line-ending = "auto"
@@ -0,0 +1,6 @@
1
+ """Main llg3d package."""
2
+
3
+ from .solver import LIB_AVAILABLE, rank, size, comm, status
4
+
5
+ __version__ = "2.0.1"
6
+ __all__ = ["LIB_AVAILABLE", "rank", "size", "comm", "status", "__version__"]
@@ -0,0 +1,6 @@
1
+ """Main entry point to execute the llg3d module."""
2
+
3
+ from .main import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,134 @@
1
+ """Module containing the definition of the chemical elements."""
2
+
3
+ from abc import ABC
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(ABC):
15
+ """
16
+ Abstract class for chemical elements.
17
+
18
+ Args:
19
+ T: Temperature in Kelvin
20
+ H_ext: External magnetic field strength
21
+ g: Grid object representing the simulation grid
22
+ dt: Time step for the simulation
23
+ """
24
+
25
+ A = 0.0
26
+ K = 0.0
27
+ lambda_G = 0.0
28
+ M_s = 0.0
29
+ a_eff = 0.0
30
+ anisotropy: str = ""
31
+
32
+ def __init__(self, T: float, H_ext: float, g: Grid, dt: float) -> None:
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
+ 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
+ """Cobalt element."""
85
+
86
+ A = 30.0e-12 #: Exchange constant :math:`[J.m^{-1}]`
87
+ K = 520.0e3 #: Anisotropy constant :math:`[J.m^{-3}]`
88
+ lambda_G = 0.5 #: Damping parameter :math:`[1]`
89
+ M_s = 1400.0e3 #: Saturation magnetization :math:`[A.m^{-1}]`
90
+ a_eff = 0.25e-9 #: Effective lattice constant :math:`[m]`
91
+ anisotropy = "uniaxial" #: Type of anisotropy (e.g., "uniaxial", "cubic")
92
+
93
+
94
+ class Iron(Element):
95
+ """Iron element."""
96
+
97
+ A = 21.0e-12 #: Exchange constant :math:`[J.m^{-1}]`
98
+ K = 48.0e3 #: Anisotropy constant :math:`[J.m^{-3}]`
99
+ lambda_G = 0.5 #: Damping parameter :math:`[1]`
100
+ M_s = 1700.0e3 #: Saturation magnetization :math:`[A.m^{-1}]`
101
+ a_eff = 0.286e-9 #: Effective lattice constant :math:`[m]`
102
+ anisotropy = "cubic" #: Type of anisotropy (e.g., "uniaxial", "cubic")
103
+
104
+
105
+ class Nickel(Element):
106
+ """Nickel element."""
107
+
108
+ A = 9.0e-12 #: Exchange constant :math:`[J.m^{-1}]`
109
+ K = -5.7e3 #: Anisotropy constant :math:`[J.m^{-3}]`
110
+ lambda_G = 0.5 #: Damping parameter :math:`[1]`
111
+ M_s = 490.0e3 #: Saturation magnetization :math:`[A.m^{-1}]`
112
+ a_eff = 0.345e-9 #: Effective lattice constant :math:`[m]`
113
+ anisotropy = "cubic" #: Type of anisotropy (e.g., "uniaxial", "cubic")
114
+
115
+
116
+ def get_element_class(element_name: str | type[Element]) -> type[Element]:
117
+ """
118
+ Get the class of the chemical element by its name.
119
+
120
+ Args:
121
+ element_name: The name of the element or its class
122
+
123
+ Returns:
124
+ The class of the element
125
+
126
+ Raises:
127
+ ValueError: If the element is not found
128
+ """
129
+ if isinstance(element_name, type):
130
+ return element_name
131
+ for cls in Element.__subclasses__():
132
+ if cls.__name__ == element_name:
133
+ return cls
134
+ raise ValueError(f"Element '{element_name}' not found in {__file__}.")
@@ -0,0 +1,123 @@
1
+ """Module to define the grid for the simulation."""
2
+
3
+ from dataclasses import dataclass
4
+ import numpy as np
5
+
6
+ from .solver import rank, size
7
+
8
+
9
+ @dataclass
10
+ class Grid:
11
+ """Stores grid data."""
12
+
13
+ # Parameter values correspond to the global grid
14
+ Jx: int #: number of points in x direction
15
+ Jy: int #: number of points in y direction
16
+ Jz: int #: number of points in z direction
17
+ dx: float #: grid spacing in x direction
18
+
19
+ def __post_init__(self) -> None:
20
+ """Compute grid characteristics."""
21
+ self.dy = self.dz = self.dx # Setting dx = dy = dz
22
+ self.Lx = (self.Jx - 1) * self.dx
23
+ self.Ly = (self.Jy - 1) * self.dy
24
+ self.Lz = (self.Jz - 1) * self.dz
25
+ # shape of the local array to the process
26
+ self.dims = self.Jx // size, self.Jy, self.Jz
27
+ # elemental volume of a cell
28
+ self.dV = self.dx * self.dy * self.dz
29
+ # total volume
30
+ self.V = self.Lx * self.Ly * self.Lz
31
+ # total number of points
32
+ self.ntot = self.Jx * self.Jy * self.Jz
33
+ self.ncell = (self.Jx - 1) * (self.Jy - 1) * (self.Jz - 1)
34
+
35
+ def __str__(self) -> str:
36
+ """Print grid information."""
37
+ header = "\t\t".join(("x", "y", "z"))
38
+ s = f"""\
39
+ \t{header}
40
+ J\t{self.Jx}\t\t{self.Jy}\t\t{self.Jz}
41
+ L\t{self.Lx:.08e}\t{self.Ly:.08e}\t{self.Lz:.08e}
42
+ d\t{self.dx:.08e}\t{self.dy:.08e}\t{self.dz:.08e}
43
+
44
+ dV = {self.dV:.08e}
45
+ V = {self.V:.08e}
46
+ ntot = {self.ntot:d}
47
+ ncell = {self.ncell:d}
48
+ """
49
+ return s
50
+
51
+ def get_filename(
52
+ self, T: float, name: str = "m1_mean", extension: str = "txt"
53
+ ) -> str:
54
+ """
55
+ Returns the output file name for a given temperature.
56
+
57
+ Args:
58
+ T: temperature
59
+ name: file name
60
+ extension: file extension
61
+
62
+ Returns:
63
+ file name
64
+
65
+ >>> g = Grid(Jx=300, Jy=21, Jz=21, dx=1.e-9)
66
+ >>> g.get_filename(1100)
67
+ 'm1_mean_T1100_300x21x21.txt'
68
+ """
69
+ suffix = f"T{int(T)}_{self.Jx}x{self.Jy}x{self.Jz}"
70
+ return f"{name}_{suffix}.{extension}"
71
+
72
+ def get_mesh(
73
+ self, local: bool = True, dtype=np.float64
74
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
75
+ """
76
+ Returns a meshgrid of the coordinates.
77
+
78
+ Args:
79
+ local: if True, returns the local coordinates,
80
+ otherwise the global coordinates
81
+ dtype: data type of the coordinates)
82
+
83
+ Returns:
84
+ tuple of 3D arrays with the coordinates
85
+ """
86
+ x_global = np.linspace(0, self.Lx, self.Jx, dtype=dtype) # global coordinates
87
+ y = np.linspace(0, self.Ly, self.Jy, dtype=dtype)
88
+ z = np.linspace(0, self.Lz, self.Jz, dtype=dtype)
89
+ if local:
90
+ x_local = np.split(x_global, size)[rank] # local coordinates
91
+ return np.meshgrid(x_local, y, z, indexing="ij")
92
+ else:
93
+ return np.meshgrid(x_global, y, z, indexing="ij")
94
+
95
+ def to_dict(self) -> dict:
96
+ """
97
+ Export grid parameters to a dictionary for JAX JIT compatibility.
98
+
99
+ Returns:
100
+ Dictionary containing grid parameters needed for computations
101
+ """
102
+ return {
103
+ "dx": self.dx,
104
+ "dy": self.dy,
105
+ "dz": self.dz,
106
+ "Jx": self.Jx,
107
+ "Jy": self.Jy,
108
+ "Jz": self.Jz,
109
+ "dV": self.dV,
110
+ }
111
+
112
+ def get_laplacian_coeff(self) -> tuple[float, float, float, float]:
113
+ """
114
+ Returns the coefficients for the laplacian computation.
115
+
116
+ Returns:
117
+ Tuple of coefficients (dx2_inv, dy2_inv, dz2_inv, center_coeff)
118
+ """
119
+ dx2_inv = 1 / self.dx**2
120
+ dy2_inv = 1 / self.dy**2
121
+ dz2_inv = 1 / self.dz**2
122
+ center_coeff = -2 * (dx2_inv + dy2_inv + dz2_inv)
123
+ return dx2_inv, dy2_inv, dz2_inv, center_coeff
@@ -0,0 +1,67 @@
1
+ """Define a CLI for the llg3d package."""
2
+
3
+ import argparse
4
+
5
+
6
+ from . import rank, size, LIB_AVAILABLE
7
+ from .parameters import parameters, get_parameter_list
8
+ from .simulation import Simulation
9
+
10
+ if LIB_AVAILABLE["mpi4py"]:
11
+ # Use the MPI version of the ArgumentParser
12
+ from .solver.mpi import ArgumentParser
13
+ else:
14
+ # Use the original version of the ArgumentParser
15
+ from argparse import ArgumentParser
16
+
17
+
18
+ def parse_args(args: list[str] | None) -> argparse.Namespace:
19
+ """
20
+ Argument parser for llg3d.
21
+
22
+ Automatically adds arguments from the parameter dictionary.
23
+
24
+ Returns:
25
+ argparse.Namespace: Parsed arguments
26
+ """
27
+ parser = ArgumentParser(
28
+ description=__doc__,
29
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
30
+ )
31
+
32
+ if size > 1:
33
+ parameters["solver"]["default"] = "mpi"
34
+
35
+ # Automatically add arguments from the parameter dictionary
36
+ for name, parameter in parameters.items():
37
+ if "action" not in parameter:
38
+ parameter["type"] = type(parameter["default"])
39
+ parser.add_argument(f"--{name}", **parameter)
40
+
41
+ return parser.parse_args(args)
42
+
43
+
44
+ def main(arg_list: list[str] = None):
45
+ """
46
+ Evaluates the command line and runs the simulation.
47
+
48
+ Args:
49
+ arg_list: List of command line arguments
50
+ """
51
+ args = parse_args(arg_list)
52
+
53
+ if size > 1 and args.solver != "mpi":
54
+ raise ValueError(f"Solver method {args.solver} is not compatible with MPI.")
55
+ if args.solver == "mpi" and not LIB_AVAILABLE["mpi4py"]:
56
+ raise ValueError(
57
+ "The MPI solver method requires to install the mpi4py package, "
58
+ "for example using pip: pip install mpi4py"
59
+ )
60
+
61
+ if rank == 0:
62
+ # Display parameters as a list
63
+ print(get_parameter_list(vars(args)))
64
+
65
+ simulation = Simulation(vars(args))
66
+ simulation.run()
67
+ simulation.save()
@@ -0,0 +1,107 @@
1
+ """Utility functions for LLG3D."""
2
+
3
+ import json
4
+ import sys
5
+ from typing import Iterable, TextIO
6
+
7
+ from .solver import rank
8
+ from .grid import Grid
9
+
10
+
11
+ def progress_bar(
12
+ it: Iterable, prefix: str = "", size: int = 60, out: TextIO = sys.stdout
13
+ ):
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
+ count = len(it)
26
+
27
+ def show(j):
28
+ x = int(size * j / count)
29
+ if rank == 0:
30
+ print(
31
+ f"{prefix}[{'█' * x}{('.' * (size - x))}] {j}/{count}",
32
+ end="\r",
33
+ file=out,
34
+ flush=True,
35
+ )
36
+
37
+ show(0)
38
+ for i, item in enumerate(it):
39
+ yield item
40
+ # To avoid slowing down the computation, we do not display at every iteration
41
+ if i % 5 == 0:
42
+ show(i + 1)
43
+ show(i + 1)
44
+ if rank == 0:
45
+ print("\n", flush=True, file=out)
46
+
47
+
48
+ def write_json(json_file: str, run: dict):
49
+ """
50
+ Writes the run dictionary to a JSON file.
51
+
52
+ Args:
53
+ json_file: Name of the JSON file
54
+ run: Dictionary containing the run information
55
+ """
56
+ with open(json_file, "w") as f:
57
+ json.dump(run, f, indent=4)
58
+
59
+
60
+ def get_output_files(g: Grid, T: float, n_mean: int, n_profile: int) -> tuple:
61
+ """
62
+ Open files and list them.
63
+
64
+ Args:
65
+ g: Grid object
66
+ T: temperature
67
+ n_mean: Number of iterations for integral output
68
+ n_profile: Number of iterations for profile output
69
+
70
+ Returns:
71
+ - a file handler for storing m space integral over time
72
+ - a file handler for storing x-profiles of m_i
73
+ - a list of output filenames
74
+ """
75
+ f_mean = None
76
+ f_profiles = None
77
+ output_filenames = []
78
+ if n_mean != 0:
79
+ output_filenames.append(g.get_filename(T, extension="txt"))
80
+ if n_profile != 0:
81
+ output_filenames.extend(
82
+ [g.get_filename(T, name=f"m{i + 1}", extension="npy") for i in range(3)]
83
+ )
84
+ if rank == 0:
85
+ if n_mean != 0:
86
+ f_mean = open(output_filenames[0], "w") # integral of m1
87
+ if n_profile != 0:
88
+ f_profiles = [
89
+ open(output_filename, "wb") for output_filename in output_filenames[1:]
90
+ ] # x profiles of m_i
91
+
92
+ return f_mean, f_profiles, output_filenames
93
+
94
+
95
+ def close_output_files(f_mean: TextIO, f_profiles: list[TextIO] = None):
96
+ """
97
+ Close all output files.
98
+
99
+ Args:
100
+ f_mean: file handler for storing m space integral over time
101
+ f_profiles: file handlers for storing x-profiles of m_i
102
+ """
103
+ if f_mean is not None:
104
+ f_mean.close()
105
+ if f_profiles is not None:
106
+ for f_profile in f_profiles:
107
+ f_profile.close()