Source code for vasp_step.vasp

# -*- coding: utf-8 -*-

"""Non-graphical part of the VASP step in a SEAMM flowchart"""

import importlib
import json
import logging
from pathlib import Path
import pprint  # noqa: F401
import sys
import time

import vasp_step
import molsystem
import seamm
from seamm_util import ureg, Q_, getParser, CompactJSONEncoder  # noqa: F401
import seamm_util.printing as printing
from seamm_util.printing import FormattedText as __

# In addition to the normal logger, two logger-like printing facilities are
# defined: "job" and "printer". "job" send output to the main job.out file for
# the job, and should be used very sparingly, typically to echo what this step
# will do in the initial summary of the job.
#
# "printer" sends output to the file "step.out" in this steps working
# directory, and is used for all normal output from this step.

logger = logging.getLogger(__name__)
job = printing.getPrinter()
printer = printing.getPrinter("VASP")

# Add this module's properties to the standard properties
path = importlib.resources.files("vasp_step") / "data"
csv_file = path / "properties.csv"
if path.exists():
    molsystem.add_properties_from_file(csv_file)


[docs] class VASP(seamm.Node): """ The non-graphical part of a VASP step in a flowchart. Attributes ---------- parser : configargparse.ArgParser The parser object. options : tuple It contains a two item tuple containing the populated namespace and the list of remaining argument strings. subflowchart : seamm.Flowchart A SEAMM Flowchart object that represents a subflowchart, if needed. parameters : VASPParameters The control parameters for VASP. See Also -------- TkVASP, VASP, VASPParameters """ def __init__( self, flowchart=None, title="VASP", namespace="org.molssi.seamm.vasp", extension=None, logger=logger, ): """A step for VASP in a SEAMM flowchart. You may wish to change the title above, which is the string displayed in the box representing the step in the flowchart. Parameters ---------- flowchart: seamm.Flowchart The non-graphical flowchart that contains this step. title: str The name displayed in the flowchart. namespace : str The namespace for the plug-ins of the subflowchart extension: None Not yet implemented logger : Logger = logger The logger to use and pass to parent classes Returns ------- None """ logger.debug(f"Creating VASP {self}") self.subflowchart = seamm.Flowchart( parent=self, name="VASP", namespace=namespace ) # yapf: disable super().__init__( flowchart=flowchart, title="VASP", extension=extension, module=__name__, logger=logger, ) # yapf: disable self._potential_metadata = None @property def version(self): """The semantic version of this module.""" return vasp_step.__version__ @property def git_revision(self): """The git version of this module.""" return vasp_step.__git_revision__ @property def potential_metadata(self): """The metadata data for the potentials.""" # Make sure we have the information about the PAW potentials if self._potential_metadata is None: directory = Path("~/SEAMM/Parameters/VASP").expanduser() index = directory / "index.json" if index.exists(): with index.open() as fd: self._potential_metadata = json.load(fd) else: self._potential_metadata = self.catalog_potentials() with index.open("w") as fd: json.dump( self.potential_metadata, fd, indent=4, sort_keys=True, cls=CompactJSONEncoder, ) print(f"Wrote the index to {index}") return self._potential_metadata
[docs] def set_id(self, node_id): """Set the id for node to a given tuple""" self._id = node_id # and set our subnodes self.subflowchart.set_ids(self._id) return self.next()
[docs] def create_parser(self): """Setup the command-line / config file parser""" parser_name = self.step_type parser = getParser() # Remember if the parser exists ... this type of step may have been # found before parser_exists = parser.exists(parser_name) # Create the standard options, e.g. log-level result = super().create_parser(name=parser_name) if parser_exists: return result # VASP specific options parser.add_argument( parser_name, "--ncores", default="available", help=( "The maximum number of cores to use for VASP. " "Default: all available cores." ), ) parser.add_argument( parser_name, "--graph-formats", default=tuple(), choices=("html", "png", "jpeg", "webp", "svg", "pdf"), nargs="+", help="extra formats to write for graphs", ) parser.add_argument( parser_name, "--graph-fontsize", default=15, help="Font size in graphs, defaults to 15 pixels", ) parser.add_argument( parser_name, "--graph-width", default=1024, help="Width of graphs in formats that support it, defaults to 1024", ) parser.add_argument( parser_name, "--graph-height", default=1024, help="Height of graphs in formats that support it, defaults to 1024", ) return result
[docs] def description_text(self, P=None): """Create the text description of what this step will do. The dictionary of control values is passed in as P so that the code can test values, etc. Parameters ---------- P: dict An optional dictionary of the current values of the control parameters. Returns ------- str A description of the current step. """ self.subflowchart.root_directory = self.flowchart.root_directory # Get the first real node node = self.subflowchart.get_node("1").next() text = self.header + "\n\n" while node is not None: try: text += __(node.description_text(), indent=3 * " ").__str__() except Exception as e: print(f"Error describing vasp flowchart: {e} in {node}") logger.critical(f"Error describing vasp flowchart: {e} in {node}") raise except: # noqa: E722 print( "Unexpected error describing vasp flowchart: {} in {}".format( sys.exc_info()[0], str(node) ) ) logger.critical( "Unexpected error describing vasp flowchart: {} in {}".format( sys.exc_info()[0], str(node) ) ) raise text += "\n" node = node.next() return text
[docs] def run(self): """Run a VASP step. Parameters ---------- None Returns ------- seamm.Node The next node object in the flowchart. """ next_node = super().run(printer) # Print our header to the main output printer.normal(self.header) printer.normal("") # Get the first real node node = self.subflowchart.get_node("1").next() while node is not None: node.run() # Loop to next node node = node.next() return next_node
[docs] def analyze(self, indent="", **kwargs): """Do any analysis of the output from this step. Also print important results to the local step.out file using "printer". Parameters ---------- indent: str An extra indentation for the output """ # Get the first real node node = self.subflowchart.get_node("1").next() # Loop over the subnodes, asking them to do their analysis while node is not None: for value in node.description: printer.important(value) printer.important(" ") node.analyze() node = node.next()
[docs] def catalog_potentials(self): """Create a catalog of the PAW potentials.""" t0 = time.time_ns() directory = Path("~/SEAMM/Parameters/VASP").expanduser() print(f"Potentials directory is {directory}") paths = directory.glob("**/POTCAR") data = {} for n, path in enumerate(paths, start=1): _type, potential = path.parts[-3:-1] # print(f"{_type} -- {potential} {path}") result = self.parse_potcar(path) result["element"] = potential.split("_")[0] result["file"] = str(path) # pprint.pprint(result) if _type not in data: data[_type] = {} if potential in data[_type]: raise ValueError(f"Potential {_type} / {potential} is duplicated!") data[_type][potential] = result t = (time.time_ns() - t0) / 1.0e9 print(f"Took {t:.3f} seconds to process {n} POTCAR files") return data
[docs] def parse_potcar(self, path): """Read a POTCAR file and extract pertinent information. Parameters ---------- path : pathlib.Path The path to the POTCAR file Returns ------- results : dict(str, str) A dictionary of the data from the POTCAR file """ result = {} lines = iter(path.read_text().splitlines()) line1 = next(lines).strip() for line in lines: line = line.strip() if line.startswith("TITEL"): result["title"] = line.split("=")[1].strip() if result["title"] != line1: print( f"Line 1 and the title differ:\n\t{line1}\n\t{result['title']}" ) elif line.startswith("EATOM"): result["Eatom"] = line.split("=")[1].split()[0].strip() elif line.startswith("POMASS"): tmp = line.split(";") result["mass"] = tmp[0].split("=")[1].strip() result["zval"] = tmp[1].split("=")[1].split()[0].strip() elif line.startswith("ENMAX"): tmp = line.split(";") result["Emax"] = tmp[0].split("=")[1].strip() result["Emin"] = tmp[1].split("=")[1].split()[0].strip() elif line == "Atomic configuration": line = next(lines).strip() n_orbitals = int(line.split()[0]) next(lines) orbitals = [] for i in range(n_orbitals): line = next(lines).strip() n, l, j, E, occupation = line.split() n = int(n) l = int(l) # noqa=E741 E = float(E) occupation = occupation.rstrip("0.") if occupation == "": occupation = "0" orbitals.append((n, l, j, E, occupation)) result["orbitals"] = orbitals # Find the occupied valence orbitals electrons = 0 symbols = [] for n, l, j, E, occupation in orbitals[::-1]: Z = float(result["zval"]) occ = float(occupation) if occ != 0: electrons += occ symbol = f"{n}{('s', 'p', 'd', 'f')[l]}{occupation}" symbols.append(symbol) if electrons >= Z: break result["configuration"] = " ".join(symbols[::-1]) break return result