Skip to content

Commit 305beed

Browse files
authored
Merge pull request #49 from computationalmodelling/sk_number
Methods for the calculation of the skyrmion number
2 parents 9a39415 + f38fd8b commit 305beed

File tree

5 files changed

+325
-16
lines changed

5 files changed

+325
-16
lines changed

fidimag/atomistic/lib/clib.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ void normalise(double *m, int *pins, int n);
7878
double skyrmion_number(double *spin, double *charge,
7979
int nx, int ny, int nz, int *ngbs);
8080

81+
double skyrmion_number_BergLuscher(double *spin, double *charge,
82+
int nx, int ny, int nz, int *ngbs);
83+
8184
void compute_guiding_center(double *spin, int nx, int ny, int nz, double *res);
8285

8386
void compute_px_py_c(double *spin, int nx, int ny, int nz,

fidimag/atomistic/lib/clib.pyx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ cdef extern from "clib.h":
1212
double skyrmion_number(double *spin, double *charge,
1313
int nx, int ny, int nz, int *ngbs)
1414

15+
16+
double skyrmion_number_BergLuscher(double *spin, double *charge,
17+
int nx, int ny, int nz, int *ngbs)
18+
1519
void compute_guiding_center(double *spin, int nx, int ny, int nz,
1620
double *res)
1721

@@ -83,6 +87,15 @@ def compute_skyrmion_number(np.ndarray[double, ndim=1, mode="c"] spin,
8387

8488
return skyrmion_number(&spin[0], &charge[0], nx, ny, nz, &ngbs[0,0])
8589

90+
91+
def compute_skyrmion_number_BergLuscher(np.ndarray[double, ndim=1, mode="c"] spin,
92+
np.ndarray[double, ndim=1, mode="c"] charge,
93+
nx, ny, nz,
94+
np.ndarray[int, ndim=2, mode="c"] ngbs
95+
):
96+
97+
return skyrmion_number_BergLuscher(&spin[0], &charge[0], nx, ny, nz, &ngbs[0,0])
98+
8699
def compute_RxRy(np.ndarray[double, ndim=1, mode="c"] spin,
87100
nx, ny, nz):
88101

fidimag/atomistic/lib/util.c

Lines changed: 171 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#include "clib.h"
2+
#include "math.h"
3+
#include "complex.h"
24

35
//compute the S \cdot (S_i \times S_j)
46
inline double volume(double S[3], double Si[3], double Sj[3]) {
@@ -17,7 +19,7 @@ double skyrmion_number(double *spin, double *charge,
1719
*
1820
* The *spin array is the vector field for a two dimensional
1921
* lattice with dimensions nx * ny
20-
* (we can take a slice of a bulk from Python and pass it here,
22+
* (we can take a slice of a bulk from Python and pass it here,
2123
* remember to do the ame for the neighbours matrix)
2224
* The array follows the order:
2325
* [Sx0 Sy0 Sz0 Sx1 Sy1 Sz1 ... ]
@@ -50,17 +52,17 @@ double skyrmion_number(double *spin, double *charge,
5052
*
5153
* Then, we use the follwing expression:
5254
*
53-
* Q = S_i \dot ( S_{i+1} \times S_{j+1} )
55+
* Q = S_i \dot ( S_{i+1} \times S_{j+1} )
5456
* + S_i \dot ( S_{i-1} \times S_{j-1} )
5557
*
5658
* This expression is based on the publication PRL 108, 017601 (2012)
5759
* where Q is called "finite spin chirality". The idea comes from
5860
* discrete chiral quantities in Hall effect studies. For example, at
5961
* the end of page 3 in Rep. Prog. Phys. 78 (2015) 052502, it
6062
* is argued:
61-
* scalar chirality (...) , which measures the volume enclosed
63+
* scalar chirality (...) , which measures the volume enclosed
6264
* by the three spins of the elementary triangle and, similarly to
63-
* (the vector chirlity) is sensitive to the sense of spin's
65+
* (the vector chirlity) is sensitive to the sense of spin's
6466
* rotation in the x–y plane
6567
*
6668
* Hence we are taking the triangles formed by (i, i+1, j+1)
@@ -72,12 +74,12 @@ double skyrmion_number(double *spin, double *charge,
7274
*
7375
* Recently, other ways to calculate a discrete skyrmion number have
7476
* been proposed: http://arxiv.org/pdf/1601.08212.pdf
75-
* Phys. Rev. B 93, 024417
76-
*
77+
* Phys. Rev. B 93, 024417
78+
*
7779
* also based on using three spins using triangles. This could be
78-
* useful for applying to a hexagonal lattice in the future.
80+
* useful for applying to a hexagonal lattice in the future.
7981
*
80-
*/
82+
*/
8183

8284
int i, j;
8385
int index, id;
@@ -91,7 +93,7 @@ double skyrmion_number(double *spin, double *charge,
9193
for (i = 0; i < nxy; i++) {
9294
index = 3 * i;
9395

94-
/* The starting index of the nearest neighbours for the
96+
/* The starting index of the nearest neighbours for the
9597
* i-th spin */
9698
int id_nn = 6 * i;
9799

@@ -156,6 +158,166 @@ double skyrmion_number(double *spin, double *charge,
156158

157159
}
158160

161+
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+
*/
167+
double dp = 0;
168+
int i;
169+
170+
for(i = 0; i< 3; i++) dp += a[i] * b[i];
171+
return dp;
172+
}
173+
174+
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+
204+
double rho;
205+
double complex exp;
206+
double crossp[3];
207+
208+
crossp[0] = s2[1] * s3[2] - s2[2] * s3[1];
209+
crossp[1] = s2[2] * s3[0] - s2[0] * s3[2];
210+
crossp[2] = s2[0] * s3[1] - s2[1] * s3[0];
211+
212+
rho = sqrt(2 * (1 + dot(&s1[0], &s2[0]))
213+
* (1 + dot(&s2[0], &s3[0]))
214+
* (1 + dot(&s3[0], &s1[0]))
215+
);
216+
217+
exp = (1 + dot(&s1[0], &s2[0])
218+
+ dot(&s2[0], &s3[0])
219+
+ dot(&s3[0], &s1[0])
220+
+ I * dot(&s1[0], &crossp[0])
221+
) / rho;
222+
223+
return 2 * cimagl(clog(exp)) / (4 * WIDE_PI);
224+
225+
}
226+
227+
double skyrmion_number_BergLuscher(double *spin, double *charge,
228+
int nx, int ny, int nz, int *ngbs) {
229+
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;
287+
288+
// Sweep through every lattice site
289+
for(i = 0; i < n; i++){
290+
291+
// x-y-z components for the i-th spin start at:
292+
spin_index = 3 * i;
293+
294+
// Reset the charge array
295+
charge[i] = 0;
296+
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:
300+
if(ngbs[6 * i] > 0 && ngbs[6 * i + 2] > 0){
301+
charge[i] += compute_BergLuscher_angle(&spin[spin_index],
302+
&spin[3 * ngbs[6 * i]],
303+
&spin[3 * ngbs[6 * i + 2]]
304+
);
305+
}
306+
307+
// Triangle: [i j=1 j=3]
308+
if(ngbs[6 * i + 1] > 0 && ngbs[6 * i + 3] > 0){
309+
charge[i] += compute_BergLuscher_angle(&spin[spin_index],
310+
&spin[3 * ngbs[6 * i + 1]],
311+
&spin[3 * ngbs[6 * i + 3]]
312+
);
313+
}
314+
315+
total_sum += charge[i];
316+
}
317+
318+
return total_sum;
319+
}
320+
159321
//compute the first derivative respect to x and for the whole mesh
160322
//assume 2d pbc is used
161323
void compute_px_py_c(double *spin, int nx, int ny, int nz, double *px, double *py){

fidimag/atomistic/llg.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ def __init__(self, mesh, name='unnamed', use_jac=False):
5252
'get': lambda sim: sim.compute_spin_error(),
5353
'header': 'm_error'}
5454

55-
self.saver.entities['skx_num'] = {
56-
'unit': '<>',
57-
'get': lambda sim: sim.skyrmion_number(),
58-
'header': 'skx_num'}
55+
# self.saver.entities['skx_num'] = {
56+
# 'unit': '<>',
57+
# 'get': lambda sim: sim.skyrmion_number(),
58+
# 'header': 'skx_num'}
5959

6060
self.saver.update_entity_order()
6161

@@ -289,12 +289,44 @@ def compute_energy(self):
289289

290290
return energy
291291

292-
def skyrmion_number(self):
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.
299+
300+
Calling this function will fill the _skx_number array with the skyrmion
301+
number density per lattice site.
302+
303+
It is important to mention that this calculation only works for a
304+
2D layer in the XY plane.
305+
306+
See the corresponding C code at fidimag/atomistic/lib/util.c for a
307+
detailed documentation about the methods.
308+
309+
"""
293310
nx = self.mesh.nx
294311
ny = self.mesh.ny
295312
nz = self.mesh.nz
296-
number = clib.compute_skyrmion_number(
297-
self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours)
313+
314+
if method == 'FiniteSpinChirality':
315+
if self.mesh.mesh_type == 'cuboid':
316+
number = clib.compute_skyrmion_number(
317+
self.spin, self._skx_number, nx, ny, nz,
318+
self.mesh.neighbours)
319+
else:
320+
raise ValueError('FiniteSpinChirality method only'
321+
' defined for cuboid meshes')
322+
323+
elif method == 'BergLuscher':
324+
number = clib.compute_skyrmion_number_BergLuscher(
325+
self.spin, self._skx_number, nx, ny, nz,
326+
self.mesh.neighbours)
327+
else:
328+
raise ValueError('Specify a valid method')
329+
298330
return number
299331

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

0 commit comments

Comments
 (0)