Skip to content

Commit a7a6123

Browse files
authored
Merge pull request #106 from computationalmodelling/full_exchange_model
Full exchange model
2 parents 0e2e182 + f2396ff commit a7a6123

File tree

21 files changed

+1272
-474
lines changed

21 files changed

+1272
-474
lines changed

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ RUN apt-get install -y curl git
2222
RUN pip3 install --upgrade setuptools==20.4
2323
# ----------
2424

25+
# ----------
26+
# hack to fix sudden breakage in CI
27+
# (fix from https://github.com/getsentry/sentry/issues/3143)
28+
# (first occurance of fail at https://travis-ci.org/computationalmodelling/fidimag/builds/319708056?utm_source=github_status&utm_medium=notification)
29+
# (which is part of this pull request: https://github.com/computationalmodelling/fidimag/pull/106)
30+
RUN pip3 install --upgrade setuptools==20.4
31+
# ----------
32+
2533
RUN pip3 install ipywidgets nbval pyvtk six
2634

2735
WORKDIR /fidimag

fidimag/atomistic/dmi.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,16 @@ def setup(self, mesh, spin, mu_s):
8585
super(DMI, self).setup(mesh, spin, mu_s)
8686

8787
if self.mesh_type == 'hexagonal':
88-
self.nneighbours = 6
88+
self.n_ngbs_dmi = 6
8989
# rdim = 3
9090

9191
elif self.mesh_type == 'cuboid':
92-
self.nneighbours = 4
92+
self.n_ngbs_dmi = 4
9393
# rdim = 3
9494

9595
# We will generate the Dzyaloshinskii vectors according
9696
# to the lattice, for the Interfacial DMI
97-
self.DMI_vector = self.compute_DMI_vectors(self.nneighbours)
97+
self.DMI_vector = self.compute_DMI_vectors(self.n_ngbs_dmi)
9898

9999
if self.dmi_type == 'bulk':
100100
self._D = np.zeros(self.neighbours.shape)
@@ -123,7 +123,9 @@ def compute_field(self, t=0, spin=None):
123123
self.energy,
124124
self._D,
125125
self.neighbours,
126-
self.n)
126+
self.n,
127+
self.n_ngbs
128+
)
127129

128130
elif self.dmi_type == 'interfacial':
129131

@@ -133,7 +135,8 @@ def compute_field(self, t=0, spin=None):
133135
self.D,
134136
self.neighbours,
135137
self.n,
136-
self.nneighbours,
138+
self.n_ngbs,
139+
self.n_ngbs_dmi,
137140
self.DMI_vector
138141
)
139142

@@ -153,7 +156,7 @@ def compute_energy_direct(self):
153156

154157
return energy
155158

156-
def compute_DMI_vectors(self, nneighbours):
159+
def compute_DMI_vectors(self, n_ngbs_dmi):
157160
"""
158161
159162
Returns a DMI vectors array, according to the order of the nearest
@@ -165,14 +168,14 @@ def compute_DMI_vectors(self, nneighbours):
165168
top right, bottom left,
166169
top left, bottom right]
167170
168-
Then, the DMI vectors array has (3 * nneighbours) entries, 3 for every
171+
Then, the DMI vectors array has (3 * n_ngbs_dmi) entries, 3 for every
169172
neighbouring site The vectors are normalised and computed according to:
170173
D_ij = r_ij X z where r_ij is the vector connecting a lattice site with
171174
the j-th neighbour (see Rohart and Thiaville PRB 88, 184422)
172175
173176
"""
174177

175-
dmi_vec = np.zeros(nneighbours * 3).reshape(-1, 3)
178+
dmi_vec = np.zeros(n_ngbs_dmi * 3).reshape(-1, 3)
176179
r_vec = np.zeros_like(dmi_vec)
177180
z_vec = np.array([0, 0, 1])
178181

@@ -193,7 +196,7 @@ def compute_DMI_vectors(self, nneighbours):
193196

194197
# Here we compute the cross product with the unitary z vector
195198
# and normalise
196-
for j in range(nneighbours):
199+
for j in range(n_ngbs_dmi):
197200
dmi_vec[j] = np.cross(r_vec[j], z_vec)
198201
dmi_vec[j] /= np.linalg.norm(dmi_vec[j])
199202

