llg3d 2.0.1__tar.gz → 3.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.
Files changed (68) hide show
  1. {llg3d-2.0.1 → llg3d-3.0.0}/PKG-INFO +5 -2
  2. {llg3d-2.0.1 → llg3d-3.0.0}/pyproject.toml +23 -5
  3. llg3d-3.0.0/src/llg3d/__init__.py +4 -0
  4. llg3d-3.0.0/src/llg3d/benchmarks/__init__.py +1 -0
  5. llg3d-3.0.0/src/llg3d/benchmarks/compare_commits.py +321 -0
  6. llg3d-3.0.0/src/llg3d/benchmarks/efficiency.py +451 -0
  7. llg3d-3.0.0/src/llg3d/benchmarks/utils.py +25 -0
  8. {llg3d-2.0.1 → llg3d-3.0.0}/src/llg3d/element.py +98 -17
  9. llg3d-3.0.0/src/llg3d/grid.py +113 -0
  10. llg3d-3.0.0/src/llg3d/io.py +395 -0
  11. llg3d-3.0.0/src/llg3d/main.py +64 -0
  12. llg3d-3.0.0/src/llg3d/parameters.py +185 -0
  13. llg3d-3.0.0/src/llg3d/post/__init__.py +1 -0
  14. llg3d-3.0.0/src/llg3d/post/extract.py +105 -0
  15. llg3d-3.0.0/src/llg3d/post/info.py +178 -0
  16. llg3d-3.0.0/src/llg3d/post/m1_vs_T.py +90 -0
  17. llg3d-3.0.0/src/llg3d/post/m1_vs_time.py +56 -0
  18. llg3d-3.0.0/src/llg3d/post/process.py +112 -0
  19. llg3d-3.0.0/src/llg3d/post/utils.py +38 -0
  20. llg3d-3.0.0/src/llg3d/post/x_profiles.py +141 -0
  21. llg3d-3.0.0/src/llg3d/py.typed +1 -0
  22. llg3d-3.0.0/src/llg3d/solvers/__init__.py +153 -0
  23. llg3d-3.0.0/src/llg3d/solvers/base.py +345 -0
  24. llg3d-3.0.0/src/llg3d/solvers/experimental/__init__.py +9 -0
  25. {llg3d-2.0.1/src/llg3d/solver → llg3d-3.0.0/src/llg3d/solvers/experimental}/jax.py +117 -143
  26. llg3d-3.0.0/src/llg3d/solvers/math_utils.py +41 -0
  27. llg3d-3.0.0/src/llg3d/solvers/mpi.py +370 -0
  28. llg3d-3.0.0/src/llg3d/solvers/numpy.py +126 -0
  29. llg3d-3.0.0/src/llg3d/solvers/opencl.py +439 -0
  30. llg3d-3.0.0/src/llg3d/solvers/profiling.py +38 -0
  31. {llg3d-2.0.1 → llg3d-3.0.0}/src/llg3d.egg-info/PKG-INFO +5 -2
  32. llg3d-3.0.0/src/llg3d.egg-info/SOURCES.txt +45 -0
  33. llg3d-3.0.0/src/llg3d.egg-info/entry_points.txt +9 -0
  34. {llg3d-2.0.1 → llg3d-3.0.0}/src/llg3d.egg-info/requires.txt +4 -0
  35. llg3d-3.0.0/tests/test_element.py +159 -0
  36. llg3d-3.0.0/tests/test_grid.py +79 -0
  37. llg3d-3.0.0/tests/test_io.py +306 -0
  38. llg3d-3.0.0/tests/test_main.py +43 -0
  39. llg3d-3.0.0/tests/test_parameters.py +53 -0
  40. llg3d-3.0.0/tests/test_profiling.py +92 -0
  41. llg3d-2.0.1/src/llg3d/__init__.py +0 -6
  42. llg3d-2.0.1/src/llg3d/grid.py +0 -123
  43. llg3d-2.0.1/src/llg3d/main.py +0 -67
  44. llg3d-2.0.1/src/llg3d/output.py +0 -107
  45. llg3d-2.0.1/src/llg3d/parameters.py +0 -75
  46. llg3d-2.0.1/src/llg3d/post/__init__.py +0 -1
  47. llg3d-2.0.1/src/llg3d/post/plot_results.py +0 -61
  48. llg3d-2.0.1/src/llg3d/post/process.py +0 -110
  49. llg3d-2.0.1/src/llg3d/post/temperature.py +0 -76
  50. llg3d-2.0.1/src/llg3d/simulation.py +0 -95
  51. llg3d-2.0.1/src/llg3d/solver/__init__.py +0 -45
  52. llg3d-2.0.1/src/llg3d/solver/mpi.py +0 -450
  53. llg3d-2.0.1/src/llg3d/solver/numpy.py +0 -207
  54. llg3d-2.0.1/src/llg3d/solver/opencl.py +0 -330
  55. llg3d-2.0.1/src/llg3d/solver/solver.py +0 -89
  56. llg3d-2.0.1/src/llg3d.egg-info/SOURCES.txt +0 -32
  57. llg3d-2.0.1/src/llg3d.egg-info/entry_points.txt +0 -4
  58. llg3d-2.0.1/tests/test_element.py +0 -51
  59. llg3d-2.0.1/tests/test_grid.py +0 -65
  60. llg3d-2.0.1/tests/test_main.py +0 -53
  61. llg3d-2.0.1/tests/test_parameters.py +0 -39
  62. {llg3d-2.0.1 → llg3d-3.0.0}/AUTHORS +0 -0
  63. {llg3d-2.0.1 → llg3d-3.0.0}/LICENSE +0 -0
  64. {llg3d-2.0.1 → llg3d-3.0.0}/README.md +0 -0
  65. {llg3d-2.0.1 → llg3d-3.0.0}/setup.cfg +0 -0
  66. {llg3d-2.0.1 → llg3d-3.0.0}/src/llg3d/__main__.py +0 -0
  67. {llg3d-2.0.1 → llg3d-3.0.0}/src/llg3d.egg-info/dependency_links.txt +0 -0
  68. {llg3d-2.0.1 → llg3d-3.0.0}/src/llg3d.egg-info/top_level.txt +0 -0
