Skip to content

Commit f7af46d

Browse files
author
davidcorteso
committed
Documented and added methods for the calculation of discrete skyrmion number or topological charges. The LLG code was modified to check which meshes are appropriate for every method
1 parent 4645a65 commit f7af46d

File tree

2 files changed

+131
-15
lines changed

2 files changed

+131
-15
lines changed

fidimag/atomistic/lib/util.c

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ double skyrmion_number(double *spin, double *charge,
159159
}
160160

161161
double dot(double *a, double *b){
162+
/* Dot product for vectors of 3 components, given by arrays a and b. If
163+
* pointers are passed, it uses the first three components starting from
164+
* that place in memory, i.e. if we pass &a[2], where a = {0, 1, 2, 3, 4},
165+
* the dot product uses {2, 3, 4} as a vector (same for b)
166+
*/
162167
double dp = 0;
163168
int i;
164169

@@ -167,6 +172,35 @@ double dot(double *a, double *b){
167172
}
168173

169174
double compute_BergLuscher_angle(double *s1, double *s2, double *s3){
175+
176+
/* Compute the spherical angle given by the neighbouring spin 3-vectors s1,
177+
* s2 and s3 (these are arrays that can be also passed as pointers; see the
178+
* -dot- function). The spherical angle Omega, defined by the
179+
* vectors in a unit sphere, is computed using the imaginary exponential
180+
* defined by Berg and Luscher [Nucl Phys B 190, 412 (1981)]:
181+
182+
* exp (i sigma Omega) = 1 + s1 * s2 + s2 * s3 + s3 * s1 + i s1 * (s2 X s3)
183+
* ------------------------------------------------
184+
* 2 (1 + s1 * s2) (1 + s2 * s3) (1 + s3 * s1)
185+
186+
* The denominator is a normalisation factor which we call rho, and i
187+
* stands for an imaginary number in the numerator. The factor sigma is an
188+
* orientation given by sign(s1 * s2 X s3), however, we do not use it since
189+
* we take counter clock wise directions for the (s1, s2, s3) triangle of
190+
* spins, as pointed out by Yin et al. [PRB 93, 174403 (2016)].
191+
*
192+
* Therefore we use a complex logarithm:
193+
194+
* clog( r * exp(i theta) ) = r + i theta
195+
196+
* to calculate the angle Omega, since the clog is well defined in the
197+
* [-PI, PI] range, giving the correct sign for the topological number (we
198+
* could also use the arcsin when decomposing the exp).
199+
*
200+
* Notice we normalise the angle by a 4 PI factor
201+
*
202+
*/
203+
170204
double rho;
171205
double complex exp;
172206
double crossp[3];
@@ -180,8 +214,8 @@ double compute_BergLuscher_angle(double *s1, double *s2, double *s3){
180214
* (1 + dot(&s3[0], &s1[0]))
181215
);
182216

183-
exp = (1 + dot(&s1[0], &s2[0])
184-
+ dot(&s2[0], &s3[0])
217+
exp = (1 + dot(&s1[0], &s2[0])
218+
+ dot(&s2[0], &s3[0])
185219
+ dot(&s3[0], &s1[0])
186220
+ I * dot(&s1[0], &crossp[0])
187221
) / rho;
@@ -193,23 +227,84 @@ double compute_BergLuscher_angle(double *s1, double *s2, double *s3){
193227
double skyrmion_number_BergLuscher(double *spin, double *charge,
194228
int nx, int ny, int nz, int *ngbs) {
195229

196-
int n = nx * ny * nz;
197-
int i, spin_index;
198-
double total_sum = 0;
230+
/* Compute the topological charge (or skyrmion number) by adding triangles
231+
* of neighbouring spins for every lattice site, which cover triangle areas
232+
* in a unit sphere (i.e. we map the lattice area into a unit sphere
233+
* surface, using a triangulation). The exponential that defines every
234+
* spherical angle was firstly mentioned by Berg and Luscher [Nucl Phys B
235+
* 190, 412 (1981)] for a discrete square lattice but we generalise it here
236+
* for hexagonal crystals.
237+
*
238+
* NEIGHBOURS DEFINITION:
239+
*
240+
* *ngbs is the array with the neighbours information for every lattice
241+
* site. For cuboid meshes, the array is like:
242+
*
243+
* NN: j =0 j=1 ... j=0 j=1 ...
244+
* [ 0-x, 0+x, 0-y, 0+y, 0-z, 0+z, 1-x, 1+x, 1-y, ... ]
245+
* spin: i=6 * 0 i=6 * 1
246+
* ...
247+
*
248+
* where 0-y is the index of the neighbour of the 0th spin, in the -y
249+
* direction, for example, so the neighbours of the i-th spin start at the
250+
* (6 * i) position of the array. This is similar for hexagonal meshes.
251+
*
252+
* For a cuboid mesh, for the i-th spin, we generate the nearest ngbs
253+
* triangles using the triangles: [i, j=1, j=3] , [i, j=0, j=2]
254+
* +x +y -x -y
255+
* i.e. we cover the top right and bottom left areas ( we could also use
256+
* the top left and bottom right)
257+
*
258+
* For a hexagonal mesh, for the i-th spin, we generate the nearest ngbs
259+
* triangles using the triangles: [i, j=0, j=2] , [i, j=0, j=2]
260+
* E NE W SW
261+
* (East) ...
262+
* whose area covers a unit cell in a hexagonal crystal arrangement.
263+
*
264+
*
265+
* Since the neighbours agree in indexes we can use the same function
266+
* for both meshes.
267+
*
268+
* ------------------------------------------------------------------------
269+
*
270+
* Having the triangles defined, we compute the spherical triangle area
271+
* spanned by the three spins using the compute_BergLuscher_angle function.
272+
*
273+
* The total topological charge Q is computed summing all triangles:
274+
* __
275+
* Q = \ 1 [ Omega(S_i, S_0, S_2) + Omega(S_i, S_1, S_3) ]
276+
* /__ ---
277+
* 4 PI
278+
* i
279+
*
280+
* ehich are normalised by 4 PI, so we get an integer number for the number
281+
* of times the unit sphere is covered by the spin directions in every
282+
* triangle, e.g. if we have two skyrmions we get approximately Q = 2.
283+
*
284+
*/
285+
286+
int n = nx * ny * nz; int i, spin_index; double total_sum = 0;
199287

288+
// Sweep through every lattice site
200289
for(i = 0; i < n; i++){
201290

291+
// x-y-z components for the i-th spin start at:
202292
spin_index = 3 * i;
203293

294+
// Reset the charge array
204295
charge[i] = 0;
205296

297+
// Compute the spherical triangle area for the triangle formed
298+
// by the i-th spin and neighbours 0 and 2, i.e.
299+
// [i j=0 j=2]. First check that the NNs exist:
206300
if(ngbs[6 * i] > 0 && ngbs[6 * i + 2] > 0){
207301
charge[i] += compute_BergLuscher_angle(&spin[spin_index],
208302
&spin[3 * ngbs[6 * i]],
209303
&spin[3 * ngbs[6 * i + 2]]
210304
);
211305
}
212306

307+
// Triangle: [i j=1 j=3]
213308
if(ngbs[6 * i + 1] > 0 && ngbs[6 * i + 3] > 0){
214309
charge[i] += compute_BergLuscher_angle(&spin[spin_index],
215310
&spin[3 * ngbs[6 * i + 1]],

fidimag/atomistic/llg.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -289,20 +289,41 @@ def compute_energy(self):
289289

290290
return energy
291291

292-
def skyrmion_number(self):
293-
nx = self.mesh.nx
294-
ny = self.mesh.ny
295-
nz = self.mesh.nz
296-
number = clib.compute_skyrmion_number(
297-
self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours)
298-
return number
292+
def skyrmion_number(self, method='FiniteSpinChirality'):
293+
"""
294+
Calculate the skyrmion number using different methods:
295+
296+
method :: 'FiniteSpinChirality', 'BergLuscher'
297+
298+
which are specifically defined for discrete spin lattices.
299299
300-
def skyrmion_number_BergLuscher(self):
300+
Calling this function will fill the _skx_number array with the skyrmion
301+
number density per lattice site.
302+
303+
See the corresponding C code at fidimag/atomistic/lib/util.c for a
304+
detailed documentation about the methods.
305+
306+
"""
301307
nx = self.mesh.nx
302308
ny = self.mesh.ny
303309
nz = self.mesh.nz
304-
number = clib.compute_skyrmion_number_BergLuscher(
305-
self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours)
310+
311+
if method == 'FiniteSpinChirality':
312+
if self.mesh.mesh_type == 'cuboid':
313+
number = clib.compute_skyrmion_number(
314+
self.spin, self._skx_number, nx, ny, nz,
315+
self.mesh.neighbours)
316+
else:
317+
raise ValueError('FiniteSpinChirality method only'
318+
' defined for cuboid meshes')
319+
320+
elif method == 'BergLuscher':
321+
number = clib.compute_skyrmion_number_BergLuscher(
322+
self.spin, self._skx_number, nx, ny, nz,
323+
self.mesh.neighbours)
324+
else:
325+
raise ValueError('Specify a valid method')
326+
306327
return number
307328

308329
def spin_at(self, i, j, k):

0 commit comments

Comments
 (0)