Skip to content

Commit 0ebaa64

Browse files
Improve types for electronic_structure.{bandstructure/cohp} (#3873)
* add types for bandstructure * relocate magic methods to top * add some types * fix type errors in bandstructure * temp save * first run of cohp, mypy errors to fix * fix collection generation * add type `SpinLike` and case tweaks * reduce repetition for `__str__` of `IcohpValue` * simplify condition * reduce indentation level * clarify `translation` * clarify `list_num` and other docstrings * clarify `label` as str * more type and docstring improvements * fix unit test * fix most mypy errors * fix remaining mypy errors * add DEBUG tag * reduce code repetition * Need Confirm: set `translation` as tuple * pre-commit auto-fixes * more type clarify * clarify `num` argument * clarify docstring of `bandstructure` * more minor tweaks * clarify type of labels_dict * replace unnecessary single-item list extend with append * fix typo * relocate magic method * clarify type of `list_icohp` * remove unused type alias * revert undesired rename * replace more single item extend with append * simplify dict generation * fix downstream lobsterpy error * tweak module docstring * need confirm: allow efermi to be None * pre-commit auto-fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 380a81b commit 0ebaa64

File tree

11 files changed

+1187
-1032
lines changed

11 files changed

+1187
-1032
lines changed

src/pymatgen/electronic_structure/bandstructure.py

Lines changed: 324 additions & 297 deletions
Large diffs are not rendered by default.

src/pymatgen/electronic_structure/boltztrap2.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
1-
"""BoltzTraP2 is a python software interpolating band structures and
2-
computing materials properties from dft band structure using Boltzmann
3-
semi-classical transport theory.
4-
This module provides a pymatgen interface to BoltzTraP2.
1+
"""This module provides an interface to BoltzTraP2.
52
Some of the code is written following the examples provided in BoltzTraP2.
63
7-
BoltzTraP2 has been developed by Georg Madsen, Jesús Carrete, Matthieu J. Verstraete.
4+
BoltzTraP2 is a Python software interpolating band structures and
5+
computing materials properties from DFT band structure using Boltzmann
6+
semi-classical transport theory, developed by Georg Madsen, Jesús Carrete,
7+
Matthieu J. Verstraete.
88
99
https://gitlab.com/sousaw/BoltzTraP2
1010
https://www.sciencedirect.com/science/article/pii/S0010465518301632
1111
12-
References are:
13-
12+
References:
1413
Georg K.H.Madsen, Jesús Carrete, Matthieu J.Verstraete
1514
BoltzTraP2, a program for interpolating band structures and
1615
calculating semi-classical transport coefficients
17-
Computer Physics Communications 231, 140-145, 2018
16+
Computer Physics Communications 231, 140-145, 2018.
1817
1918
Madsen, G. K. H., and Singh, D. J. (2006).
2019
BoltzTraP. A code for calculating band-structure dependent quantities.
21-
Computer Physics Communications, 175, 67-71
20+
Computer Physics Communications, 175, 67-71.
2221
2322
Todo:
24-
- DONE: spin polarized bands
25-
- read first derivative of the eigenvalues from vasprun.xml (mommat)
26-
- handle magnetic moments (magmom)
23+
- Read first derivative of the eigenvalues from vasprun.xml (mommat)
24+
- Handle magnetic moments (MAGMOM)
2725
"""
2826

2927
from __future__ import annotations

src/pymatgen/electronic_structure/cohp.py

Lines changed: 525 additions & 423 deletions
Large diffs are not rendered by default.

src/pymatgen/electronic_structure/core.py

Lines changed: 91 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
if TYPE_CHECKING:
1414
from collections.abc import Sequence
1515

16+
from numpy.typing import NDArray
1617
from typing_extensions import Self
1718

1819
from pymatgen.core import Lattice
@@ -77,7 +78,7 @@ def __str__(self) -> str:
7778
return str(self.name)
7879

7980
@property
80-
def orbital_type(self):
81+
def orbital_type(self) -> OrbitalType:
8182
"""OrbitalType of an orbital."""
8283
return OrbitalType[self.name[0]]
8384

@@ -127,19 +128,18 @@ class Magmom(MSONable):
127128
"""
128129

129130
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),
131134
) -> None:
132135
"""
133136
Args:
134137
moment: magnetic moment, supplied as float or list/np.ndarray
135138
saxis: spin axis, supplied as list/np.ndarray, parameter will
136139
be converted to unit vector (default is [0, 0, 1]).
137-
138-
Returns:
139-
Magmom object
140140
"""
141-
# to init from another Magmom instance
142-
if isinstance(moment, Magmom):
141+
# Init from another Magmom instance
142+
if isinstance(moment, type(self)):
143143
saxis = moment.saxis # type: ignore[has-type]
144144
moment = moment.moment # type: ignore[has-type]
145145