@@ -1,19 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llg3d
3
- Version: 2.0.1
3
+ Version: 3.0.0
4
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.9
10
+ Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  License-File: AUTHORS
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: matplotlib
16
16
  Requires-Dist: scipy
17
+ Requires-Dist: tqdm
17
18
  Provides-Extra: mpi
18
19
  Requires-Dist: mpi4py; extra == "mpi"
19
20
  Provides-Extra: opencl
@@ -22,6 +23,8 @@ Requires-Dist: mako; extra == "opencl"
22
23
  Provides-Extra: jax
23
24
  Requires-Dist: jax[cuda]; sys_platform != "darwin" and extra == "jax"
24
25
  Requires-Dist: jax[cpu]; sys_platform == "darwin" and extra == "jax"
26
+ Provides-Extra: git
27
+ Requires-Dist: GitPython; extra == "git"
25
28
  Dynamic: license-file
26
29
 
27
30
  # LLG3D: A solver for the stochastic Landau-Lifshitz-Gilbert equation in 3D
@@ -14,8 +14,8 @@ classifiers = [
14
14
  "License :: OSI Approved :: MIT License",
15
15
  "Operating System :: OS Independent",
16
16
  ]
17
- requires-python = ">=3.9"
18
- dependencies = ["numpy", "matplotlib", "scipy"]
17
+ requires-python = ">=3.10"
18
+ dependencies = ["numpy", "matplotlib", "scipy", "tqdm"]
19
19
  dynamic = ["version"]
20
20
 
21
21
 
@@ -28,8 +28,13 @@ Homepage = "https://gitlab.math.unistra.fr/llg3d/llg3d"
28
28
 
29
29
  [project.scripts]
30
30
  llg3d = "llg3d.main:main"
31
- "llg3d.post" = "llg3d.post.temperature:main"
32
- "llg3d.plot_results" = "llg3d.post.plot_results:main"
31
+ "llg3d.m1_vs_T" = "llg3d.post.m1_vs_T:main"
32
+ "llg3d.m1_vs_time" = "llg3d.post.m1_vs_time:main"
33
+ "llg3d.x_profiles" = "llg3d.post.x_profiles:main"
34
+ "llg3d.info" = "llg3d.post.info:main"
35
+ "llg3d.extract" = "llg3d.post.extract:main"
36
+ "llg3d.bench.compare_commits" = "llg3d.benchmarks.compare_commits:main"
37
+ "llg3d.bench.efficiency" = "llg3d.benchmarks.efficiency:main"
33
38
 
34
39
  [project.optional-dependencies]
35
40
  mpi = ["mpi4py"]
@@ -38,6 +43,7 @@ jax = [
38
43
  "jax[cuda] ; sys_platform != 'darwin'",
39
44
  "jax[cpu] ; sys_platform == 'darwin'",
40
45
  ]
46
+ git = ["GitPython"]
41
47
 
