Skip to content

Commit 494ab60

Browse files
matthewkunerjanoshpre-commit-ci[bot]
authored
fix estimate_nbands function (#3149)
* modify formula to be correct, update tests * remove pytest approx calls for integers * pre-commit auto-fixes * add ability to correctly account for NPAR and noncollinear magnetism in estimating nbands * woohoo I just love linting * snake_case vars * dedup test comments * snake_case --------- Co-authored-by: Janosh Riebesell <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 2a9517a commit 494ab60

File tree

2 files changed

+46
-20
lines changed

2 files changed

+46
-20
lines changed

pymatgen/io/vasp/sets.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -677,21 +677,30 @@ def estimate_nbands(self) -> int:
677677
Estimate the number of bands that VASP will initialize a
678678
calculation with by default. Note that in practice this
679679
can depend on # of cores (if not set explicitly).
680+
Note that this formula is slightly different than the formula on the VASP wiki
681+
(as of July 2023). This is because the formula in the source code (`main.F`) is
682+
slightly different than what is on the wiki.
680683
"""
681-
nions = len(self.structure)
684+
n_ions = len(self.structure)
682685

683-
# from VASP's point of view, the number of magnetic atoms are
684-
# the number of atoms with non-zero magmoms, so use Incar as
685-
# source of truth
686-
nmag = len([m for m in self.incar["MAGMOM"] if not np.allclose(m, 0)])
686+
if self.incar["ISPIN"] == 1: # per the VASP source, if non-spin polarized ignore n_mag
687+
n_mag = 0
688+
else: # otherwise set equal to sum of total magmoms
689+
n_mag = sum(self.incar["MAGMOM"])
690+
n_mag = np.floor((n_mag + 1) / 2)
687691

688-
# by definition, if non-spin polarized ignore nmag
689-
if (not nmag) or (self.incar["ISPIN"] == 1):
690-
nbands = np.ceil(self.nelect / 2 + nions / 2)
691-
else:
692-
nbands = np.ceil(0.6 * self.nelect + nmag)
692+
possible_val_1 = np.floor((self.nelect + 2) / 2) + max(np.floor(n_ions / 2), 3)
693+
possible_val_2 = np.floor(self.nelect * 0.6)
694+
695+
n_bands = max(possible_val_1, possible_val_2) + n_mag
696+
697+
if self.incar.get("LNONCOLLINEAR") is True:
698+
n_bands = n_bands * 2
699+
700+
if n_par := self.incar.get("NPAR"):
701+
n_bands = (np.floor((n_bands + n_par - 1) / n_par)) * n_par
693702

694-
return int(nbands)
703+
return int(n_bands)
695704

696705
def __str__(self):
697706
return type(self).__name__

pymatgen/io/vasp/tests/test_sets.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -236,15 +236,6 @@ def test_nelect(self):
236236
struct = Structure(lattice, ["Si", "Si", "Fe"], coords)
237237
assert MITRelaxSet(struct).nelect == 16
238238

239-
# Test estimate of number of bands (function of nelect) with nmag>0
240-
assert MITRelaxSet(struct).estimate_nbands() == 13
241-
assert MPRelaxSet(struct).estimate_nbands() == 17
242-
243-
# Test estimate of number of bands (function of nelect) with nmag==0
244-
struct = Structure(lattice, ["Si", "Si", "Si"], coords)
245-
assert MITRelaxSet(struct).estimate_nbands() == 11
246-
assert MPRelaxSet(struct).estimate_nbands() == 11
247-
248239
# Check that it works even when oxidation states are present. Was a bug
249240
# previously.
250241
struct = Structure(lattice, ["Si4+", "Si4+", "Fe2+"], coords)
@@ -256,6 +247,32 @@ def test_nelect(self):
256247
assert MITRelaxSet(struct).nelect == 16
257248
assert MPRelaxSet(struct).nelect == 22
258249

250+
@skip_if_no_psp_dir
251+
def test_estimate_nbands(self):
252+
# estimate_nbands is a function of n_elect, n_ions, magmom, noncollinearity of magnetism, and n_par
253+
coords = [[0] * 3, [0.5] * 3, [0.75] * 3]
254+
lattice = Lattice.cubic(4)
255+
256+
# pure Si
257+
struct = Structure(lattice, ["Si", "Si", "Si"], coords)
258+
assert MITRelaxSet(struct).estimate_nbands() == 11
259+
assert MPRelaxSet(struct).estimate_nbands() == 11
260+
261+
# Si + Fe
262+
struct = Structure(lattice, ["Si", "Si", "Fe"], coords)
263+
assert MITRelaxSet(struct).estimate_nbands() == 15
264+
assert MPRelaxSet(struct).estimate_nbands() == 18
265+
266+
# Si + Fe with NPAR = 4
267+
uis = {"NPAR": 4}
268+
assert MITRelaxSet(struct, user_incar_settings=uis).estimate_nbands() == approx(16)
269+
assert MPRelaxSet(struct, user_incar_settings=uis).estimate_nbands() == approx(20)
270+
271+
# Si + Fe with noncollinear magnetism turned on
272+
uis = {"LNONCOLLINEAR": True}
273+
assert MITRelaxSet(struct, user_incar_settings=uis).estimate_nbands() == approx(30)
274+
assert MPRelaxSet(struct, user_incar_settings=uis).estimate_nbands() == approx(36)
275+
259276
@skip_if_no_psp_dir
260277
def test_get_incar(self):
261278
incar = self.mp_set.incar

0 commit comments

Comments
 (0)