fidimag/atomistic/energy.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def setup(self, mesh, spin, mu_s):
2222
self.nz = mesh.nz
2323
self.spin = spin
2424
self.n = mesh.n
25+
self.n_ngbs = mesh.n_ngbs
2526
self.mesh_type = mesh.mesh_type
2627

2728
self.total_energy = 0

fidimag/atomistic/exchange.py

Lines changed: 96 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -3,86 +3,6 @@
33
from .energy import Energy
44

55

6-
class UniformExchange(Energy):
7-
8-
"""
9-
10-
This class provides the Exchange Interaction energy term for a
11-
homogeneous material, defined as
12-
13-
__ -> ->
14-
E = - \ J_ij S_i * S_j
15-
/__
16-
<i, j>
17-
i != j
18-
19-
where J_ij is the exchange tensor, S_i and S_j are the total spin vectors
20-
at the i-th and j-th lattice sites, and <i, j> means counting the
21-
interaction between neighbouring spins only once (notice that there is no
22-
factor of 2 associated with J)
23-
24-
This class only computes a uniform exchange field, thus J_ij is a
25-
diagonal tensor with constant magnitude, J_ij -> J
26-
27-
28-
OPTIONAL ARGUMENTS: -------------------------------------------------------
29-
30-
J :: A number for the exchange tensor magnitude
31-
name :: Interaction name
32-
33-
USAGE: --------------------------------------------------------------------
34-
35-
For a homogeneous material, it can be specified in a simulation object
36-
*Sim* as
37-
38-
Sim.add(UniformExchange(J))
39-
40-
where J is a float.
41-
42-
"""
43-
44-
def __init__(self, J=0, name='UniformExchange'):
45-
self.J = J
46-
self.name = name
47-
48-
self.Jx = self.J
49-
self.Jy = self.J
50-
self.Jz = self.J
51-
self.jac = True
52-
53-
def compute_field(self, t=0, spin=None):
54-
55-
if spin is not None:
56-
m = spin
57-
else:
58-
m = self.spin
59-
60-
clib.compute_exchange_field(m,
61-
self.field,
62-
self.energy,
63-
self.Jx,
64-
self.Jy,
65-
self.Jz,
66-
self.neighbours,
67-
self.n)
68-
69-
return self.field * self.mu_s_inv
70-
71-
def compute_energy_directly(self):
72-
73-
energy = clib.compute_exchange_energy(self.spin,
74-
self.Jx,
75-
self.Jy,
76-
self.Jz,
77-
self.nx,
78-
self.ny,
79-
self.nz,
80-
self.xperiodic,
81-
self.yperiodic)
82-
83-
return energy
84-
85-
866
class Exchange(Energy):
877

888
"""
@@ -106,16 +26,25 @@ class Exchange(Energy):
10626
10727
OPTIONAL ARGUMENTS: -------------------------------------------------------
10828
109-
J_fun :: The exchange tensor which can be a number (same
110-
magnitude for every neighbour at every lattice site)
111-
or a space dependent function that returns 6
29+
J :: The exchange tensor which can be:
30+
31+
1. A number (same exchange magnitude for every
32+
neighbour at every lattice site)
33+
34+
2. A space dependent function that returns 6
11235
components, one for every nearest neighbour (NN).
11336
For a square lattice the NNs are defined in 3D as:
11437
[-x +x -y +y -z +z], thus the exchange components
11538
are specified in that order. In a hexagonal lattice
11639
the NNs are only in a 2D plane as the cardinal
11740
positions: [W E NE SW NW SE].
11841
42+
3. A list with N exchange constants, where N is the
43+
number of neighbours shells specified in the mesh.
44+
The list is in order, thus the 0th element is for
45+
the exchange constant of the 1st shell, etc.
46+
i.e. [J1, J2, J3, ...]
47+
11948
name :: Interaction name
12049
12150
USAGE: --------------------------------------------------------------------
@@ -139,43 +68,114 @@ def my_exchange(pos):
13968
14069
Sim.add(Exchange(my_exchange))
14170
71+
DEV NOTES: ----------------------------------------------------------------
72+
73+
* If a float or int is passed as the Exchange constant J, this class will
74+
use the *compute_exchange_field* C function (see lib/exch.c), which assumes
75+
a uniform exchange. This C function does not take J as an array thus it
76+
will not call array elements to compute the neighbours contribution but
77+
it will only use a constant, thus it should be faster
78+
79+
* If option 3. is pecified for the J parameter, this class will call the
80+
full exchange calculation function from the C library
14281
14382
"""
14483

