Skip to content

Commit 5bb3d78

Browse files
authored
Merge pull request #121 from LemurPwned/feat/add-ahe-example
2 parents 5f63880 + 3ffbe11 commit 5bb3d78

25 files changed

Lines changed: 12786 additions & 188 deletions

.github/workflows/python-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
strategy:
3333
fail-fast: true
3434
matrix:
35-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
35+
python-version: ["3.11", "3.12", "3.13"]
3636

3737
steps:
3838
- uses: actions/checkout@v4
@@ -57,7 +57,7 @@ jobs:
5757
strategy:
5858
fail-fast: true
5959
matrix:
60-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
60+
python-version: ["3.11", "3.12", "3.13"]
6161

6262
steps:
6363
- uses: actions/checkout@v4

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@ repos:
3636

3737
- repo: https://github.com/astral-sh/ruff-pre-commit
3838
# Ruff version.
39-
rev: v0.12.3
39+
rev: v0.14.0
4040
hooks:
4141
# Run the linter.
4242
- id: ruff
43-
files: ^cmtj
43+
files: ^(cmtj|curated-examples)/
4444
args: ["--fix"]
4545
types_or: [python, pyi]
4646
# Run the formatter.
4747
- id: ruff-format
48-
files: ^cmtj
48+
files: ^(cmtj|curated-examples)/
4949
types_or: [python, pyi]
5050

5151
- repo: https://github.com/pocc/pre-commit-hooks

