llg3d 2.0.0__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.
- {llg3d-2.0.0 → llg3d-3.0.0}/PKG-INFO +6 -3
- llg3d-3.0.0/pyproject.toml +131 -0
- llg3d-3.0.0/src/llg3d/__init__.py +4 -0
- llg3d-3.0.0/src/llg3d/__main__.py +6 -0
- llg3d-3.0.0/src/llg3d/benchmarks/__init__.py +1 -0
- llg3d-3.0.0/src/llg3d/benchmarks/compare_commits.py +321 -0
- llg3d-3.0.0/src/llg3d/benchmarks/efficiency.py +451 -0
- llg3d-3.0.0/src/llg3d/benchmarks/utils.py +25 -0
- llg3d-3.0.0/src/llg3d/element.py +215 -0
- llg3d-3.0.0/src/llg3d/grid.py +113 -0
- llg3d-3.0.0/src/llg3d/io.py +395 -0
- llg3d-3.0.0/src/llg3d/main.py +64 -0
- llg3d-3.0.0/src/llg3d/parameters.py +185 -0
- llg3d-3.0.0/src/llg3d/post/__init__.py +1 -0
- llg3d-3.0.0/src/llg3d/post/extract.py +105 -0
- llg3d-3.0.0/src/llg3d/post/info.py +178 -0
- llg3d-3.0.0/src/llg3d/post/m1_vs_T.py +90 -0
- llg3d-3.0.0/src/llg3d/post/m1_vs_time.py +56 -0
- llg3d-3.0.0/src/llg3d/post/process.py +112 -0
- llg3d-3.0.0/src/llg3d/post/utils.py +38 -0
- llg3d-3.0.0/src/llg3d/post/x_profiles.py +141 -0
- llg3d-3.0.0/src/llg3d/py.typed +1 -0
- llg3d-3.0.0/src/llg3d/solvers/__init__.py +153 -0
- llg3d-3.0.0/src/llg3d/solvers/base.py +345 -0
- llg3d-3.0.0/src/llg3d/solvers/experimental/__init__.py +9 -0
- llg3d-3.0.0/src/llg3d/solvers/experimental/jax.py +361 -0
- llg3d-3.0.0/src/llg3d/solvers/math_utils.py +41 -0
- llg3d-3.0.0/src/llg3d/solvers/mpi.py +370 -0
- llg3d-3.0.0/src/llg3d/solvers/numpy.py +126 -0
- llg3d-3.0.0/src/llg3d/solvers/opencl.py +439 -0
- llg3d-3.0.0/src/llg3d/solvers/profiling.py +38 -0
- {llg3d-2.0.0 → llg3d-3.0.0}/src/llg3d.egg-info/PKG-INFO +6 -3
- llg3d-3.0.0/src/llg3d.egg-info/SOURCES.txt +45 -0
- llg3d-3.0.0/src/llg3d.egg-info/entry_points.txt +9 -0
- {llg3d-2.0.0 → llg3d-3.0.0}/src/llg3d.egg-info/requires.txt +4 -0
- llg3d-3.0.0/tests/test_element.py +159 -0
- llg3d-3.0.0/tests/test_grid.py +79 -0
- llg3d-3.0.0/tests/test_io.py +306 -0
- llg3d-3.0.0/tests/test_main.py +43 -0
- llg3d-3.0.0/tests/test_parameters.py +53 -0
- llg3d-3.0.0/tests/test_profiling.py +92 -0
- llg3d-2.0.0/pyproject.toml +0 -80
- llg3d-2.0.0/src/llg3d/__init__.py +0 -4
- llg3d-2.0.0/src/llg3d/__main__.py +0 -6
- llg3d-2.0.0/src/llg3d/element.py +0 -128
- llg3d-2.0.0/src/llg3d/grid.py +0 -126
- llg3d-2.0.0/src/llg3d/main.py +0 -66
- llg3d-2.0.0/src/llg3d/output.py +0 -108
- llg3d-2.0.0/src/llg3d/parameters.py +0 -75
- llg3d-2.0.0/src/llg3d/post/__init__.py +0 -1
- llg3d-2.0.0/src/llg3d/post/plot_results.py +0 -65
- llg3d-2.0.0/src/llg3d/post/process.py +0 -105
- llg3d-2.0.0/src/llg3d/post/temperature.py +0 -83
- llg3d-2.0.0/src/llg3d/simulation.py +0 -104
- llg3d-2.0.0/src/llg3d/solver/__init__.py +0 -45
- llg3d-2.0.0/src/llg3d/solver/jax.py +0 -383
- llg3d-2.0.0/src/llg3d/solver/mpi.py +0 -449
- llg3d-2.0.0/src/llg3d/solver/numpy.py +0 -210
- llg3d-2.0.0/src/llg3d/solver/opencl.py +0 -329
- llg3d-2.0.0/src/llg3d/solver/solver.py +0 -93
- llg3d-2.0.0/src/llg3d.egg-info/SOURCES.txt +0 -32
- llg3d-2.0.0/src/llg3d.egg-info/entry_points.txt +0 -4
- llg3d-2.0.0/tests/test_element.py +0 -53
- llg3d-2.0.0/tests/test_grid.py +0 -62
- llg3d-2.0.0/tests/test_main.py +0 -50
- llg3d-2.0.0/tests/test_parameters.py +0 -39
- {llg3d-2.0.0 → llg3d-3.0.0}/AUTHORS +0 -0
- {llg3d-2.0.0 → llg3d-3.0.0}/LICENSE +0 -0
- {llg3d-2.0.0 → llg3d-3.0.0}/README.md +0 -0
- {llg3d-2.0.0 → llg3d-3.0.0}/setup.cfg +0 -0
- {llg3d-2.0.0 → llg3d-3.0.0}/src/llg3d.egg-info/dependency_links.txt +0 -0
- {llg3d-2.0.0 → 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:
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 3.0.0
|
|
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.
|
|
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
|
|
@@ -0,0 +1,131 @@
|
|
|
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.10"
|
|
18
|
+
dependencies = ["numpy", "matplotlib", "scipy", "tqdm"]
|
|
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.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"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
mpi = ["mpi4py"]
|
|
41
|
+
opencl = ["pyopencl", "mako"]
|
|
42
|
+
jax = [
|
|
43
|
+
"jax[cuda] ; sys_platform != 'darwin'",
|
|
44
|
+
"jax[cpu] ; sys_platform == 'darwin'",
|
|
45
|
+
]
|
|
46
|
+
git = ["GitPython"]
|
|
47
|
+
|
|
48
|
+
[tool.setuptools]
|
|
49
|
+
include-package-data = true
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.package-dir]
|
|
52
|
+
"" = "src"
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.packages.find]
|
|
55
|
+
where = ["src"]
|
|
56
|
+
namespaces = false
|
|
57
|
+
|
|
58
|
+
[tool.setuptools.dynamic.version]
|
|
59
|
+
attr = "llg3d.__version__"
|
|
60
|
+
|
|
61
|
+
[tool.coverage.run]
|
|
62
|
+
parallel = true
|
|
63
|
+
source = ["src/"]
|
|
64
|
+
omit = ["src/llg3d/benchmarks/*", "src/llg3d/solvers/jax/*"]
|
|
65
|
+
|
|
66
|
+
[dependency-groups]
|
|
67
|
+
dev = [
|
|
68
|
+
{ include-group = "lint" },
|
|
69
|
+
{ include-group = "test" },
|
|
70
|
+
{ include-group = "doc" },
|
|
71
|
+
]
|
|
72
|
+
test = ["pytest", "pytest-cov", "pytest-mpi"]
|
|
73
|
+
lint = ["ruff", "mypy", "pydoclint", "scipy-stubs", "types-tqdm"]
|
|
74
|
+
doc = [
|
|
75
|
+
"Sphinx >= 7.2.2",
|
|
76
|
+
"sphinx-design",
|
|
77
|
+
"myst-parser",
|
|
78
|
+
"furo",
|
|
79
|
+
"nbsphinx",
|
|
80
|
+
"sphinx-copybutton",
|
|
81
|
+
"sphinx-autobuild",
|
|
82
|
+
"sphinx-prompt",
|
|
83
|
+
"sphinx-last-updated-by-git",
|
|
84
|
+
"sphinxcontrib-programoutput",
|
|
85
|
+
"sphinxcontrib-bibtex",
|
|
86
|
+
"ipython",
|
|
87
|
+
"ipykernel",
|
|
88
|
+
"tabulate",
|
|
89
|
+
"types-tabulate",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[tool.ruff]
|
|
93
|
+
# Set line length to 88 (compatible with Black)
|
|
94
|
+
line-length = 88
|
|
95
|
+
exclude = ["src/llg3d/process_tmp/"]
|
|
96
|
+
|
|
97
|
+
[tool.ruff.lint.per-file-ignores]
|
|
98
|
+
# Ignore all directories named `tests`.
|
|
99
|
+
"tests/**" = ["D"]
|
|
100
|
+
|
|
101
|
+
[tool.ruff.lint]
|
|
102
|
+
# Enable pydocstyle (D) rules along with existing rules
|
|
103
|
+
select = ["E", "F", "W", "D"]
|
|
104
|
+
extend-select = ["D213"]
|
|
105
|
+
ignore = [
|
|
106
|
+
"D107", # Missing docstring in __init__()
|
|
107
|
+
# Style preferences - ignore D212 to enable D213 (Google convention)
|
|
108
|
+
"D212", # Multi-line docstring summary should start at the first line (we prefer D213)
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint.pydocstyle]
|
|
112
|
+
# Use Google convention for docstrings
|
|
113
|
+
convention = "google"
|
|
114
|
+
|
|
115
|
+
[tool.ruff.format]
|
|
116
|
+
# Use double quotes for strings (compatible with Black)
|
|
117
|
+
quote-style = "double"
|
|
118
|
+
# Use 4 spaces for indentation (compatible with Black)
|
|
119
|
+
indent-style = "space"
|
|
120
|
+
# Respect magic trailing comma (compatible with Black)
|
|
121
|
+
skip-magic-trailing-comma = false
|
|
122
|
+
# Line length compatible with Black
|
|
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 @@
|
|
|
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()
|