From 9a7cc38969dce908bb894a7a67696225e965d330 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 28 Nov 2025 14:16:22 +1100 Subject: [PATCH 01/17] Add psi4 to src/censo/params.py --- src/censo/params.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/censo/params.py b/src/censo/params.py index a8daa82b..c14ec179 100644 --- a/src/censo/params.py +++ b/src/censo/params.py @@ -99,6 +99,7 @@ class Prog(str, Enum): """ ORCA = "orca" + PSI4 = "psi4" TM = "tm" XTB = "xtb" GENERIC = "generic" @@ -110,6 +111,7 @@ class QmProg(str, Enum): """ ORCA = Prog.ORCA.value + PSI4 = Prog.PSI4.value TM = Prog.TM.value From c4c41353f6d4b81d55220a8498afb7d0b1079236 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 28 Nov 2025 14:39:11 +1100 Subject: [PATCH 02/17] Add pbe-d3 for psi4 to src/censo/assets/dfa.json --- src/censo/assets/dfa.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/censo/assets/dfa.json b/src/censo/assets/dfa.json index fec3868d..cfebf83b 100644 --- a/src/censo/assets/dfa.json +++ b/src/censo/assets/dfa.json @@ -42,7 +42,13 @@ "type": "mgga" }, "pbe-novdw": { "tm": "pbe", "orca": "pbe", "disp": "novdw", "type": "gga" }, - "pbe-d3": { "tm": "pbe", "orca": "pbe", "disp": "d3bj", "type": "gga" }, + "pbe-d3": { + "tm": "pbe", + "orca": "pbe", + "psi4": "pbe-d3", + "disp": "d3bj", + "type": "gga" + }, "pbe-d3(0)": { "tm": "pbe", "orca": "pbe", "disp": "d3(0)", "type": "gga" }, "pbe-d4": { "tm": "pbe", "orca": "pbe", "disp": "d4", "type": "gga" }, "pbe-nl": { "tm": "pbe", "disp": "nl", "type": "gga" }, From 23dc5068b9a6b9c825ae39798522464e7d6a004f Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 28 Nov 2025 14:43:45 +1100 Subject: [PATCH 03/17] add psi4 to src/censo/config/paths.py --- src/censo/config/paths.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/censo/config/paths.py b/src/censo/config/paths.py index 4ea7df51..7f72276a 100644 --- a/src/censo/config/paths.py +++ b/src/censo/config/paths.py @@ -32,6 +32,9 @@ class PathsConfig(BaseModel): _orcaversion: str = PrivateAttr("") """ORCA version string. Should be extracted from somewhere.""" + psi4: str = Field("") + """Absolute path to the turbomole binary directory.""" + tm: str = Field("") """Absolute path to the turbomole binary directory.""" @@ -76,6 +79,18 @@ def validate_orca(cls, value: str): raise ValueError(f"orca executable not found at {value}.") return value + @field_validator("psi4") + def validate_psi4(cls, value: str): + """ + Validate the psi4 executable path. + + :param value: The path to validate. + :return: The validated path. + """ + if not Path(value).is_file(): + raise ValueError(f"psi4 executable not found at {value}.") + return value + @field_validator("tm") def validate_turbomole(cls, value: str): """ From da75487ca85d7de41e3d83baeef599c0fb98d17e Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 28 Nov 2025 15:24:23 +1100 Subject: [PATCH 04/17] remove minimum core count --- src/censo/params.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/censo/params.py b/src/censo/params.py index c14ec179..472b6e99 100644 --- a/src/censo/params.py +++ b/src/censo/params.py @@ -44,7 +44,7 @@ class Returncode(int, Enum): CONFIG_ERROR = 5 -OMPMIN_DEFAULT = 4 # we use 4 because ORCA/OpenMPI is buggy as hell below that? +OMPMIN_DEFAULT = 1 OMPMAX_DEFAULT = os.cpu_count() or 16 ASSETS_PATH = Path(__file__).parent / "assets" From b2eee217643a3eee875786459b61629a09d832a1 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 28 Nov 2025 15:27:00 +1100 Subject: [PATCH 05/17] revert commit --- src/censo/params.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/censo/params.py b/src/censo/params.py index 472b6e99..c14ec179 100644 --- a/src/censo/params.py +++ b/src/censo/params.py @@ -44,7 +44,7 @@ class Returncode(int, Enum): CONFIG_ERROR = 5 -OMPMIN_DEFAULT = 1 +OMPMIN_DEFAULT = 4 # we use 4 because ORCA/OpenMPI is buggy as hell below that? OMPMAX_DEFAULT = os.cpu_count() or 16 ASSETS_PATH = Path(__file__).parent / "assets" From 7d4fa49590ece30cab1d7a4f2d672b5ebb424821 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 28 Nov 2025 15:51:29 +1100 Subject: [PATCH 06/17] add guard to censo --- censo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/censo b/censo index 4164be76..7746a9cc 100755 --- a/censo +++ b/censo @@ -2,8 +2,8 @@ import sys import os -sys.path.insert(0, f"{os.path.join(os.path.split(__file__)[0], 'src')}") - from censo.cli.interface import entry_point -sys.exit(entry_point()) +if __name__ == "__main__": + sys.path.insert(0, f"{os.path.join(os.path.split(__file__)[0], 'src')}") + sys.exit(entry_point()) From c90e809b09e5ebcf53d25b06e682e64a7d4aaa4b Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Fri, 28 Nov 2025 19:18:37 +1100 Subject: [PATCH 07/17] add generic program --- src/censo/assets/dfa.json | 3 +++ src/censo/params.py | 1 + 2 files changed, 4 insertions(+) diff --git a/src/censo/assets/dfa.json b/src/censo/assets/dfa.json index cfebf83b..a9e1baa3 100644 --- a/src/censo/assets/dfa.json +++ b/src/censo/assets/dfa.json @@ -225,5 +225,8 @@ "orca": "dsd-pbep86", "disp": "d3bj", "type": "double" + }, + "generic": { + "generic": "generic" } } diff --git a/src/censo/params.py b/src/censo/params.py index c14ec179..6cd9cc67 100644 --- a/src/censo/params.py +++ b/src/censo/params.py @@ -113,6 +113,7 @@ class QmProg(str, Enum): ORCA = Prog.ORCA.value PSI4 = Prog.PSI4.value TM = Prog.TM.value + GENERIC = Prog.GENERIC.value class GridLevel(str, Enum): From 42133c155053db1948468756bb82e8d18d862bd9 Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Fri, 28 Nov 2025 19:28:52 +1100 Subject: [PATCH 08/17] add support for generic --- src/censo/assets/dfa.json | 4 +++- src/censo/config/parts/rot.py | 2 +- src/censo/config/parts/uvvis.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/censo/assets/dfa.json b/src/censo/assets/dfa.json index a9e1baa3..66396b7f 100644 --- a/src/censo/assets/dfa.json +++ b/src/censo/assets/dfa.json @@ -227,6 +227,8 @@ "type": "double" }, "generic": { - "generic": "generic" + "generic": "generic", + "disp": "generic", + "type": "generic" } } diff --git a/src/censo/config/parts/rot.py b/src/censo/config/parts/rot.py index 72ecd050..eec10371 100644 --- a/src/censo/config/parts/rot.py +++ b/src/censo/config/parts/rot.py @@ -10,7 +10,7 @@ class RotConfig(BasePartConfig): """Config class for optical rotation calculations (gas-phase only).""" - prog: Literal[QmProg.TM] = QmProg.TM + prog: Literal[QmProg.TM] | Literal[QmProg.GENERIC] = QmProg.TM """Program that should be used for calculations.""" func: str = "pbe-d4" diff --git a/src/censo/config/parts/uvvis.py b/src/censo/config/parts/uvvis.py index 7bc538b4..0afc3c81 100644 --- a/src/censo/config/parts/uvvis.py +++ b/src/censo/config/parts/uvvis.py @@ -9,7 +9,7 @@ class UVVisConfig(BasePartConfig): """Config class for UVVis""" - prog: Literal[QmProg.ORCA] = QmProg.ORCA + prog: Literal[QmProg.ORCA] | Literal[QmProg.GENERIC] = QmProg.ORCA """Program that should be used for calculations.""" func: str = "wb97x-d4" From 0950dcddb2f2df4ff253b5b30db1ab7aa74587b8 Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Fri, 28 Nov 2025 19:38:39 +1100 Subject: [PATCH 09/17] add Psi4Proc --- src/censo/config/paths.py | 3 +++ src/censo/processing/__init__.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/censo/config/paths.py b/src/censo/config/paths.py index 7f72276a..99c6ee7e 100644 --- a/src/censo/config/paths.py +++ b/src/censo/config/paths.py @@ -41,6 +41,9 @@ class PathsConfig(BaseModel): xtb: str = Field("") """Absolute path to the xtb binary.""" + generic: str = Field("") + """Absolute path to the generic binary.""" + cosmotherm: str = Field("") """Absolute path to the cosmotherm binary.""" diff --git a/src/censo/processing/__init__.py b/src/censo/processing/__init__.py index 8db26452..609bee53 100644 --- a/src/censo/processing/__init__.py +++ b/src/censo/processing/__init__.py @@ -1,8 +1,9 @@ from .processor import GenericProc from .qm_processor import QmProc from .orca_processor import OrcaProc +from .psi4_processor import Psi4Proc from .tm_processor import TmProc from .xtb_processor import XtbProc from .job import JobContext -__all__ = ["GenericProc", "XtbProc", "QmProc", "OrcaProc", "TmProc", "JobContext"] +__all__ = ["GenericProc", "XtbProc", "QmProc", "OrcaProc", "Psi4Proc", "TmProc", "JobContext"] From 4363a899de1b01e8be25aa8f677bc688a986997f Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Fri, 28 Nov 2025 19:40:08 +1100 Subject: [PATCH 10/17] add psi4_processor.py --- src/censo/processing/psi4_processor.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/censo/processing/psi4_processor.py diff --git a/src/censo/processing/psi4_processor.py b/src/censo/processing/psi4_processor.py new file mode 100644 index 00000000..acc3ef18 --- /dev/null +++ b/src/censo/processing/psi4_processor.py @@ -0,0 +1,12 @@ +""" +Contains Psi4Proc class for calculating psi4 related properties of conformers. +""" + +import typing + +from .qm_processor import QmProc + + +@typing.final +class Psi4Proc(QmProc): + pass From 3a63aef8b3dcf035d280c79ea54f4122e9e43118 Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Fri, 28 Nov 2025 19:43:29 +1100 Subject: [PATCH 11/17] register psi4 --- src/censo/processing/psi4_processor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/censo/processing/psi4_processor.py b/src/censo/processing/psi4_processor.py index acc3ef18..962a54cf 100644 --- a/src/censo/processing/psi4_processor.py +++ b/src/censo/processing/psi4_processor.py @@ -4,9 +4,16 @@ import typing +from ..utilities import Factory +from ..params import ( + Prog, +) from .qm_processor import QmProc @typing.final class Psi4Proc(QmProc): pass + + +Factory.register_builder(Prog.PSI4, Psi4Proc) From 33e77c70db958fa30272c0101d0530789eb49043 Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Sun, 30 Nov 2025 23:15:00 +1100 Subject: [PATCH 12/17] Add processcor for psi4 --- src/censo/parallel.py | 2 +- src/censo/processing/psi4_processor.py | 199 ++++++++++++++++++++++++- 2 files changed, 199 insertions(+), 2 deletions(-) diff --git a/src/censo/parallel.py b/src/censo/parallel.py index 60eedb43..e35f9b53 100644 --- a/src/censo/parallel.py +++ b/src/censo/parallel.py @@ -303,7 +303,7 @@ def execute[ ) else: results[meta.conf_name] = result - if prog in QmProg: + if (prog in QmProg) and (prog is not QmProg.PSI4): # TODO: implement mo for psi4 conf.mo_paths[prog].append(result.mo_path) except CancelledError: logger.debug("Future cancelled.") diff --git a/src/censo/processing/psi4_processor.py b/src/censo/processing/psi4_processor.py index 962a54cf..4603b853 100644 --- a/src/censo/processing/psi4_processor.py +++ b/src/censo/processing/psi4_processor.py @@ -2,18 +2,215 @@ Contains Psi4Proc class for calculating psi4 related properties of conformers. """ +import os +import pathlib +import subprocess import typing +from ..config.job_config import ( + SPJobConfig, +) +from .job import ( + JobContext, +) +from .results import ( + SPResult, + MetaData, +) from ..utilities import Factory +from ..logging import setup_logger from ..params import ( Prog, ) +from ..assets import FUNCTIONALS from .qm_processor import QmProc +logger = setup_logger(__name__) + @typing.final class Psi4Proc(QmProc): - pass + + def __prep( + self, + job: JobContext, + config: SPJobConfig, + jobtype: str, + no_solv: bool = False, + xyzfile: str | None = None, + ) -> list[str]: + """ + Prepares an OrderedDict to be fed into the parser in order to write an input file for jobtype 'jobtype' + (e.g. sp). + + Can load up a template file from user assets folder. + + :param job: JobContext object containing the job information + :type job: JobContext + :param config: Configuration for the job + :type config: SPJobConfig + :param jobtype: jobtype to prepare the input for + :type jobtype: str + :param no_solv: if True, no solvent model is used + :type no_solv: bool + :param xyzfile: if not None, the geometry is read from this file instead of the job object + :type xyzfile: str | None + :returns: List of strings for the input file + :rtype: list[str] + + NOTE: the xyzfile has to already exist, this function just bluntly writes the name into the input. + """ + + inp: list[str] = [] + + template = "template" in config.model_fields and bool(config.template) + if template: + logger.warn("template not implemented for psi4") + + inp[:0] = self.__prep_main(jobtype, config, no_solv) + + # prepare all options that are supposed to be placed before the + # geometry definition + inp[1:1] = self.__prep_pregeom(jobtype, config, job.omp) + + # prepare the geometry + if template: + geom_line = next(inp.index(line) for line in inp if "{geom}" in line) + inp.pop(geom_line) + inp[geom_line:geom_line] = self.__prep_geom( + job.conf, + xyzfile, + job.charge, + job.unpaired, + ) + else: + inp.extend( + self.__prep_geom( + job.conf, + xyzfile, + job.charge, + job.unpaired, + ) + ) + + inp.extend(self.__prep_postgeom(config, jobtype)) + + # Finally append newlines where needed + return [line + "\n" if not line.endswith("\n") else line for line in inp] + + @typing.final + def __sp( + self, + job: JobContext, + config: SPJobConfig, + jobdir: str | pathlib.Path | None = None, + filename: str = "sp", + no_solv: bool = False, + prep: bool = True, + ) -> tuple[SPResult, MetaData]: + """ + PSI4 single-point calculation. + Unwrapped function to call from other methods. + + :param job: JobContext object containing the job information, metadata is stored in job.meta + :type job: JobContext + :param config: Configuration for the job + :type config: SPJobConfig + :param jobdir: path to the job directory + :type jobdir: str | Path | None + :param filename: name of the input file + :type filename: str + :param no_solv: if True, no solvent model is used + :type no_solv: bool + :param prep: if True, a new input file is generated + :type prep: bool + :returns: Tuple of (SPResult, MetaData) + :rtype: tuple[SPResult, MetaData] + """ + if jobdir is None: + jobdir = self._setup(job, "sp") + + # set results + result = SPResult() + meta = MetaData(job.conf.name) + + # set in/out path + inputpath = os.path.join(jobdir, f"{filename}.inp") + outputpath = os.path.join(jobdir, f"{filename}.out") + + solvation = not (config.gas_phase or no_solv) + if solvation: + logger.warning("Solvation is not implemented for psi4") + + if job.omp > 1: + logger.warning("omp is not implemented for psi4") + + if config.copy_mo: + logger.warning("copy_mo is not implemented for psi4") + + # prepare input + if prep: + inp = [] + + # basis set + inp.append(f"set basis {config.basis}") + inp.append('') + + # molecule + inp.append("molecule {") + for line in job.conf.toxyz()[2:]: + inp.append(f"\t{line.strip()}") + inp.append("}") + inp.append('') + + # functional + inp.append(f'print("%f" % energy("{FUNCTIONALS[config.func][Prog.PSI4.value]}"))') + inp.append('') + + logger.warn("Grid is not implemnented for psi4") + + # write input into file "{filename}.inp" in a subdir created for the + # conformer + with open(inputpath, "w") as f: + f.write('\n'.join(inp)) + + # call psi4 + call = [config.paths.psi4, f"{filename}.inp"] + returncode, _ = self._make_call(call, outputpath, jobdir) + meta.success = returncode == 0 + + # read output + with open(outputpath) as out: + lines = out.readlines() + + # Get final energy + try: + result.energy = next( + ( + float(line.split()[3]) + for line in lines + if "Total Energy = " in line + ), + ) + except StopIteration: + meta.success = False + meta.error = "Could not parse final energy" + + return result, meta + + @typing.override + def sp(self, *args, **kwargs): + """ + Perform single-point calculation. + + :param args: Arguments. + :param kwargs: Keyword arguments. + :return: Tuple of (SP result, metadata). + """ + + logger.warn("psi4 is not fully implemented") + + return self.__sp(*args, **kwargs) Factory.register_builder(Prog.PSI4, Psi4Proc) From feb0e3910156b846e9854b0ac4bb068ff885ab57 Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Sun, 30 Nov 2025 23:35:26 +1100 Subject: [PATCH 13/17] add functionals --- src/censo/assets/dfa.json | 63 ++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/censo/assets/dfa.json b/src/censo/assets/dfa.json index 66396b7f..950c54da 100644 --- a/src/censo/assets/dfa.json +++ b/src/censo/assets/dfa.json @@ -2,18 +2,21 @@ "pbeh-3c": { "tm": "pbeh-3c", "orca": "pbeh-3c", + "psi4": "pbeh-3c", "disp": "composite", "type": "composite_hybrid" }, "b97-3c": { "tm": "b97-3c", "orca": "b97-3c", + "psi4": "b97-3c", "disp": "composite", "type": "composite_gga" }, "r2scan-3c": { "tm": "r2scan-3c", "orca": "r2scan-3c", + "psi4": "r2scan-3c", "disp": "composite", "type": "composite_mgga" }, @@ -38,6 +41,7 @@ "r2scan-d4": { "tm": "r2scan", "orca": "r2scan", + "psi4": "r2scan-d4", "disp": "d4", "type": "mgga" }, @@ -50,24 +54,57 @@ "type": "gga" }, "pbe-d3(0)": { "tm": "pbe", "orca": "pbe", "disp": "d3(0)", "type": "gga" }, - "pbe-d4": { "tm": "pbe", "orca": "pbe", "disp": "d4", "type": "gga" }, - "pbe-nl": { "tm": "pbe", "disp": "nl", "type": "gga" }, - "bp86": { "tm": "b-p", "disp": "novdw", "type": "gga" }, + "pbe-d4": { + "tm": "pbe", + "orca": "pbe", + "psi4": "pbe-d4", + "disp": "d4", + "type": "gga" + }, + "pbe-nl": { + "tm": "pbe", + "psi4": "pbe-nl", + "disp": "nl", + "type": "gga" + }, + "bp86": { + "tm": "b-p", + "psi4": "bp86", + "disp": "novdw", + "type": "gga" + }, "tpss-novdw": { "tm": "tpss", "orca": "tpss", "disp": "novdw", "type": "mgga" }, - "tpss-d3": { "tm": "tpss", "orca": "tpss", "disp": "d3bj", "type": "mgga" }, + "tpss-d3": { + "tm": "tpss", + "orca": "tpss", + "psi4": "tpss-d3", + "disp": "d3bj", + "type": "mgga" + }, "tpss-d3(0)": { "tm": "tpss", "orca": "tpss", "disp": "d3(0)", "type": "mgga" }, - "tpss-d4": { "tm": "tpss", "orca": "tpss", "disp": "d4", "type": "mgga" }, - "tpss-nl": { "tm": "tpss", "disp": "nl", "type": "mgga" }, + "tpss-d4": { + "tm": "tpss", + "orca": "tpss", + "psi4": "tpss-d4", + "disp": "d4", + "type": "mgga" + }, + "tpss-nl": { + "tm": "tpss", + "psi4": "tpss-nl", + "disp": "nl", + "type": "mgga" + }, "revtpss-novdw": { "tm": "revtpss", "orca": "revTPSS", @@ -79,13 +116,23 @@ "disp": "novdw", "type": "global_hybrid" }, - "tpssh-d3": { "orca": "tpssh", "disp": "d3", "type": "global_hybrid" }, + "tpssh-d3": { + "orca": "tpssh", + "psi4": "tpssh-d3", + "disp": "d3", + "type": "global_hybrid" + }, "tpssh-d3(0)": { "orca": "tpssh", "disp": "d3(0)", "type": "global_hybrid" }, - "tpssh-d4": { "orca": "tpssh", "disp": "d4", "type": "global_hybrid" }, + "tpssh-d4": { + "orca": "tpssh", + "psi4": "tpssh-d4", + "disp": "d4", + "type": "global_hybrid" + }, "b97-d3": { "tm": "b97-d", "orca": "b97-d3", From 65abf881e2f7bd3085318252628afe1aefcccf4c Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Mon, 1 Dec 2025 06:42:57 +1100 Subject: [PATCH 14/17] minor comment changes --- src/censo/processing/psi4_processor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/censo/processing/psi4_processor.py b/src/censo/processing/psi4_processor.py index 4603b853..37472f7a 100644 --- a/src/censo/processing/psi4_processor.py +++ b/src/censo/processing/psi4_processor.py @@ -4,7 +4,6 @@ import os import pathlib -import subprocess import typing from ..config.job_config import ( @@ -138,13 +137,12 @@ def __sp( inputpath = os.path.join(jobdir, f"{filename}.inp") outputpath = os.path.join(jobdir, f"{filename}.out") + # check for unsupported config solvation = not (config.gas_phase or no_solv) if solvation: logger.warning("Solvation is not implemented for psi4") - if job.omp > 1: logger.warning("omp is not implemented for psi4") - if config.copy_mo: logger.warning("copy_mo is not implemented for psi4") @@ -169,8 +167,8 @@ def __sp( logger.warn("Grid is not implemnented for psi4") - # write input into file "{filename}.inp" in a subdir created for the - # conformer + # write input into file "{filename}.inp" in a subdir + # created for the conformer with open(inputpath, "w") as f: f.write('\n'.join(inp)) From 47bad3cfae371d81de682e2dc00c7dab3c202b67 Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Mon, 1 Dec 2025 07:16:16 +1100 Subject: [PATCH 15/17] implement opt for psi4 --- src/censo/processing/psi4_processor.py | 128 ++++++++++++++++--------- 1 file changed, 81 insertions(+), 47 deletions(-) diff --git a/src/censo/processing/psi4_processor.py b/src/censo/processing/psi4_processor.py index 37472f7a..03a7f20d 100644 --- a/src/censo/processing/psi4_processor.py +++ b/src/censo/processing/psi4_processor.py @@ -7,12 +7,14 @@ import typing from ..config.job_config import ( + OptJobConfig, SPJobConfig, ) from .job import ( JobContext, ) from .results import ( + OptResult, SPResult, MetaData, ) @@ -39,10 +41,10 @@ def __prep( xyzfile: str | None = None, ) -> list[str]: """ - Prepares an OrderedDict to be fed into the parser in order to write an input file for jobtype 'jobtype' + Prepares an list of str to be written an input file for jobtype 'jobtype' (e.g. sp). - Can load up a template file from user assets folder. + TODO: use a template file from user assets folder. :param job: JobContext object containing the job information :type job: JobContext @@ -66,36 +68,30 @@ def __prep( if template: logger.warn("template not implemented for psi4") - inp[:0] = self.__prep_main(jobtype, config, no_solv) + # basis set + inp.append(f"set basis {config.basis}") + inp.append('') - # prepare all options that are supposed to be placed before the - # geometry definition - inp[1:1] = self.__prep_pregeom(jobtype, config, job.omp) + # molecule + inp.append("molecule {") + for line in job.conf.toxyz()[2:]: + inp.append(f"\t{line.strip()}") + inp.append("}") + inp.append('') - # prepare the geometry - if template: - geom_line = next(inp.index(line) for line in inp if "{geom}" in line) - inp.pop(geom_line) - inp[geom_line:geom_line] = self.__prep_geom( - job.conf, - xyzfile, - job.charge, - job.unpaired, - ) - else: - inp.extend( - self.__prep_geom( - job.conf, - xyzfile, - job.charge, - job.unpaired, - ) - ) + # functional + match jobtype: + case "sp": + inp.append(f'print("%f" % energy("{FUNCTIONALS[config.func][Prog.PSI4.value]}"))') + case "opt": + inp.append(f'print("%f" % optimize("{FUNCTIONALS[config.func][Prog.PSI4.value]}"))') + case unknown: + logger.warn(f"{unknown} is not implemented for psi4") + inp.append('') - inp.extend(self.__prep_postgeom(config, jobtype)) + logger.warn("Grid is not implemnented for psi4") - # Finally append newlines where needed - return [line + "\n" if not line.endswith("\n") else line for line in inp] + return inp @typing.final def __sp( @@ -148,24 +144,7 @@ def __sp( # prepare input if prep: - inp = [] - - # basis set - inp.append(f"set basis {config.basis}") - inp.append('') - - # molecule - inp.append("molecule {") - for line in job.conf.toxyz()[2:]: - inp.append(f"\t{line.strip()}") - inp.append("}") - inp.append('') - - # functional - inp.append(f'print("%f" % energy("{FUNCTIONALS[config.func][Prog.PSI4.value]}"))') - inp.append('') - - logger.warn("Grid is not implemnented for psi4") + inp = self.__prep(job, config, "sp", solvation) # write input into file "{filename}.inp" in a subdir # created for the conformer @@ -206,9 +185,64 @@ def sp(self, *args, **kwargs): :return: Tuple of (SP result, metadata). """ - logger.warn("psi4 is not fully implemented") + logger.warn("sp is not fully implemented for psi4") return self.__sp(*args, **kwargs) + @typing.override + def opt( + self, + job: JobContext, + config: OptJobConfig, + ) -> tuple[OptResult, MetaData]: + """ + Geometry optimization using psi4 optimizer. + Note that solvation in handled here always implicitly. + + :param job: JobContext object containing the job information, metadata is stored in job.meta + :param config: Optimization configuration + :return: Tuple of (OptResult, MetaData) + """ + + logger.warn("opt is not fully implemented for psi4") + + # prepare result + result = OptResult() + meta = MetaData(job.conf.name) + + jobdir = self._setup(job, "opt") + filename = "opt" + + # set orca input/output paths + inputpath = os.path.join(jobdir, f"{filename}.inp") + outputpath = os.path.join(jobdir, f"{filename}.out") + + # prepare input dict + inp = self.__prep(job, config, "opt", no_solv=config.gas_phase) + + # write input into file "{filename}.inp" in a subdir + # created for the conformer + with open(inputpath, "w") as f: + f.write('\n'.join(inp)) + + # read output + with open(outputpath) as out: + lines = out.readlines() + + # Get final energy + try: + result.energy = next( + ( + float(line.split()[3]) + for line in lines + if "Total Energy = " in line + ), + ) + except StopIteration: + meta.success = False + meta.error = "Could not parse final energy" + + return result, meta + Factory.register_builder(Prog.PSI4, Psi4Proc) From 9fa7e8372e4c1be4eb22c98682b1ec235140e906 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 1 Dec 2025 09:34:37 +1100 Subject: [PATCH 16/17] add functionals --- src/censo/assets/dfa.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/censo/assets/dfa.json b/src/censo/assets/dfa.json index 950c54da..30748ec3 100644 --- a/src/censo/assets/dfa.json +++ b/src/censo/assets/dfa.json @@ -151,6 +151,7 @@ "pbe0-d3": { "tm": "pbe0", "orca": "pbe0", + "psi4": "pbe0-d3", "disp": "d3bj", "type": "global_hybrid" }, @@ -163,10 +164,16 @@ "pbe0-d4": { "tm": "pbe0", "orca": "pbe0", + "psi4": "pbe0-d4", "disp": "d4", "type": "global_hybrid" }, - "pbe0-nl": { "tm": "pbe0", "disp": "nl", "type": "global_hybrid" }, + "pbe0-nl": { + "tm": "pbe0", + "psi4": "pbe0-nl", + "disp": "nl", + "type": "global_hybrid" + }, "pw6b95-novdw": { "tm": "pw6b95", "orca": "pw6b95", @@ -176,6 +183,7 @@ "pw6b95-d3": { "tm": "pw6b95", "orca": "pw6b95", + "psi4": "pw6b95-d3", "disp": "d3bj", "type": "global_hybrid" }, @@ -188,6 +196,7 @@ "pw6b95-d4": { "tm": "pw6b95", "orca": "pw6b95", + "psi4": "pw6b95-d4", "disp": "d4", "type": "global_hybrid" }, @@ -200,6 +209,7 @@ "b3lyp-d3": { "tm": "b3-lyp", "orca": "b3lyp", + "psi4": "b3lyp-d3", "disp": "d3bj", "type": "global_hybrid" }, @@ -212,39 +222,46 @@ "b3lyp-d4": { "tm": "b3-lyp", "orca": "b3lyp", + "psi4": "b3lyp-d4", "disp": "d4", "type": "global_hybrid" }, "b3lyp-nl": { "tm": "b3-lyp", "orca": "b3lyp", + "psi4": "b3lyp-nl", "disp": "nl", "type": "global_hybrid" }, "wb97x-v": { "tm": "wb97x-v", "orca": "wb97x-v", + "psi4": "wb97x-v", "disp": "included", "type": "rs_hybrid" }, "wb97x-d3": { "orca": "wb97x-d3", + "psi4": "wb97x-d3", "disp": "included", "type": "rs_hybrid" }, "wb97x-d3bj": { "orca": "wb97x-d3bj", + "psi4": "wb97x-d3bj", "disp": "included", "type": "rs_hybrid" }, "wb97x-d4": { "orca": "wb97x-d4", + "psi4": "wb97x-d4", "disp": "included", "type": "rs_hybrid" }, "wb97m-v": { "orca": "wb97m-v", "tm": "wb97m-v", + "psi4": "wb97m-v", "disp": "included", "type": "rs_hybrid" }, From 875b8b434ad4bd78530b5a209bfd85a6ccf210e4 Mon Sep 17 00:00:00 2001 From: Michael Wang Date: Mon, 1 Dec 2025 11:31:22 +1100 Subject: [PATCH 17/17] fix string --- src/censo/config/paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/censo/config/paths.py b/src/censo/config/paths.py index 99c6ee7e..19041b30 100644 --- a/src/censo/config/paths.py +++ b/src/censo/config/paths.py @@ -33,7 +33,7 @@ class PathsConfig(BaseModel): """ORCA version string. Should be extracted from somewhere.""" psi4: str = Field("") - """Absolute path to the turbomole binary directory.""" + """Absolute path to the psi4 binary directory.""" tm: str = Field("") """Absolute path to the turbomole binary directory."""