33from .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-
866class 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