42
48
  [tool.setuptools]
43
49
  include-package-data = true
@@ -55,6 +61,7 @@ attr = "llg3d.__version__"
55
61
  [tool.coverage.run]
56
62
  parallel = true
57
63
  source = ["src/"]
64
+ omit = ["src/llg3d/benchmarks/*", "src/llg3d/solvers/jax/*"]
58
65
 
59
66
  [dependency-groups]
60
67
  dev = [
@@ -63,7 +70,7 @@ dev = [
63
70
  { include-group = "doc" },
64
71
  ]
65
72
  test = ["pytest", "pytest-cov", "pytest-mpi"]
66
- lint = ["ruff"]
73
+ lint = ["ruff", "mypy", "pydoclint", "scipy-stubs", "types-tqdm"]
67
74
  doc = [
68
75
  "Sphinx >= 7.2.2",
69
76
  "sphinx-design",
@@ -77,6 +84,9 @@ doc = [
77
84
  "sphinxcontrib-programoutput",
78
85
  "sphinxcontrib-bibtex",
79
86
  "ipython",
87
+ "ipykernel",
88
+ "tabulate",
89
+ "types-tabulate",
80
90
  ]
81
91
 
82
92
  [tool.ruff]
@@ -111,3 +121,11 @@ indent-style = "space"
111
121
  skip-magic-trailing-comma = false
112
122
  # Line length compatible with Black
113
123
  line-ending = "auto"
124
+
125
+ [tool.pydoclint]
126
+ style = "google"
127
+ arg-type-hints-in-docstring = false
128
+ check-return-types = false
129
+
130
+ [tool.coverage.report]
131
+ omit = ["jax.py"]
@@ -0,0 +1,4 @@
1
+ """Main llg3d package."""
2
+
3
+ __version__ = "3.0.0"
4
+ __all__ = ["__version__"]
@@ -0,0 +1 @@
1
+ """Benchmarks package."""
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Compare simple benchmark timings between commits using GitPython.
4
+
5
+ This script keeps repository objects local to `main()` so it fails fast when
6
+ not run from the llg3d repository.
7
+ """
8
+
9
+ import argparse
10
+ import os
11
+ import re
12
+ import shlex
13
+ import subprocess
14
+ import sys
15
+ import logging
16
+ from pathlib import Path
17
+
18
+
19
+ from .utils import ChdirTemporaryDirectory
20
+ from git import Repo
21
+
22
+
23
+ def get_llg3d_repo() -> Repo:
24
+ """Return the llg3d GitPython Repo object."""
25
+ repo = Repo(Path(__file__).resolve(), search_parent_directories=True)
26
+ if repo.working_tree_dir is None:
27
+ raise RuntimeError("Could not determine repository working tree")
28
+ root = Path(repo.working_tree_dir)
29
+
30
+ if (root / "src" / "llg3d").exists():
31
+ return repo
32
+
33
+ py = root / "pyproject.toml"
34
+ if py.exists():
35
+ try:
36
+ import tomllib
37
+
38
+ with py.open("rb") as fh:
39
+ data = tomllib.load(fh)
40
+ if data.get("project", {}).get("name") == "llg3d":
41
+ return repo
42
+ except Exception:
43
+ pass
44
+
45
+ try:
46
+ for remote in repo.remotes:
47
+ try:
48
+ url = remote.url
49
+ except Exception:
50
+ url = repo.git.config(f"--get remote.{remote.name}.url")
51
+ if url and "llg3d" in str(url):
52
+ return repo
53
+ except Exception:
54
+ pass
55
+
56
+ logging.error("This script must be run inside the llg3d repository.")
57
+ logging.error(f"Detected repo root: {root}")
58
+ sys.exit(2)
59
+
60
+
61
+ def get_current_commit(repo: Repo) -> str:
62
+ """Return the current commit hexsha for `repo`."""
63
+ return repo.head.commit.hexsha
64
+
65
+
66
+ def get_current_ref(repo: Repo) -> str:
67
+ """Return the current branch name, or the commit if HEAD is detached."""
68
+ if repo.head.is_detached:
69
+ return get_current_commit(repo)
70
+ return repo.active_branch.name
71
+
72
+
73
+ def ensure_clean_worktree(repo: Repo) -> None:
74
+ """Exit if the worktree contains changes or untracked files."""
75
+ if repo.is_dirty(untracked_files=True):
76
+ logging.error("Working tree is not clean. Stash or commit changes first.")
77
+ logging.error("Hint: git stash -u")
78
+ sys.exit(1)
79
+
80
+
81
+ def checkout_commit(repo: Repo, commit: str) -> None:
82
+ """Checkout the given `commit` in `repo`."""
83
+ repo.git.checkout(commit)
84
+
85
+
86
+ def run_single_benchmark(
87
+ solver: str,
88
+ Jx: int,
89
+ Jy: int,
90
+ Jz: int,
91
+ n_mean: int,
92
+ N: int,
93
+ launcher: str | None = None,
94
+ np_procs: int = 1,
95
+ precision: str = "single",
96
+ repo_root: Path | None = None,
97
+ ) -> dict:
98
+ """Run a single benchmark and return a dict with timing results."""
99
+ if repo_root is None:
100
+ raise RuntimeError("repo_root must be provided")
101
+
102
+ arg_new = (
103
+ f"--element Cobalt --N {N} --dt 1.0e-14 --Jx {Jx} --Jy {Jy} --Jz {Jz} "
104
+ f"--dx 1.0e-9 --T 1100.0 --H_ext 0.0 --solver {solver} --precision {precision} "
105
+ f"--n_mean {n_mean} --n_profile 0 --seed 42"
106
+ )
107
+ arg_old = (
108
+ f"-element Cobalt -N {N} -dt 1.0e-14 -Jx {Jx} -Jy {Jy} -Jz {Jz} "
109
+ f"-dx 1.0e-9 -T 1100.0 -H_ext 0.0 -n_average {n_mean} -n_profile 0"
110
+ )
111
+
112
+ candidates = [
113
+ ["python", "-m", "llg3d"],
114
+ ["python", "-m", "llg3d.__main__"],
115
+ ["python", "-m", "llg3d.main"],
116
+ ["python", "-m", "llg3d.llg3d"],
117
+ ]
118
+
119
+ effective_launcher = launcher
120
+ if solver == "mpi" and not effective_launcher:
121
+ effective_launcher = f"mpirun -np {np_procs}"
122
+
123
+ env = os.environ.copy()
124
+ env["PYTHONPATH"] = f"{repo_root / 'src'}" + (":" + env.get("PYTHONPATH", ""))
125
+
126
+ last_err = None
127
+ output = ""
128
+
129
+ with ChdirTemporaryDirectory() as tmpdir:
130
+ for arg_str in (arg_new, arg_old):
131
+ arg_list = shlex.split(arg_str)
132
+ for cli in candidates:
133
+ cmd = cli + arg_list
134
+ if effective_launcher:
135
+ cmd = shlex.split(effective_launcher) + cmd
136
+ proc = subprocess.run(
137
+ cmd,
138
+ cwd=tmpdir,
139
+ capture_output=True,
140
+ text=True,
141
+ env=env,
142
+ check=False,
143
+ )
144
+ if proc.returncode == 0:
145
+ output = proc.stdout
146
+ break
147
+ last_err = (
148
+ "Cmd: "
149
+ + " ".join(cmd)
150
+ + "\n"
151
+ + "Stdout:\n"
152
+ + proc.stdout
153
+ + "\n"
154
+ + "Stderr:\n"
155
+ + proc.stderr
156
+ )
157
+ # log the last failing command at DEBUG level; it can be
158
+ # unrelated to the solver implementation/version
159
+ logging.debug(last_err)
160
+ if output:
161
+ break
162
+
163
+ if not output:
164
+ raise RuntimeError("Benchmark failed for all CLI variants\n" + (last_err or ""))
165
+
166
+ total_time = None
167
+ for line in output.splitlines():
168
+ if "total_time [s]" in line:
169
+ m = re.search(r"total_time \[s\]\s*=\s*([0-9.]+)", line)
170
+ if m:
171
+ total_time = float(m.group(1))
172
+ break
173
+
174
+ if total_time is None:
175
+ raise RuntimeError("Failed to extract total_time from output.\n" + output)
176
+
177
+ return {"total_time": total_time, "time_per_ite": total_time / N if N else 0.0}
178
+
179
+
180
+ def print_separator(char: str = "=", length: int = 80) -> None:
181
+ """Print a simple separator line to stdout."""
182
+ logging.info(char * length)
183
+
184
+
185
+ def main() -> None:
186
+ """Command-line entry point: compare benchmark timings between commits."""
187
+ # configure logging for the CLI; keep message format minimal
188
+ logging.basicConfig(level=logging.INFO, format="%(message)s")
189
+
190
+ repo = get_llg3d_repo()
191
+ assert repo.working_tree_dir is not None
192
+ repo_root = Path(repo.working_tree_dir)
193
+
194
+ parser = argparse.ArgumentParser(
195
+ description="Compare simple benchmark timings between two commits",
196
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
197
+ )
198
+ parser.add_argument("--reference-commit", default="HEAD~1")
199
+ parser.add_argument(
200
+ "--solver",
201
+ default="numpy",
202
+ choices=["numpy", "mpi", "opencl"],
203
+ )
204
+ parser.add_argument(
205
+ "--debug",
206
+ action="store_true",
207
+ help="Enable debug logging output",
208
+ )
209
+ parser.add_argument(
210
+ "--np", type=int, default=1, help="Number of processes for MPI solver"
211
+ )
212
+ parser.add_argument(
213
+ "--launcher", default="", help="Launcher command for MPI solver"
214
+ )
215
+ parser.add_argument(
216
+ "--Jx", type=int, default=300, help="Number of grid points in x"
217
+ )
218
+ parser.add_argument("--Jy", type=int, default=21, help="Number of grid points in y")
219
+ parser.add_argument("--Jz", type=int, default=21, help="Number of grid points in z")
220
+ parser.add_argument(
221
+ "--n_mean",
222
+ type=int,
223
+ default=0,
224
+ help="Spatial average frequency (number of iterations)",
225
+ )
226
+ parser.add_argument(
227
+ "--precision",
228
+ choices=["single", "double"],
229
+ default="single",
230
+ help="Precision of the simulation",
231
+ )
232
+ parser.add_argument("--N", type=int, default=100, help="Number of iterations")
233
+ parser.add_argument("--skip-reference", action="store_true")
234
+
235
+ args = parser.parse_args()
236
+
237
+ if args.debug:
238
+ logging.getLogger().setLevel(logging.DEBUG)
239
+
240
+ current_ref = get_current_ref(repo)
241
+ current_commit = get_current_commit(repo)
242
+ reference_commit = args.reference_commit
243
+
244
+ if current_ref == current_commit:
245
+ logging.error("detached HEAD — checkout a branch first")
246
+ sys.exit(1)
247
+
248
+ print_separator()
249
+ logging.info("SIMPLE COMPARISON BENCHMARK")
250
+ print_separator()
251
+ logging.info(f"Current ref : {current_ref}")
252
+ logging.info(f"Current hash : {current_commit[:8]}")
253
+ logging.info(f"Reference : {reference_commit}")
254
+ logging.info(f"Solver : {args.solver}")
255
+ print_separator()
256
+
257
+ reference_result = None
258
+ if not args.skip_reference:
259
+ stash = False
260
+ if repo.is_dirty(untracked_files=True):
261
+ try:
262
+ repo.git.stash("save", "-u")
263
+ stash = True
264
+ except Exception:
265
+ stash = False
266
+
267
+ try:
268
+ checkout_commit(repo, reference_commit)
269
+ reference_result = run_single_benchmark(
270
+ args.solver,
271
+ args.Jx,
272
+ args.Jy,
273
+ args.Jz,
274
+ args.n_mean,
275
+ args.N,
276
+ args.launcher or None,
277
+ args.np,
278
+ args.precision,
279
+ repo_root=repo_root,
280
+ )
281
+ finally:
282
+ checkout_commit(repo, current_ref)
283
+ if stash:
284
+ try:
285
+ repo.git.stash("pop")
286
+ except Exception:
287
+ pass
288
+
289
+ current_result = run_single_benchmark(
290
+ args.solver,
291
+ args.Jx,
292
+ args.Jy,
293
+ args.Jz,
294
+ args.n_mean,
295
+ args.N,
296
+ args.launcher or None,
297
+ args.np,
298
+ args.precision,
299
+ repo_root=repo_root,
300
+ )
301
+
302
+ if reference_result:
303
+ speedup = reference_result["total_time"] / current_result["total_time"]
304
+ diff_pct = (
305
+ 100
306
+ * (current_result["total_time"] - reference_result["total_time"])
307
+ / reference_result["total_time"]
308
+ )
309
+ logging.info(f"Reference : {reference_result['total_time']:.3f} s")
310
+ logging.info(f"Current : {current_result['total_time']:.3f} s")
311
+ logging.info(f"Speedup : {speedup:.3f}x")
312
+ logging.info(f"Diff : {diff_pct:+.1f}%")
313
+ else:
314
+ logging.info(f"Current : {current_result['total_time']:.3f} s")
315
+
316
+ print_separator()
317
+ logging.info("✓ Benchmark completed!")
318
+
319
+
320
+ if __name__ == "__main__":
321
+ main()