Skip to content

Commit

Permalink
Merge pull request #152 from JaGeo/master
Browse files Browse the repository at this point in the history
Validators and Jobs for Lobster
  • Loading branch information
shyuep authored Apr 27, 2020
2 parents 95030ea + 9e3bdcb commit f9b4b8e
Show file tree
Hide file tree
Showing 46 changed files with 39,360 additions and 0 deletions.
12 changes: 12 additions & 0 deletions custodian/lobster/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from __future__ import unicode_literals

"""
This package implements Lobster Jobs and Error Handlers.
"""

__author__ = "Janine George, Guido Petretto"
__version__ = "0.1"
__maintainer__ = "Janine George, Guido Petretto"
__email__ = "[email protected]"
__status__ = "Production"
__date__ = "27/4/20"
91 changes: 91 additions & 0 deletions custodian/lobster/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import os

from custodian.custodian import Validator
from pymatgen.io.lobster import Lobsterout

""" This module implements specific error handler for Lobster runs. """

__author__ = "Janine George, Guido Petretto"
__copyright__ = "Copyright 2020, The Materials Project"
__version__ = "0.1"
__maintainer__ = "Janine George"
__email__ = "[email protected]"
__date__ = "April 27, 2020"


class EnoughBandsValidator(Validator):
"""
validates if enough bands for COHP calculation are available
"""

def __init__(self, output_filename: str = "lobsterout"):
"""
Args:
output_filename: filename of output file, usually lobsterout
"""
self.output_filename = output_filename

def check(self) -> bool:
"""
checks if the VASP calculation had enough bands
Returns:
(bool) if True, too few bands have been applied
"""
# checks if correct number of bands is available
try:
with open(self.output_filename) as f:
data = f.read()
return 'You are employing too few bands in your PAW calculation.' in data
except OSError:
return False


class LobsterFilesValidator(Validator):
"""
Check for existence of some of the files that lobster
normally create upon running.
Check if lobster terminated normally by looking for finished
"""

def __init__(self):
pass

def check(self) -> bool:
for vfile in ["lobsterout"]:
if not os.path.exists(vfile):
return True
with open("lobsterout") as f:
data = f.read()
return ('finished' not in data)


class ChargeSpillingValidator(Validator):
"""
Check if spilling is below certain threshold!
"""

def __init__(self, output_filename: str = 'lobsterout', charge_spilling_limit: float = 0.05):
"""
Args:
output_filename: filename of the output file of lobter, usually lobsterout
charge_spilling_limit: limit of the charge spilling that will be considered okay
"""

self.output_filename = output_filename
self.charge_spilling_limit = charge_spilling_limit

def check(self) -> bool:
# open lobsterout and find charge spilling

if os.path.exists(self.output_filename):
lobsterout = Lobsterout(self.output_filename)
if lobsterout.chargespilling[0] > self.charge_spilling_limit:
return True
if len(lobsterout.chargespilling) > 1:
if lobsterout.chargespilling[1] > self.charge_spilling_limit:
return True
return False
else:
return False
89 changes: 89 additions & 0 deletions custodian/lobster/jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import logging
import os
import shutil
import subprocess

from custodian.custodian import Job
from monty.io import zopen
from monty.shutil import compress_file

""" This module implements jobs for Lobster runs. """

__author__ = "Janine George, Guido Petretto"
__copyright__ = "Copyright 2020, The Materials Project"
__version__ = "0.1"
__maintainer__ = "Janine George"
__email__ = "[email protected]"
__date__ = "April 27, 2020"

LOBSTERINPUT_FILES = ["lobsterin"]
LOBSTEROUTPUT_FILES = ["lobsterout", "CHARGE.lobster", "COHPCAR.lobster", "COOPCAR.lobster", "DOSCAR.lobster",
"GROSSPOP.lobster", "ICOHPLIST.lobster", "ICOOPLIST.lobster", "lobster.out",
"projectionData.lobster"]

logger = logging.getLogger(__name__)


class LobsterJob(Job):
"""
Runs the Lobster Job
"""

def __init__(self, lobster_cmd: str, output_file: str = "lobsterout", stderr_file: str = "std_err_lobster.txt",
gzipped: bool = True, add_files_to_gzip=[], backup: bool = True):
"""
Args:
lobster_cmd: command to run lobster
output_file: usually lobsterout
stderr_file: standard output
gzipped: if True, Lobster files and add_files_to_gzip will be gzipped
add_files_to_gzip: list of files that should be gzipped
backup: if True, lobsterin will be copied to lobsterin.orig
"""
self.lobster_cmd = lobster_cmd
self.output_file = output_file
self.stderr_file = stderr_file
self.gzipped = gzipped
self.add_files_to_gzip = add_files_to_gzip
self.backup = backup