cmtj/utils/demag.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
"""
2+
This piece of code was inspired by: https://github.com/micromagnetics/70LinesOfNumpy
3+
Reference: https://arxiv.org/abs/1411.7188.
4+
5+
Hence, this piece of code is a modified version of the original code and still follows LGPL license.
6+
Note on dipole and demag tensor:
7+
8+
- The dipole tensor excludes "itself" interaction.
9+
- The demag tensor includes "itself" interaction.
10+
11+
Ad. dipole
12+
For macrospin mask the other object in the M field, compute the field and return value of the
13+
field in the masked region -- that's the magnitude of the dipole coupling field.
14+
"""
15+
16+
from math import asinh, atan, log, pi, sqrt
17+
18+
import numpy as np
19+
from numba import jit
20+
21+
EPS = 1e-18
22+
23+
24+
def _aharoni_demag_factor_z(a: float, b: float, c: float) -> float:
25+
"""
26+
Demagnetising factor for a rectangular prism magnetised along the z-axis.
27+
28+
The implementation follows Aharoni, J. Appl. Phys. 83 (1998) 3432.
29+
All edge lengths must be positive and expressed in the same units.
30+
"""
31+
a, b, c = map(float, (a, b, c))
32+
if min(a, b, c) <= 0.0:
33+
raise ValueError("Edge lengths must be positive.")
34+
35+
r = sqrt(a * a + b * b + c * c)
36+
r_ab = sqrt(a * a + b * b)
37+
r_ac = sqrt(a * a + c * c)
38+
r_bc = sqrt(b * b + c * c)
39+
40+
def _log_ratio(numerator: float, denominator: float) -> float:
41+
ratio = numerator / denominator
42+
if ratio <= 0.0:
43+
raise ValueError("Logarithm argument must be positive.")
44+
return log(ratio)
45+
46+
pi_dz = 0.0
47+
pi_dz += (b * b - c * c) / (2.0 * b * c) * _log_ratio(r - a, r + a)
48+
pi_dz += (a * a - c * c) / (2.0 * a * c) * _log_ratio(r - b, r + b)
49+
pi_dz += (b / (2.0 * c)) * _log_ratio(r_ab + a, r_ab - a)
50+
pi_dz += (a / (2.0 * c)) * _log_ratio(r_ab + b, r_ab - b)
51+
pi_dz += (c / (2.0 * a)) * _log_ratio(r_bc - b, r_bc + b)
52+
pi_dz += (c / (2.0 * b)) * _log_ratio(r_ac - a, r_ac + a)
53+
pi_dz += 2.0 * atan(a * b / (c * r))
54+
pi_dz += (a**3 + b**3 - 2.0 * c**3) / (3.0 * a * b * c)
55+
pi_dz += (a * a + b * b - 2.0 * c * c) / (3.0 * a * b * c) * r
56+
pi_dz += (c / (a * b)) * (r_ac + r_bc)
57+
pi_dz -= (r_ab**3 + r_bc**3 + r_ac**3) / (3.0 * a * b * c)
58+
return pi_dz / pi
59+
60+
61+
def rectangular_prism_demag_factors(width: float, length: float, height: float):
62+
"""
63+
Compute (Dx, Dy, Dz) for a uniformly magnetised rectangular prism.
64+
65+
Args:
66+
width: Prism extent along the x-axis.
67+
length: Prism extent along the y-axis.
68+
height: Prism extent along the z-axis.
69+
70+
Returns:
71+
Tuple of demagnetising factors (Dx, Dy, Dz).
72+
"""
73+
dz = _aharoni_demag_factor_z(width, length, height)
74+
dx = _aharoni_demag_factor_z(length, height, width)
75+
dy = _aharoni_demag_factor_z(height, width, length)
76+
return dx, dy, dz
77+
78+
79+
@jit
80+
def f(p):
81+
x, y, z = abs(p[0]), abs(p[1]), abs(p[2])
82+
return (
83+
+y / 2.0 * (z**2 - x**2) * asinh(y / (sqrt(x**2 + z**2) + EPS))
84+
+ z / 2.0 * (y**2 - x**2) * asinh(z / (sqrt(x**2 + y**2) + EPS))
85+
- x * y * z * atan(y * z / (x * sqrt(x**2 + y**2 + z**2) + EPS))
86+
+ 1.0 / 6.0 * (2 * x**2 - y**2 - z**2) * sqrt(x**2 + y**2 + z**2)
87+
)
88+
89+
90+
# newell g
91+
@jit
92+
def g(p):
93+
x, y, z = p[0], p[1], abs(p[2])
94+
return (
95+
+x * y * z * asinh(z / (sqrt(x**2 + y**2) + EPS))
96+
+ y / 6.0 * (3.0 * z**2 - y**2) * asinh(x / (sqrt(y**2 + z**2) + EPS))
97+
+ x / 6.0 * (3.0 * z**2 - x**2) * asinh(y / (sqrt(x**2 + z**2) + EPS))
98+
- z**3 / 6.0 * atan(x * y / (z * sqrt(x**2 + y**2 + z**2) + EPS))
99+
- z * y**2 / 2.0 * atan(x * z / (y * sqrt(x**2 + y**2 + z**2) + EPS))
100+
- z * x**2 / 2.0 * atan(y * z / (x * sqrt(x**2 + y**2 + z**2) + EPS))
101+
- x * y * sqrt(x**2 + y**2 + z**2) / 3.0
102+
)
103+
104+
105+
def set_n_demag(n_demag, n, dx, c, permute, func):
106+
it = np.nditer(n_demag[:, :, :, c], flags=["multi_index"], op_flags=["writeonly"])
107+
drrr = np.prod(dx)
108+
while not it.finished:
109+
value = 0.0
110+
for i in np.rollaxis(np.indices((2,) * 6), 0, 7).reshape(64, 6):
111+
idx = list(
112+
map(
113+
lambda k: (it.multi_index[k] + n[k] - 1) % (2 * n[k] - 1) - n[k] + 1,
114+
range(3),
115+
)
116+
)
117+
value += (-1) ** sum(i) * func(list(map(lambda j: (idx[j] + i[j] - i[j + 3]) * dx[j], permute)))
118+
it[0] = -value / (4 * pi * drrr)
119+
it.iternext()
120+
121+
122+
def get_full_demag_tensor(n, dx):
123+
"""
124+
Get the full demag tensor for a given cells n and their sizes dx.
125+
"""
126+
n_demag = np.zeros([2 * i - 1 for i in n] + [6])
127+
for i, t in enumerate(
128+
(
129+
(f, 0, 1, 2),
130+
(g, 0, 1, 2),
131+
(g, 0, 2, 1),
132+
(f, 1, 2, 0),
133+
(g, 1, 2, 0),
134+
(f, 2, 0, 1),
135+
)
136+
):
137+
set_n_demag(n_demag, n, dx, i, t[1:], t[0])
138+
n_demag = n_demag[: n[0], : n[1], : n[2], :]
139+
# N11, N12, N13
140+
# N21, N22, N23
141+
# N31, N32, N33
142+
# ===
143+
# 0 1 2
144+
# 1 3 4
145+
# 2 4 5
146+
tensor = np.zeros_like(*n, 3, 3, dtype=n_demag.dtype)
147+
tensor[..., 0, 0] = n_demag[..., 0]
148+
tensor[..., 0, 1] = n_demag[..., 1]
149+
tensor[..., 0, 2] = n_demag[..., 2]
150+
tensor[..., 1, 0] = n_demag[..., 1]
151+
tensor[..., 1, 1] = n_demag[..., 3] # N22
152+
tensor[..., 1, 2] = n_demag[..., 4] # N23
153+
tensor[..., 2, 0] = n_demag[..., 2] # N31 = N13
154+
tensor[..., 2, 1] = n_demag[..., 4] # N32 = N23
155+
tensor[..., 2, 2] = n_demag[..., 5] # N33
156+
return tensor

0 commit comments

Comments
 (0)