145-
def __init__(self, J_fun, name='Exchange'):
146-
self.J_fun = J_fun
84+
def __init__(self, J, name='Exchange'):
85+
self.J = J
14786
self.name = name
14887
self.jac = False
14988

15089
def setup(self, mesh, spin, mu_s):
15190
super(Exchange, self).setup(mesh, spin, mu_s)
15291

153-
self._J = np.zeros(self.neighbours.shape)
92+
# Uniform exchange ----------------------------------------------------
93+
if isinstance(self.J, (int, float)):
94+
self.Jx = float(self.J)
95+
self.Jy = float(self.J)
96+
self.Jz = float(self.J)
97+
98+
self.compute_field = self.compute_field_uniform
15499

155-
if isinstance(self.J_fun, (int, float)):
156-
self._J[:, :] = self.J_fun
157-
elif hasattr(self.J_fun, '__call__'):
100+
# Spatially resolved exchange -----------------------------------------
101+
# TODO: Add option to pass numpy arrays
102+
elif hasattr(self.J, '__call__'):
103+
self._J = np.zeros(self.neighbours.shape)
158104
n = self.mesh.n
159105
for i in range(n):
160-
value = self.J_fun(self.coordinates[i])
106+
value = self.J(self.coordinates[i])
161107
if len(value) == 6:
162108
self._J[i, :] = value[:]
163109
else:
164110
raise Exception('The given spatial function for J is not acceptable!')
165111
pass
166112

167-
def compute_field(self, t=0, spin=None):
113+
self.compute_field = self.compute_field_spatial
114+
115+
# Full exchange calculation (beyond nearest neighbours) ---------------
116+
# n_shells should not be larger than 8 (checked in the mesh class)
117+
elif (isinstance(self.J, (list, np.ndarray)) and
118+
len(self.J) == self.mesh.n_shells):
119+
self._J = np.zeros(9)
120+
for i in range(len(self.J)):
121+
self._J[i] = float(self.J[i])
168122

169-
if spin is not None:
170-
m = spin
171-
else:
172-
m = self.spin
123+
self.compute_field = self.compute_field_full
124+
125+
def compute_field_spatial(self, t=0, spin=None):
126+
127+
m = spin if spin is not None else self.spin
173128

174129
clib.compute_exchange_field_spatial(m,
175130
self.field,
176131
self.energy,
177132
self._J,
178133
self.neighbours,
179-
self.n)
134+
self.n,
135+
self.n_ngbs
136+
)
180137

181138
return self.field * self.mu_s_inv
139+
140+
def compute_field_uniform(self, t=0, spin=None):
141+
142+
m = spin if spin is not None else self.spin
143+
144+
clib.compute_exchange_field(m,
145+
self.field,
146+
self.energy,
147+
self.Jx,
148+
self.Jy,
149+
self.Jz,
150+
self.neighbours,
151+
self.n,
152+
self.n_ngbs
153+
)
154+
155+
return self.field * self.mu_s_inv
156+
157+
def compute_field_full(self, t=0, spin=None):
158+
159+
m = spin if spin is not None else self.spin
160+
161+
clib.compute_full_exchange_field(m, self.field, self.energy,
162+
self._J,
163+
self.neighbours,
164+
self.n, self.mesh.n_ngbs,
165+
self.mesh.n_shells,
166+
self.mesh._n_ngbs_shell,
167+
self.mesh._sum_ngbs_shell
168+
)
169+
170+
return self.field * self.mu_s_inv
171+
172+
173+
class UniformExchange(Exchange):
174+
175+
"""
176+
For compatibility we leave this class which was merged into the
177+
Exchange class
178+
"""
179+
180+
def __init__(self, J=0, name='UniformExchange'):
181+
super(UniformExchange, self).__init__(J, name=name)

0 commit comments

Comments
 (0)