def setup(self):
"""
will backup lobster input files
"""
if self.backup:
for f in LOBSTERINPUT_FILES:
shutil.copy(f, "{}.orig".format(f))

def run(self):
"""
runs the job
"""
cmd = self.lobster_cmd

logger.info("Running {}".format(" ".join(cmd)))

with zopen(self.output_file, 'w') as f_std, \
zopen(self.stderr_file, "w", buffering=1) as f_err:
# use line buffering for stderr
p = subprocess.Popen(cmd, stdout=f_std, stderr=f_err)

return p

def postprocess(self):
"""
will gzip relevant files (won't gzip custodian.json and other output files from the cluster)
"""
if self.gzipped:
for file in LOBSTEROUTPUT_FILES:
if os.path.exists(file):
compress_file(file, compression="gz")
for file in LOBSTERINPUT_FILES:
if os.path.exists(file):
compress_file(file, compression="gz")
if self.backup:
if os.path.exists("lobsterin.orig"):
compress_file("lobsterin.orig", compression="gz")
for file in self.add_files_to_gzip:
compress_file(file, compression="gz")
Empty file.
83 changes: 83 additions & 0 deletions custodian/lobster/tests/test_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import os
import unittest

from custodian.lobster.handlers import ChargeSpillingValidator, EnoughBandsValidator, LobsterFilesValidator

# get location of module
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
test_files_lobster = os.path.join(TEST_DIR, '../../../test_files/lobster/lobsterouts')


class TestChargeSpillingValidator(unittest.TestCase):

def test_check_and_correct(self):
v = ChargeSpillingValidator(output_filename=os.path.join(test_files_lobster, "lobsterout.normal"))
self.assertFalse(v.check())

v2 = ChargeSpillingValidator(output_filename=os.path.join(test_files_lobster, "lobsterout.largespilling"))
self.assertTrue(v2.check())

v2b = ChargeSpillingValidator(output_filename=os.path.join(test_files_lobster, "lobsterout.largespilling_2"))
self.assertTrue(v2b.check())

v3 = ChargeSpillingValidator(output_filename=os.path.join(test_files_lobster, "nolobsterout", "lobsterout"))
self.assertFalse(v3.check())

v4 = ChargeSpillingValidator(output_filename=os.path.join(test_files_lobster, "no_spin", "lobsterout"))
self.assertFalse(v4.check())

def test_as_dict(self):
v = ChargeSpillingValidator(output_filename=os.path.join(test_files_lobster, "lobsterout.normal"))
d = v.as_dict()
v2 = ChargeSpillingValidator.from_dict(d)
self.assertIsInstance(v2, ChargeSpillingValidator)


class TestLobsterFilesValidator(unittest.TestCase):

def test_check_and_correct_1(self):
os.chdir(test_files_lobster)
v = LobsterFilesValidator()
self.assertFalse(v.check())

def test_check_and_correct_2(self):
os.chdir(os.path.join(test_files_lobster, "../lobsterins"))
v2 = LobsterFilesValidator()
self.assertTrue(v2.check())

def test_check_and_correct_3(self):
os.chdir(os.path.join(test_files_lobster, "crash"))
v3 = LobsterFilesValidator()
self.assertTrue(v3.check())

def test_as_dict(self):
os.chdir(test_files_lobster)
v = LobsterFilesValidator()
d = v.as_dict()
v2 = LobsterFilesValidator.from_dict(d)
self.assertIsInstance(v2, LobsterFilesValidator)


class TestEnoughBandsValidator(unittest.TestCase):

def test_check_and_correct(self):
v = EnoughBandsValidator(output_filename=os.path.join(test_files_lobster, "lobsterout.normal"))
self.assertFalse(v.check())

def test_check_and_correct2(self):
v2 = EnoughBandsValidator(output_filename=os.path.join(test_files_lobster, "lobsterout.nocohp"))
self.assertTrue(v2.check())

def test_check_and_correct3(self):
v3 = EnoughBandsValidator(output_filename=os.path.join(test_files_lobster, "nolobsterout/lobsterout"))
self.assertFalse(v3.check())

