diff --git a/HISTORY.rst b/HISTORY.rst index 0cffbf5..4d57c4c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,9 @@ ======= History ======= +2024.1.11 -- Changes to allow running in containers. + * Moved to the new executor and ensured it still runs directly. + * Fixed bugs in printing the summary output. 2023.8.23 -- Fix for installation of Psi4 * Psi4 is now available on CondaForge, so install from there if requested. diff --git a/devtools/conda-envs/test_env.yaml b/devtools/conda-envs/test_env.yaml index 0ac073c..53f43ec 100644 --- a/devtools/conda-envs/test_env.yaml +++ b/devtools/conda-envs/test_env.yaml @@ -11,7 +11,6 @@ dependencies: # Of the code - numpy - optking - - psutil - scipy - seamm - tabulate @@ -29,5 +28,6 @@ dependencies: # Pip-only installs - pip: - # Documentation - - sphinx-copybutton + - seamm-exec + # Documentation + - sphinx-copybutton diff --git a/psi4_step/data/references.bib b/psi4_step/data/references.bib index 3f2955a..d53e04b 100644 --- a/psi4_step/data/references.bib +++ b/psi4_step/data/references.bib @@ -1,16 +1,16 @@ @article{doi:10.1063/5.0006002, - author = {Smith,Daniel G. A. and Burns,Lori A. and Simmonett,Andrew C. and - Parrish,Robert M. and Schieber,Matthew C. and Galvelis,Raimondas and - Kraus,Peter and Kruse,Holger and Di Remigio,Roberto and Alenaizan,Asem - and James,Andrew M. and Lehtola,Susi and Misiewicz,Jonathon P. and - Scheurer,Maximilian and Shaw,Robert A. and Schriber,Jeffrey B. and - Xie,Yi and Glick,Zachary L. and Sirianni,Dominic A. and - O’Brien,Joseph Senan and Waldrop,Jonathan M. and Kumar,Ashutosh and - Hohenstein,Edward G. and Pritchard,Benjamin P. and Brooks,Bernard R. - and Schaefer,Henry F. and Sokolov,Alexander Yu. and Patkowski,Konrad - and DePrince,A. Eugene and Bozkaya,Uğur and King,Rollin A. and - Evangelista,Francesco A. and Turney,Justin M. and Crawford,T. Daniel - and Sherrill,C. David }, + author = {Smith, Daniel G. A. and Burns, Lori A. and Simmonett, Andrew C. and + Parrish, Robert M. and Schieber, Matthew C. and Galvelis, Raimondas and + Kraus, Peter and Kruse, Holger and Di Remigio, Roberto and Alenaizan, Asem and + James, Andrew M. and Lehtola, Susi and Misiewicz, Jonathon P. and + Scheurer, Maximilian and Shaw, Robert A. and Schriber, Jeffrey B. and + Xie, Yi and Glick, Zachary L. and Sirianni, Dominic A. and + O’Brien, Joseph Senan and Waldrop, Jonathan M. and Kumar, Ashutosh and + Hohenstein, Edward G. and Pritchard, Benjamin P. and Brooks, Bernard R. and + Schaefer, Henry F. and Sokolov, Alexander Yu. and Patkowski, Konrad and + DePrince, A. Eugene and Bozkaya, Uğur and King, Rollin A. and + Evangelista, Francesco A. and Turney, Justin M. and Crawford, T. Daniel and + Sherrill, C. David}, title = {PSI4 1.4: Open-source software for high-throughput quantum chemistry}, journal = {The Journal of Chemical Physics}, volume = {152}, @@ -21,13 +21,13 @@ @article{doi:10.1063/5.0006002 URL = {https://doi.org/10.1063/5.0006002}, eprint = {https://doi.org/10.1063/5.0006002} } -@Misc{packmol_step, - author = {Saxe, P.}, +@Misc{psi4_step, + author = {Paul Saxe}, title = {Psi4 plug-in for SEAMM}, - month = {oct}, - year = 2020, + month = {$month}, + year = {$year}, organization = {Molecular Sciences Software Institute (MolSSI)}, url = {https://github.com/molssi-seamm/psi4_step}, address = {Blacksburg, VA, USA}, - version = {2020.10.6} + version = {$version} } diff --git a/psi4_step/energy.py b/psi4_step/energy.py index 3c5bf2c..ef59863 100644 --- a/psi4_step/energy.py +++ b/psi4_step/energy.py @@ -52,10 +52,15 @@ def git_revision(self): """The git version of this module.""" return psi4_step.__git_revision__ - def description_text(self, P=None, calculation_type="Single-point energy"): + def description_text( + self, + P=None, + calculation_type="Single-point energy", + configuration=None, + ): """Prepare information about what this node will do""" - if not P: + if P is not None: P = self.parameters.values_to_dict() if P["level"] == "recommended": @@ -75,7 +80,8 @@ def description_text(self, P=None, calculation_type="Single-point energy"): text = f"{calculation_type} using {method} with an " text += f"exchange-correlation potential of {functional}" if ( - len(psi4_step.dft_functionals[functional]["dispersion"]) > 1 + functional in psi4_step.dft_functionals + and len(psi4_step.dft_functionals[functional]["dispersion"]) > 1 and P["dispersion"] != "none" ): text += f" with the {P['dispersion']} dispersion correction." @@ -87,6 +93,17 @@ def description_text(self, P=None, calculation_type="Single-point energy"): # Spin if P["spin-restricted"] == "yes": text += " The spin will be restricted to a pure eigenstate." + elif P["spin-restricted"] == "default": + if configuration is not None: + if configuration.spin_multiplicity == 1: + text += " The spin will be restricted to a pure eigenstate." + else: + text += " The spin will not be restricted and may not be a " + text += "proper eigenstate." + else: + text += " The spin will be restricted to a pure " + text += "eigenstate for singlet states. Otherwise it will not " + text += "be restricted and may not be a proper eigenstate." elif self.is_expr(P["spin-restricted"]): text += " Whether the spin will be restricted to a pure " text += "eigenstate will be determined by {P['spin-restricted']}" @@ -95,11 +112,21 @@ def description_text(self, P=None, calculation_type="Single-point energy"): text += "proper eigenstate." # Plotting - if P["density"]: - if P["orbitals"]: + if isinstance(P["density"], str): + density = P["density"] != "no" + else: + density = P["density"] + + if isinstance(P["orbitals"], str): + orbitals = P["orbitals"] != "no" + else: + orbitals = P["orbitals"] + + if density: + if orbitals: text += "\nThe alpha and beta electron, total, and spin densities, " text += f"and orbitals {P['selected orbitals']} will be plotted." - elif P["orbitals"]: + elif orbitals: text += f"\nThe orbitals {P['selected orbitals']} will be plotted." return self.header + "\n" + __(text, **P, indent=4 * " ").__str__() @@ -122,7 +149,13 @@ def get_input(self, calculation_type="energy", restart=None): PP[key] = "{:~P}".format(PP[key]) self.description = [] - self.description.append(__(self.description_text(PP), **PP, indent=self.indent)) + self.description.append( + __( + self.description_text(PP, configuration=configuration), + **PP, + indent=self.indent, + ) + ) lines = [] if calculation_type == "energy": diff --git a/psi4_step/optimization.py b/psi4_step/optimization.py index 8c0deb6..6631d51 100644 --- a/psi4_step/optimization.py +++ b/psi4_step/optimization.py @@ -38,15 +38,22 @@ def __init__( self.description = "A geometry optimization" - def description_text(self, P=None): + def description_text( + self, + P=None, + calculation_type="Geometry optimization", + configuration=None, + ): """Prepare information about what this node will do""" if not P: P = self.parameters.values_to_dict() - text = super().description_text(P=P, calculation_type="Geometry optimization") + text = super().description_text( + P=P, calculation_type=calculation_type, configuration=configuration + ) - added = "The geometry optimization will use the {optimization method} " + added = "\nThe geometry optimization will use the {optimization method} " if P["max geometry steps"] == "default": added += "method, using the default maximum number of steps, which" added += " is based on the system size." @@ -89,7 +96,13 @@ def get_input(self, calculation_type="optimize"): PP[key] = "{:~P}".format(PP[key]) self.description = [] - self.description.append(__(self.description_text(PP), **PP, indent=self.indent)) + self.description.append( + __( + self.description_text(PP, configuration=configuration), + **PP, + indent=self.indent, + ) + ) lines = [] lines.append("") diff --git a/psi4_step/psi4.py b/psi4_step/psi4.py index 868d185..e72a12d 100644 --- a/psi4_step/psi4.py +++ b/psi4_step/psi4.py @@ -3,16 +3,15 @@ """Non-graphical part of the Psi4 step in a SEAMM flowchart """ +import configparser import json import logging from pathlib import Path import pprint -import psutil - import psi4_step import seamm -from seamm_util import ureg, Q_ # noqa: F401 +import seamm_exec import seamm_util.printing as printing from seamm_util.printing import FormattedText as __ @@ -236,13 +235,6 @@ def create_parser(self): return result # Options for Psi4 - parser.add_argument( - parser_name, - "--psi4-path", - default="", - help="the path to the Psi4 executable", - ) - parser.add_argument( parser_name, "--ncores", @@ -324,7 +316,7 @@ def run(self): printer.important(self.header) printer.important("") - # Add the main citation for DFTB+ + # Add the main citation for Psi4 self.references.cite( raw=self._bibliography["doi:10.1063/5.0006002"], alias="psi4", @@ -344,41 +336,39 @@ def run(self): options = self.options seamm_options = self.global_options - # How many processors does this node have? - n_cores = psutil.cpu_count(logical=False) - self.logger.info("The number of cores is {}".format(n_cores)) + # Get the computational environment and set limits + ce = seamm_exec.computational_environment() - # How many threads to use + # Maximum number of threads + n_threads = ce["NTASKS"] if options["ncores"] == "available": - n_threads = n_cores + pass else: n_threads = int(options["ncores"]) - if n_threads > n_cores: - n_threads = n_cores - if n_threads < 1: - n_threads = 1 + if n_threads < 1: + n_threads = 1 if seamm_options["ncores"] != "available": - n_threads = min(n_threads, int(options["ncores"])) - self.logger.info(f"Psi4 will use {n_threads} threads.") - - # How much memory to use - svmem = psutil.virtual_memory() + tmp = int(seamm_options["ncores"]) + if tmp < n_threads: + n_threads = tmp + ce["NTASKS"] = n_threads + # And memory if seamm_options["memory"] == "all": - mem_limit = svmem.total + mem_limit = ce["MEM_PER_NODE"] elif seamm_options["memory"] == "available": # For the default, 'available', use in proportion to number of # cores used - mem_limit = svmem.total * (n_threads / n_cores) + mem_limit = n_threads * ce["MEM_PER_CPU"] else: mem_limit = dehumanize(seamm_options["memory"]) if options["memory"] == "all": - memory = svmem.total + memory = ce["MEM_PER_NODE"] elif options["memory"] == "available": # For the default, 'available', use in proportion to number of # cores used - memory = svmem.total * (n_threads / n_cores) + memory = n_threads * ce["MEM_PER_CPU"] else: memory = dehumanize(options["memory"]) @@ -395,12 +385,9 @@ def run(self): min_memory = dehumanize("250 MiB") if min_memory > memory: memory = min_memory + ce["MEM_PER_NODE"] = memory memory = humanize(memory, kilo=1024) - printer.important( - self.indent + f" Psi4 will use {n_threads} threads and {memory} memory\n" - ) - # Work through the subflowchart to find out what to do. self.subflowchart.root_directory = self.flowchart.root_directory @@ -409,15 +396,6 @@ def run(self): # Get the first real node node0 = self.subflowchart.get_node("1").next() - # See if this is a normal or special run - # node = node0 - # while node is not None: - # if isinstance(node, psi4_step.AcceleratedOptimization): - # node.run(node0, memory, n_threads) - # return next_node - - # node = node.next() - # Start the input data input_data = [] input_data.append("import json") @@ -474,24 +452,37 @@ def run(self): result["stderr"] = "" failed = False else: - exe_path = Path(options["psi4_path"]) - env = { - "PSIPATH": str(exe_path), - "PATH": str(exe_path), - } - - local = seamm.ExecLocal() - exe = exe_path / "psi4" - result = local.run( - cmd=[str(exe), f"-n {n_threads}"], + executor = self.flowchart.executor + + # Read configuration file for Psi4 + ini_dir = Path(seamm_options["root"]).expanduser() + full_config = configparser.ConfigParser() + full_config.read(ini_dir / "psi4.ini") + executor_type = executor.name + if executor_type not in full_config: + raise RuntimeError( + f"No section for '{executor_type}' in Psi4 ini file " + f"({ini_dir / 'psi4.ini'})" + ) + config = dict(full_config.items(executor_type)) + + printer.important( + self.indent + + f" Psi4 will use {ce['NTASKS']} threads and {memory} memory\n" + ) + + result = executor.run( + cmd=["{code}"], + config=config, + directory=self.directory, files=files, return_files=return_files, - env=env, - directory=directory, in_situ=True, - ) # yapf: disable + shell=True, + ce=ce, + ) - if result is None: + if not result: self.logger.error("There was an error running Psi4") raise RuntimeError("There was an error running Psi4") diff --git a/psi4_step/tk_energy.py b/psi4_step/tk_energy.py index f396fef..b9b9af6 100644 --- a/psi4_step/tk_energy.py +++ b/psi4_step/tk_energy.py @@ -248,17 +248,18 @@ def reset_calculation(self, widget=None): widgets2.append(self["freeze-cores"]) row += 1 if self.node.method is None or self.node.method == "dft": - dispersions = psi4_step.dft_functionals[functional]["dispersion"] - if len(dispersions) > 1: - w = self["dispersion"] - w.config(values=dispersions) - if w.get() not in dispersions: - w.value(dispersions[1]) - w.grid(row=row, column=1, sticky=tk.W) - widgets2.append(self["dispersion"]) - row += 1 + if functional in psi4_step.dft_functionals: + dispersions = psi4_step.dft_functionals[functional]["dispersion"] + if len(dispersions) > 1: + w = self["dispersion"] + w.config(values=dispersions) + if w.get() not in dispersions: + w.value(dispersions[1]) + w.grid(row=row, column=1, sticky=tk.W) + widgets2.append(self["dispersion"]) + row += 1 sw.align_labels(widgets2) - frame.columnconfigure(0, minsize=30) + frame.columnconfigure(0, minsize=30) self["spin-restricted"].grid(row=row, column=0, columnspan=2, sticky=tk.EW) widgets.append(self["spin-restricted"]) row += 1 diff --git a/requirements.txt b/requirements.txt index 581ba2b..171a14c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ numpy # optking -psutil scipy seamm tabulate