13
13
if TYPE_CHECKING :
14
14
from collections .abc import Sequence
15
15
16
+ from numpy .typing import NDArray
16
17
from typing_extensions import Self
17
18
18
19
from pymatgen .core import Lattice
@@ -77,7 +78,7 @@ def __str__(self) -> str:
77
78
return str (self .name )
78
79
79
80
@property
80
- def orbital_type (self ):
81
+ def orbital_type (self ) -> OrbitalType :
81
82
"""OrbitalType of an orbital."""
82
83
return OrbitalType [self .name [0 ]]
83
84
@@ -127,19 +128,18 @@ class Magmom(MSONable):
127
128
"""
128
129
129
130
def __init__ (
130
- self , moment : float | Sequence [float ] | np .ndarray | Magmom , saxis : Sequence [float ] = (0 , 0 , 1 )
131
+ self ,
132
+ moment : float | Sequence [float ] | NDArray | Magmom ,
133
+ saxis : Sequence [float ] = (0 , 0 , 1 ),
131
134
) -> None :
132
135
"""
133
136
Args:
134
137
moment: magnetic moment, supplied as float or list/np.ndarray
135
138
saxis: spin axis, supplied as list/np.ndarray, parameter will
136
139
be converted to unit vector (default is [0, 0, 1]).
137
-
138
- Returns:
139
- Magmom object
140
140
"""
141
- # to init from another Magmom instance
142
- if isinstance (moment , Magmom ):
141
+ # Init from another Magmom instance
142
+ if isinstance (moment , type ( self ) ):
143
143
saxis = moment .saxis # type: ignore[has-type]
144
144
moment = moment .moment # type: ignore[has-type]
145
145
@@ -153,6 +153,63 @@ def __init__(
153
153
154
154
self .saxis = saxis / np .linalg .norm (saxis )
155
155
156
+ def __getitem__ (self , key ):
157
+ return self .moment [key ]
158
+
159
+ def __iter__ (self ):
160
+ return iter (self .moment )
161
+
162
+ def __abs__ (self ) -> float :
163
+ return np .linalg .norm (self .moment )
164
+
165
+ def __eq__ (self , other : object ) -> bool :
166
+ """Equal if 'global' magnetic moments are the same, saxis can differ."""
167
+ try :
168
+ other_magmom = type (self )(other )
169
+ except (TypeError , ValueError ):
170
+ return NotImplemented
171
+
172
+ return np .allclose (self .global_moment , other_magmom .global_moment )
173
+
174
+ def __lt__ (self , other : Self ) -> bool :
175
+ return abs (self ) < abs (other )
176
+
177
+ def __neg__ (self ) -> Self :
178
+ return type (self )(- self .moment , saxis = self .saxis )
179
+
180
+ def __hash__ (self ) -> int :
181
+ return hash (tuple (self .moment ) + tuple (self .saxis ))
182
+
183
+ def __float__ (self ) -> float :
184
+ """Get magnitude of magnetic moment with a sign with respect to
185
+ an arbitrary direction.
186
+
187
+ Should give unsurprising output if Magmom is treated like a
188
+ scalar or if a set of Magmoms describes a collinear structure.
189
+
190
+ Implemented this way rather than simpler abs(self) so that
191
+ moments will have a consistent sign in case of e.g.
192
+ antiferromagnetic collinear structures without additional
193
+ user intervention.
194
+
195
+ However, should be used with caution for non-collinear
196
+ structures and might give nonsensical results except in the case
197
+ of only slightly non-collinear structures (e.g. small canting).
198
+
199
+ This approach is also used to obtain "diff" VolumetricDensity
200
+ in pymatgen.io.vasp.outputs.VolumetricDensity when processing
201
+ Chgcars from SOC calculations.
202
+ """
203
+ return float (self .get_00t_magmom_with_xyz_saxis ()[2 ])
204
+
205
+ def __str__ (self ) -> str :
206
+ return str (float (self ))
207
+
208
+ def __repr__ (self ) -> str :
209
+ if np .allclose (self .saxis , (0 , 0 , 1 )):
210
+ return f"Magnetic moment { self .moment } "
211
+ return f"Magnetic moment { self .moment } (spin axis = { self .saxis } )"
212
+
156
213
@classmethod
157
214
def from_global_moment_and_saxis (cls , global_moment , saxis ) -> Self :
158
215
"""Convenience method to initialize Magmom from a given global
@@ -166,7 +223,7 @@ def from_global_moment_and_saxis(cls, global_moment, saxis) -> Self:
166
223
global_moment: global magnetic moment
167
224
saxis: desired saxis
168
225
"""
169
- magmom = Magmom (global_moment )
226
+ magmom = cls (global_moment )
170
227
return cls (magmom .get_moment (saxis = saxis ), saxis = saxis )
171
228
172
229
@classmethod
@@ -217,26 +274,26 @@ def get_moment(self, saxis=(0, 0, 1)):
217
274
Returns:
218
275
np.ndarray of length 3
219
276
"""
220
- # transform back to moment with spin axis [0, 0, 1]
277
+ # Transform back to moment with spin axis [0, 0, 1]
221
278
trafo_mat_inv = self ._get_transformation_matrix_inv (self .saxis )
222
279
moment = np .matmul (self .moment , trafo_mat_inv )
223
280
224
- # transform to new saxis
281
+ # Transform to new saxis
225
282
trafo_mat = self ._get_transformation_matrix (saxis )
226
283
moment = np .matmul (moment , trafo_mat )
227
284
228
- # round small values to zero
285
+ # Round small values to zero
229
286
moment [np .abs (moment ) < 1e-8 ] = 0
230
287
231
288
return moment
232
289
233
290
@property
234
- def global_moment (self ) -> np . ndarray :
291
+ def global_moment (self ) -> NDArray :
235
292
"""The magnetic moment defined in an arbitrary global reference frame as an np.array of length 3."""
236
293
return self .get_moment ()
237
294
238
295
@property
239
- def projection (self ):
296
+ def projection (self ) -> float :
240
297
"""Projects moment along spin quantization axis. Useful for obtaining
241
298
collinear approximation for slightly non-collinear magmoms.
242
299
@@ -245,16 +302,16 @@ def projection(self):
245
302
"""
246
303
return np .dot (self .moment , self .saxis )
247
304
248
- def get_xyz_magmom_with_001_saxis (self ):
305
+ def get_xyz_magmom_with_001_saxis (self ) -> Self :
249
306
"""Get a Magmom in the default setting of saxis = [0, 0, 1] and
250
307
the magnetic moment rotated as required.
251
308
252
309
Returns:
253
310
Magmom
254
311
"""
255
- return Magmom (self .get_moment ())
312
+ return type ( self ) (self .get_moment ())
256
313
257
- def get_00t_magmom_with_xyz_saxis (self ):
314
+ def get_00t_magmom_with_xyz_saxis (self ) -> Self :
258
315
"""For internal implementation reasons, in non-collinear calculations VASP prefers the following.
259
316
260
317
MAGMOM = 0 0 total_magnetic_moment
@@ -276,7 +333,7 @@ def get_00t_magmom_with_xyz_saxis(self):
276
333
Returns:
277
334
Magmom
278
335
"""
279
- # reference direction gives sign of moment
336
+ # Reference direction gives sign of moment
280
337
# entirely arbitrary, there will always be a pathological case
281
338
# where a consistent sign is not possible if the magnetic moments
282
339
# are aligned along the reference direction, but in practice this
@@ -288,8 +345,8 @@ def get_00t_magmom_with_xyz_saxis(self):
288
345
if np .dot (ref_direction , new_saxis ) < 0 :
289
346
t = - t
290
347
new_saxis = - new_saxis
291
- return Magmom ([0 , 0 , t ], saxis = new_saxis )
292
- return Magmom (self )
348
+ return type ( self ) ([0 , 0 , t ], saxis = new_saxis )
349
+ return type ( self ) (self )
293
350
294
351
@staticmethod
295
352
def have_consistent_saxis (magmoms ) -> bool :
@@ -339,14 +396,14 @@ def get_suggested_saxis(magmoms):
339
396
Returns:
340
397
np.ndarray of length 3
341
398
"""
342
- # heuristic , will pick largest magmom as reference
399
+ # Heuristic , will pick largest magmom as reference
343
400
# useful for creating collinear approximations of
344
401
# e.g. slightly canted magnetic structures
345
402
# for fully collinear structures, will return expected
346
403
# result
347
404
348
405
magmoms = [Magmom (magmom ) for magmom in magmoms ]
349
- # filter only non-zero magmoms
406
+ # Filter only non-zero magmoms
350
407
magmoms = [magmom for magmom in magmoms if abs (magmom )]
351
408
magmoms .sort (reverse = True )
352
409
if len (magmoms ) > 0 :
@@ -367,20 +424,24 @@ def are_collinear(magmoms) -> bool:
367
424
if not Magmom .have_consistent_saxis (magmoms ):
368
425
magmoms = Magmom .get_consistent_set_and_saxis (magmoms )[0 ]
369
426
370
- # convert to numpy array for convenience
427
+ # Convert to numpy array for convenience
371
428
magmoms = np .array ([list (magmom ) for magmom in magmoms ])
372
429
magmoms = magmoms [np .any (magmoms , axis = 1 )] # remove zero magmoms
373
430
if len (magmoms ) == 0 :
374
431
return True
375
432
376
- # use first moment as reference to compare against
433
+ # Use first moment as reference to compare against
377
434
ref_magmom = magmoms [0 ]
378
- # magnitude of cross products != 0 if non-collinear with reference
435
+ # Magnitude of cross products != 0 if non-collinear with reference
379
436
num_ncl = np .count_nonzero (np .linalg .norm (np .cross (ref_magmom , magmoms ), axis = 1 ))
380
437
return num_ncl == 0
381
438
382
439
@classmethod
383
- def from_moment_relative_to_crystal_axes (cls , moment : list [float ], lattice : Lattice ) -> Self :
440
+ def from_moment_relative_to_crystal_axes (
441
+ cls ,
442
+ moment : list [float ],
443
+ lattice : Lattice ,
444
+ ) -> Self :
384
445
"""Obtaining a Magmom object from a magnetic moment provided
385
446
relative to crystal axes.
386
447
@@ -393,14 +454,14 @@ def from_moment_relative_to_crystal_axes(cls, moment: list[float], lattice: Latt
393
454
Returns:
394
455
Magmom
395
456
"""
396
- # get matrix representing unit lattice vectors
457
+ # Get matrix representing unit lattice vectors
397
458
unit_m = lattice .matrix / np .linalg .norm (lattice .matrix , axis = 1 )[:, None ]
398
459
moment = np .matmul (list (moment ), unit_m )
399
- # round small values to zero
460
+ # Round small values to zero
400
461
moment [np .abs (moment ) < 1e-8 ] = 0
401
462
return cls (moment )
402
463
403
- def get_moment_relative_to_crystal_axes (self , lattice ):
464
+ def get_moment_relative_to_crystal_axes (self , lattice : Lattice ):
404
465
"""If scalar magmoms, moments will be given arbitrarily along z.
405
466
Used for writing moments to magCIF file.
406
467
@@ -410,67 +471,9 @@ def get_moment_relative_to_crystal_axes(self, lattice):
410
471
Returns:
411
472
vector as list of floats
412
473
"""
413
- # get matrix representing unit lattice vectors
474
+ # Get matrix representing unit lattice vectors
414
475
unit_m = lattice .matrix / np .linalg .norm (lattice .matrix , axis = 1 )[:, None ]
415
- # note np.matmul() requires numpy version >= 1.10
416
476
moment = np .matmul (self .global_moment , np .linalg .inv (unit_m ))
417
- # round small values to zero
477
+ # Round small values to zero
418
478
moment [np .abs (moment ) < 1e-8 ] = 0
419
479
return moment
420
-
421
- def __getitem__ (self , key ):
422
- return self .moment [key ]
423
-
424
- def __iter__ (self ):
425
- return iter (self .moment )
426
-
427
- def __abs__ (self ):
428
- return np .linalg .norm (self .moment )
429
-
430
- def __eq__ (self , other : object ) -> bool :
431
- """Equal if 'global' magnetic moments are the same, saxis can differ."""
432
- try :
433
- other_magmom = Magmom (other )
434
- except (TypeError , ValueError ):
435
- return NotImplemented
436
-
437
- return np .allclose (self .global_moment , other_magmom .global_moment )
438
-
439
- def __lt__ (self , other ):
440
- return abs (self ) < abs (other )
441
-
442
- def __neg__ (self ):
443
- return Magmom (- self .moment , saxis = self .saxis )
444
-
445
- def __hash__ (self ) -> int :
446
- return hash (tuple (self .moment ) + tuple (self .saxis ))
447
-
448
- def __float__ (self ) -> float :
449
- """Get magnitude of magnetic moment with a sign with respect to
450
- an arbitrary direction.
451
-
452
- Should give unsurprising output if Magmom is treated like a
453
- scalar or if a set of Magmoms describes a collinear structure.
454
-
455
- Implemented this way rather than simpler abs(self) so that
456
- moments will have a consistent sign in case of e.g.
457
- antiferromagnetic collinear structures without additional
458
- user intervention.
459
-
460
- However, should be used with caution for non-collinear
461
- structures and might give nonsensical results except in the case
462
- of only slightly non-collinear structures (e.g. small canting).
463
-
464
- This approach is also used to obtain "diff" VolumetricDensity
465
- in pymatgen.io.vasp.outputs.VolumetricDensity when processing
466
- Chgcars from SOC calculations.
467
- """
468
- return float (self .get_00t_magmom_with_xyz_saxis ()[2 ])
469
-
470
- def __str__ (self ) -> str :
471
- return str (float (self ))
472
-
473
- def __repr__ (self ) -> str :
474
- if np .allclose (self .saxis , (0 , 0 , 1 )):
475
- return f"Magnetic moment { self .moment } "
476
- return f"Magnetic moment { self .moment } (spin axis = { self .saxis } )"
0 commit comments