def test_as_dict(self):
v = EnoughBandsValidator(output_filename=os.path.join(test_files_lobster, "lobsterout.normal"))
d = v.as_dict()
v2 = EnoughBandsValidator.from_dict(d)
self.assertIsInstance(v2, EnoughBandsValidator)


if __name__ == '__main__':
unittest.main()
67 changes: 67 additions & 0 deletions custodian/lobster/tests/test_jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import os
import shutil
import unittest

from custodian.lobster.jobs import LobsterJob
from monty.os import cd
from monty.tempfile import ScratchDir

MODULE_DIR = os.path.dirname(os.path.abspath(__file__))
test_files_lobster2 = os.path.join(MODULE_DIR, '../../../test_files/lobster/lobsterins')
test_files_lobster3 = os.path.join(MODULE_DIR, '../../../test_files/lobster/vasp_lobster_output')

VASP_OUTPUT_FILES = ["OUTCAR", "vasprun.xml", "CHG", "CHGCAR", "CONTCAR", "INCAR", "KPOINTS", "POSCAR", "POTCAR",
"DOSCAR", "EIGENVAL", "IBZKPT", "OSZICAR", "PCDAT", "PROCAR", "REPORT", "WAVECAR", "XDATCAR"]


class LobsterJobTest(unittest.TestCase):
"""
similar to VaspJobTest
ommit test of run
"""

def test_to_from_dict(self):
v = LobsterJob(lobster_cmd="hello")
v2 = LobsterJob.from_dict(v.as_dict())
self.assertEqual(type(v2), type(v))
self.assertEqual(v2.lobster_cmd, "hello")

def test_setup(self):
with cd(test_files_lobster2):
with ScratchDir('.', copy_from_current_on_enter=True) as d:
# check if backup is done correctly
v = LobsterJob("hello", backup=True)
v.setup()
self.assertTrue(os.path.exists("lobsterin.orig"))
# check if backup id done correctly
with ScratchDir('.', copy_from_current_on_enter=True) as d:
v = LobsterJob("hello", backup=False)
v.setup()
self.assertFalse(os.path.exists("lobsterin.orig"))

def test_postprocess(self):
# test gzipped and zipping of additional files
with cd(os.path.join(test_files_lobster3)):
with ScratchDir('.', copy_from_current_on_enter=True) as d:
shutil.copy('lobsterin', 'lobsterin.orig')
v = LobsterJob("hello", gzipped=True, add_files_to_gzip=VASP_OUTPUT_FILES)
v.postprocess()
self.assertTrue(os.path.exists("WAVECAR.gz"))
self.assertTrue(os.path.exists("lobsterin.gz"))
self.assertTrue(os.path.exists("lobsterout.gz"))
self.assertTrue(os.path.exists("INCAR.gz"))
self.assertTrue(os.path.exists("lobsterin.orig.gz"))

with ScratchDir('.', copy_from_current_on_enter=True) as d:
shutil.copy('lobsterin', 'lobsterin.orig')
v = LobsterJob("hello", gzipped=False, add_files_to_gzip=VASP_OUTPUT_FILES)
v.postprocess()
self.assertTrue(os.path.exists("WAVECAR"))
self.assertTrue(os.path.exists("lobsterin"))
self.assertTrue(os.path.exists("lobsterout"))
self.assertTrue(os.path.exists("INCAR"))
self.assertTrue(os.path.exists("lobsterin.orig"))


if __name__ == '__main__':
unittest.main()
9 changes: 9 additions & 0 deletions test_files/lobster/lobsterins/lobsterin
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
COHPstartEnergy -15.0
COHPendEnergy 5.0
basisSet pbeVaspFit2015
cohpGenerator from 0.1 to 6.0 orbitalwise
gaussianSmearingWidth 0.05
saveProjectionToFile
basisfunctions K 3p 3s 4s
basisfunctions Sn 4d 5p 5s
basisfunctions O 2p 2s
9 changes: 9 additions & 0 deletions test_files/lobster/lobsterins/lobsterin2
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
COHPstartEnergy -15.0
COHPendEnergy 10.0
basisSet pbeVaspFit2015
cohpGenerator from 0.1 to 6.0 orbitalwise
gaussianSmearingWidth 0.05
saveProjectionToFile
basisfunctions K 3p 3s 4s
basisfunctions Sn 4d 5p 5s
basisfunctions O 2p 2s
Loading

0 comments on commit f9b4b8e

Please sign in to comment.