@@ -153,6 +153,63 @@ def __init__(
153153

154154
self.saxis = saxis / np.linalg.norm(saxis)
155155

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+
156213
@classmethod
157214
def from_global_moment_and_saxis(cls, global_moment, saxis) -> Self:
158215
"""Convenience method to initialize Magmom from a given global
@@ -166,7 +223,7 @@ def from_global_moment_and_saxis(cls, global_moment, saxis) -> Self:
166223
global_moment: global magnetic moment
167224
saxis: desired saxis
168225
"""
169-
magmom = Magmom(global_moment)
226+
magmom = cls(global_moment)
170227
return cls(magmom.get_moment(saxis=saxis), saxis=saxis)
171228

172229
@classmethod
@@ -217,26 +274,26 @@ def get_moment(self, saxis=(0, 0, 1)):
217274
Returns:
218275
np.ndarray of length 3
219276
"""
220-
# transform back to moment with spin axis [0, 0, 1]
277+
# Transform back to moment with spin axis [0, 0, 1]
221278
trafo_mat_inv = self._get_transformation_matrix_inv(self.saxis)
222279
moment = np.matmul(self.moment, trafo_mat_inv)
223280

224-
# transform to new saxis
281+
# Transform to new saxis
225282
trafo_mat = self._get_transformation_matrix(saxis)
226283
moment = np.matmul(moment, trafo_mat)
227284

228-
# round small values to zero
285+
# Round small values to zero
229286
moment[np.abs(moment) < 1e-8] = 0
230287

231288
return moment
232289

233290
@property
234-
def global_moment(self) -> np.ndarray:
291+
def global_moment(self) -> NDArray:
235292
"""The magnetic moment defined in an arbitrary global reference frame as an np.array of length 3."""
236293
return self.get_moment()
237294

238295
@property
239-
def projection(self):
296+
def projection(self) -> float:
240297
"""Projects moment along spin quantization axis. Useful for obtaining
241298
collinear approximation for slightly non-collinear magmoms.
242299
@@ -245,16 +302,16 @@ def projection(self):
245302
"""
246303
return np.dot(self.moment, self.saxis)
247304

248-
def get_xyz_magmom_with_001_saxis(self):
305+
def get_xyz_magmom_with_001_saxis(self) -> Self:
249306
"""Get a Magmom in the default setting of saxis = [0, 0, 1] and
250307
the magnetic moment rotated as required.
251308
252309
Returns:
253310
Magmom
254311
"""
255-
return Magmom(self.get_moment())
312+
return type(self)(self.get_moment())
256313

257-
def get_00t_magmom_with_xyz_saxis(self):
314+
def get_00t_magmom_with_xyz_saxis(self) -> Self:
258315
"""For internal implementation reasons, in non-collinear calculations VASP prefers the following.
259316
260317
MAGMOM = 0 0 total_magnetic_moment
@@ -276,7 +333,7 @@ def get_00t_magmom_with_xyz_saxis(self):
276333
Returns:
277334
Magmom
278335
"""
279-
# reference direction gives sign of moment
336+
# Reference direction gives sign of moment
280337
# entirely arbitrary, there will always be a pathological case
281338
# where a consistent sign is not possible if the magnetic moments
282339
# are aligned along the reference direction, but in practice this
@@ -288,8 +345,8 @@ def get_00t_magmom_with_xyz_saxis(self):
288345
if np.dot(ref_direction, new_saxis) < 0:
289346
t = -t
290347
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)
293350

294351
@staticmethod
295352
def have_consistent_saxis(magmoms) -> bool:
@@ -339,14 +396,14 @@ def get_suggested_saxis(magmoms):
339396
Returns:
340397
np.ndarray of length 3
341398
"""
342-
# heuristic, will pick largest magmom as reference
399+
# Heuristic, will pick largest magmom as reference
343400
# useful for creating collinear approximations of
344401
# e.g. slightly canted magnetic structures
345402
# for fully collinear structures, will return expected
346403
# result
347404

348405
magmoms = [Magmom(magmom) for magmom in magmoms]
349-
# filter only non-zero magmoms
406+
# Filter only non-zero magmoms
350407
magmoms = [magmom for magmom in magmoms if abs(magmom)]
351408
magmoms.sort(reverse=True)
352409
if len(magmoms) > 0:
@@ -367,20 +424,24 @@ def are_collinear(magmoms) -> bool:
367424
if not Magmom.have_consistent_saxis(magmoms):
368425
magmoms = Magmom.get_consistent_set_and_saxis(magmoms)[0]
369426

370-
# convert to numpy array for convenience
427+
# Convert to numpy array for convenience
371428
magmoms = np.array([list(magmom) for magmom in magmoms])
372429
magmoms = magmoms[np.any(magmoms, axis=1)] # remove zero magmoms
373430
if len(magmoms) == 0:
374431
return True
375432

376-
# use first moment as reference to compare against
433+
# Use first moment as reference to compare against
377434
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
379436
num_ncl = np.count_nonzero(np.linalg.norm(np.cross(ref_magmom, magmoms), axis=1))
380437
return num_ncl == 0
381438

382439
@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:
384445
"""Obtaining a Magmom object from a magnetic moment provided
385446
relative to crystal axes.
386447
@@ -393,14 +454,14 @@ def from_moment_relative_to_crystal_axes(cls, moment: list[float], lattice: Latt
393454
Returns:
394455
Magmom
395456
"""
396-
# get matrix representing unit lattice vectors
457+
# Get matrix representing unit lattice vectors
397458
unit_m = lattice.matrix / np.linalg.norm(lattice.matrix, axis=1)[:, None]
398459
moment = np.matmul(list(moment), unit_m)
399-
# round small values to zero
460+
# Round small values to zero
400461
moment[np.abs(moment) < 1e-8] = 0
401462
return cls(moment)
402463

403-
def get_moment_relative_to_crystal_axes(self, lattice):
464+
def get_moment_relative_to_crystal_axes(self, lattice: Lattice):
404465
"""If scalar magmoms, moments will be given arbitrarily along z.
405466
Used for writing moments to magCIF file.
406467
@@ -410,67 +471,9 @@ def get_moment_relative_to_crystal_axes(self, lattice):
410471
Returns:
411472
vector as list of floats
412473
"""
413-
# get matrix representing unit lattice vectors
474+
# Get matrix representing unit lattice vectors
414475
unit_m = lattice.matrix / np.linalg.norm(lattice.matrix, axis=1)[:, None]
415-
# note np.matmul() requires numpy version >= 1.10
416476
moment = np.matmul(self.global_moment, np.linalg.inv(unit_m))
417-
# round small values to zero
477+
# Round small values to zero
418478
moment[np.abs(moment) < 1e-8] = 0
419479
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

Comments
 (0)