From 52322bef2f38cc3e447d01c690397e3bf35e768b Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Wed, 7 Feb 2024 21:18:40 +0100 Subject: [PATCH 01/41] Added a few methods and reference wavefunctions. Also add QRO, ghost_atoms and basis_set methods. --- src/tcutility/job/orca.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index aa99480f..7c0698e3 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -1,7 +1,8 @@ -from tcutility import log, results +from tcutility import log, results, ensure_list, spell_check from tcutility.job.generic import Job import subprocess as sp import os +from typing import List, Union j = os.path.join @@ -13,6 +14,8 @@ def __init__(self, *args, **kwargs): self.settings.main = {'LARGEPRINT'} self._charge = 0 self._multiplicity = 1 + self._ghosts = [] + self._method = None self.memory = None self.processes = None self.orca_path = None @@ -26,6 +29,22 @@ def __remove_task(self): self.__casefold_main() [self.settings.main.discard(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] + def method(self, method): + spell_check.check(method, ['MP2', 'CCSD', 'CCSD(T)', 'CCSDT']) + self.settings.main.append(method) + self._method = method + + def reference(self, ref): + spell_check.check(ref, ['UHF', 'UKS', 'RHF', 'RKS', 'ROHF', 'ROKS']) + self.settings.main.append(method) + self._method = method + + def QRO(self, enable=True): + self.settings.MDCI.UseQRO = enable + + def basis_set(self, value): + self.settings.main.append(value) + def single_point(self): self.__remove_task() self.settings.main.add('sp') @@ -60,6 +79,9 @@ def spin_polarization(self, val): def multiplicity(self, val): self._multiplicity = val + def ghost_atoms(self, indices: Union[int, List[int]]): + self._ghosts.extend(ensure_list(indices)) + def get_memory_usage(self): mem = self.memory or self._sbatch.mem or None @@ -111,15 +133,18 @@ def get_input(self): else: ret += f'* xyz {self._charge} {self._multiplicity}\n' - for atom in self._molecule: - ret += f' {atom.symbol:2} {atom.x: >13f} {atom.y: >13f} {atom.z: >13f}\n' + for i, atom in enumerate(self._molecule, start=1): + if i in self._ghosts: + ret += f' {atom.symbol:2}: {atom.x: >13f} {atom.y: >13f} {atom.z: >13f}\n' + else: + ret += f' {atom.symbol:3} {atom.x: >13f} {atom.y: >13f} {atom.z: >13f}\n' ret += '*\n' return ret def _setup_job(self): try: - if self.orca_path is None: + if self.orca_path is None and not self.test_mode: self.orca_path = sp.check_output(['which', 'orca']).decode().strip() except sp.CalledProcessError: log.error('Could not find the orca path. Please set it manually.') @@ -142,6 +167,8 @@ def _setup_job(self): return True + + if __name__ == '__main__': job = ORCAJob() job.molecule('water.xyz') From 4a92ab61f7627b1794a749d878b9ef624e841c18 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Wed, 7 Feb 2024 21:38:27 +0100 Subject: [PATCH 02/41] Multiplity is spinpol + 1 --- src/tcutility/job/orca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 7c0698e3..805b752e 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -74,7 +74,7 @@ def charge(self, val): self._charge = val def spin_polarization(self, val): - self._multiplicity = 2 * val + 1 + self._multiplicity = val + 1 def multiplicity(self, val): self._multiplicity = val From 8301c3dee6187462cf3d2b87377dba0d92c9be89 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Wed, 7 Feb 2024 22:06:45 +0100 Subject: [PATCH 03/41] Fixed a typo in useqros --- src/tcutility/job/orca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 805b752e..366e1cfd 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -40,7 +40,7 @@ def reference(self, ref): self._method = method def QRO(self, enable=True): - self.settings.MDCI.UseQRO = enable + self.settings.MDCI.UseQROs = enable def basis_set(self, value): self.settings.main.append(value) From dfb5685fd293667205d3cd14755cf17609ccab4c Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 8 Feb 2024 16:52:20 +0100 Subject: [PATCH 04/41] Moved main settings to a new place --- src/tcutility/job/orca.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 366e1cfd..4d24312b 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -11,7 +11,6 @@ class ORCAJob(Job): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.settings.main = {'LARGEPRINT'} self._charge = 0 self._multiplicity = 1 self._ghosts = [] @@ -19,56 +18,62 @@ def __init__(self, *args, **kwargs): self.memory = None self.processes = None self.orca_path = None - + self.main = set() self.single_point() + def add_main(self, key: str): + ''' + Add a keyword to the main input of the ORCA input. + ''' + self.main.add(key) + def __casefold_main(self): - self.settings.main = {key.casefold() for key in self.settings.main} + self.main = {key.casefold() for key in self.main} def __remove_task(self): self.__casefold_main() - [self.settings.main.discard(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] + [self.main.discard(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] def method(self, method): spell_check.check(method, ['MP2', 'CCSD', 'CCSD(T)', 'CCSDT']) - self.settings.main.append(method) + self.main.append(method) self._method = method def reference(self, ref): spell_check.check(ref, ['UHF', 'UKS', 'RHF', 'RKS', 'ROHF', 'ROKS']) - self.settings.main.append(method) - self._method = method + self.main.append(ref) + self._reference = ref def QRO(self, enable=True): self.settings.MDCI.UseQROs = enable def basis_set(self, value): - self.settings.main.append(value) + self.main.append(value) def single_point(self): self.__remove_task() - self.settings.main.add('sp') + self.main.add('sp') def transition_state(self): self.__remove_task() self.vibrations() - self.settings.main.add('optts') + self.main.add('optts') def optimization(self): self.__remove_task() self.vibrations() - self.settings.main.add('opt') + self.main.add('opt') def vibrations(self, enable=True, numerical=False): self.__casefold_main() - self.settings.main.discard('numfreq') - self.settings.main.discard('freq') + self.main.discard('numfreq') + self.main.discard('freq') if not enable: return if numerical: - self.settings.main.add('numfreq') + self.main.add('numfreq') else: - self.settings.main.add('freq') + self.main.add('freq') def charge(self, val): self._charge = val @@ -108,7 +113,7 @@ def get_input(self): log.warn('MaxCore and nprocs not specified. Please use SBATCH settings or set job.processes and job.memory.') ret = '' - for key in self.settings.main: + for key in self.main: ret += f'!{key}\n' ret += '\n' From 89c127f36167516b803ed8a8bbf1a16fa01d7af7 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 8 Feb 2024 16:52:47 +0100 Subject: [PATCH 05/41] Implemented base_job, this allows you to make a common job that will be used to build new ones more quickly --- src/tcutility/job/__init__.py | 4 ++ src/tcutility/job/generic.py | 83 ++++++++++++++++++++++++++++++----- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/tcutility/job/__init__.py b/src/tcutility/job/__init__.py index 69d6ae5f..6fe90e37 100644 --- a/src/tcutility/job/__init__.py +++ b/src/tcutility/job/__init__.py @@ -6,3 +6,7 @@ from .crest import CRESTJob, QCGJob # noqa: F401 from .orca import ORCAJob # noqa: F401 from .nmr import NMRJob # noqa: F401 + +# the base_job object will be copied to all created Job objects +# this will eventually be moved to tcutility.config.base_job +base_job = Job() diff --git a/src/tcutility/job/generic.py b/src/tcutility/job/generic.py index 381f6097..ba3b7642 100644 --- a/src/tcutility/job/generic.py +++ b/src/tcutility/job/generic.py @@ -5,23 +5,35 @@ from typing import Union from scm import plams import shutil +import copy j = os.path.join -class Job: - '''This is the base Job class used to build more advanced classes such as :class:`AMSJob ` and :class:`ORCAJob `. +class PostInitCaller(type): + def __call__(cls, *args, **kwargs): + obj = type.__call__(cls, *args, **kwargs) + obj.__post_init__(*args, **kwargs) + return obj + + +class Job(metaclass=PostInitCaller): + ''' + This is the base Job class used to build more advanced classes such as :class:`AMSJob ` and :class:`ORCAJob `. The base class contains an empty :class:`Result ` object that holds the settings. It also provides :meth:`__enter__` and :meth:`__exit__` methods to make use of context manager syntax. All class methods are in principle safe to overwrite, but the :meth:`_setup_job` method **must** be overwritten. Args: - test_mode: whether to enable the testing mode. If enabled, the job will be setup like normally, but the running step is skipped. This is useful if you want to know what the job settings look like before running the real calculations. + base_job: a :class:`Job` object whose settings and properties are copied over to the newly created job. + It defaults to the ``tcutility.job.base_job`` object. + test_mode: whether to enable the testing mode. If enabled, the job will be setup like normally, but the running step is skipped. + This is useful if you want to know what the job settings look like before running the real calculations. overwrite: whether to overwrite a previously run job in the same working directory. wait_for_finish: whether to wait for this job to finish running before continuing your runscript. ''' - def __init__(self, test_mode: bool = False, overwrite: bool = False, wait_for_finish: bool = False): + def __init__(self, base_job: 'Job' = None, test_mode: bool = False, overwrite: bool = False, wait_for_finish: bool = False): self.settings = results.Result() self._sbatch = results.Result() self._molecule = None @@ -29,11 +41,29 @@ def __init__(self, test_mode: bool = False, overwrite: bool = False, wait_for_fi self.slurm_job_id = None self.name = 'calc' self.rundir = 'tmp' + self._preambles = [] + self._postambles = [] + self.test_mode = test_mode self.overwrite = overwrite self.wait_for_finish = wait_for_finish - self._preambles = [] - self._postambles = [] + self.base_job = base_job + + def __post_init__(self, *args, **kwargs): + # if base_job is given we want to copy its properties and settings to this job + if self.base_job is not None: + self.copy_from_job(self.base_job) + # if not given we default to the global base-job + elif self.base_job is None: + # we must capture this in a try/except block because the global base-job cannot import itself of course + try: + from tcutility.job import base_job as global_base_job + + self.copy_from_job(global_base_job) + except ImportError: + pass + + [setattr(self, key, value) for key, value in kwargs.items()] def __enter__(self): return self @@ -41,6 +71,19 @@ def __enter__(self): def __exit__(self, *args): self.run() + def copy_from_job(self, other: 'Job'): + ''' + Copy settings from an ``other`` job to this job. + + Args: + other: a job containing the settings that will be copied to this job. + + .. note:: + Instead of calling this function yourself you can also use the ``base_job`` argument when constructing a :class:`Job` object. + ''' + [setattr(self, key, copy.copy(val)) for key, val in other.__dict__.items()] + # self.__dict__.update(other.__dict__) + def can_skip(self): ''' Check whether the job can be skipped. We check this by loading the calculation and checking if the job status was fatal. @@ -156,8 +199,19 @@ def add_postamble(self, line: str): self._postambles.append(line) def add_postscript(self, script): + ''' + Add a post-script to this job. The post-script is a Python script that will be executed after the job is finished. + + Args: + script: the script to be run after the job is finished. It can be a ``str`` or an imported Python module. + In the latter case it will take the ``__script__`` property of the module to get its location. + ''' + # to be sure we are in the correct directory we will switch to the working directory again self.add_postamble(f'cd {self.workdir}') - self.add_postamble(f'python {script.__file__}') + if isinstance(script, str): + self.add_postamble(f'python {script}') + else: + self.add_postamble(f'python {script.__file__}') def dependency(self, otherjob: 'Job'): ''' @@ -168,28 +222,28 @@ def dependency(self, otherjob: 'Job'): self.sbatch(kill_on_invalid_dep='Yes') @property - def workdir(self): + def workdir(self) -> str: ''' The working directory of this job. All important files are written here, for example the input file and runscript. ''' return j(os.path.abspath(self.rundir), self.name) @property - def runfile_path(self): + def runfile_path(self) -> str: ''' The file path to the runscript of this job. ''' return j(self.workdir, f'{self.name}.run') @property - def inputfile_path(self): + def inputfile_path(self) -> str: ''' The file path to the input file of this job. ''' return j(self.workdir, f'{self.name}.in') @property - def output_mol_path(self): + def output_mol_path(self) -> str: ''' This method should return the name of the output molecule if it makes sense to give it back. E.g. for ADF it will be output.xyz in the workdir for optimization jobs. @@ -219,3 +273,10 @@ def molecule(self, mol: Union[str, plams.Molecule, plams.Atom, list[plams.Atom]] elif isinstance(mol, plams.Atom): self._molecule = plams.Molecule() self._molecule.add_atom(mol) + + +if __name__ == '__main__': + with Job() as job2: + print(job2._sbatch) + print(job2.rundir) + print(job2._molecule) From 9703366e6745e82404ae6885a07d5272645157d6 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 8 Feb 2024 17:15:55 +0100 Subject: [PATCH 06/41] Revert "Implemented base_job, this allows you to make a common job that will be used to build new ones more quickly" This reverts commit 89c127f36167516b803ed8a8bbf1a16fa01d7af7. --- src/tcutility/job/__init__.py | 4 -- src/tcutility/job/generic.py | 83 +++++------------------------------ 2 files changed, 11 insertions(+), 76 deletions(-) diff --git a/src/tcutility/job/__init__.py b/src/tcutility/job/__init__.py index 6fe90e37..69d6ae5f 100644 --- a/src/tcutility/job/__init__.py +++ b/src/tcutility/job/__init__.py @@ -6,7 +6,3 @@ from .crest import CRESTJob, QCGJob # noqa: F401 from .orca import ORCAJob # noqa: F401 from .nmr import NMRJob # noqa: F401 - -# the base_job object will be copied to all created Job objects -# this will eventually be moved to tcutility.config.base_job -base_job = Job() diff --git a/src/tcutility/job/generic.py b/src/tcutility/job/generic.py index ba3b7642..381f6097 100644 --- a/src/tcutility/job/generic.py +++ b/src/tcutility/job/generic.py @@ -5,35 +5,23 @@ from typing import Union from scm import plams import shutil -import copy j = os.path.join -class PostInitCaller(type): - def __call__(cls, *args, **kwargs): - obj = type.__call__(cls, *args, **kwargs) - obj.__post_init__(*args, **kwargs) - return obj - - -class Job(metaclass=PostInitCaller): - ''' - This is the base Job class used to build more advanced classes such as :class:`AMSJob ` and :class:`ORCAJob `. +class Job: + '''This is the base Job class used to build more advanced classes such as :class:`AMSJob ` and :class:`ORCAJob `. The base class contains an empty :class:`Result ` object that holds the settings. It also provides :meth:`__enter__` and :meth:`__exit__` methods to make use of context manager syntax. All class methods are in principle safe to overwrite, but the :meth:`_setup_job` method **must** be overwritten. Args: - base_job: a :class:`Job` object whose settings and properties are copied over to the newly created job. - It defaults to the ``tcutility.job.base_job`` object. - test_mode: whether to enable the testing mode. If enabled, the job will be setup like normally, but the running step is skipped. - This is useful if you want to know what the job settings look like before running the real calculations. + test_mode: whether to enable the testing mode. If enabled, the job will be setup like normally, but the running step is skipped. This is useful if you want to know what the job settings look like before running the real calculations. overwrite: whether to overwrite a previously run job in the same working directory. wait_for_finish: whether to wait for this job to finish running before continuing your runscript. ''' - def __init__(self, base_job: 'Job' = None, test_mode: bool = False, overwrite: bool = False, wait_for_finish: bool = False): + def __init__(self, test_mode: bool = False, overwrite: bool = False, wait_for_finish: bool = False): self.settings = results.Result() self._sbatch = results.Result() self._molecule = None @@ -41,29 +29,11 @@ def __init__(self, base_job: 'Job' = None, test_mode: bool = False, overwrite: b self.slurm_job_id = None self.name = 'calc' self.rundir = 'tmp' - self._preambles = [] - self._postambles = [] - self.test_mode = test_mode self.overwrite = overwrite self.wait_for_finish = wait_for_finish - self.base_job = base_job - - def __post_init__(self, *args, **kwargs): - # if base_job is given we want to copy its properties and settings to this job - if self.base_job is not None: - self.copy_from_job(self.base_job) - # if not given we default to the global base-job - elif self.base_job is None: - # we must capture this in a try/except block because the global base-job cannot import itself of course - try: - from tcutility.job import base_job as global_base_job - - self.copy_from_job(global_base_job) - except ImportError: - pass - - [setattr(self, key, value) for key, value in kwargs.items()] + self._preambles = [] + self._postambles = [] def __enter__(self): return self @@ -71,19 +41,6 @@ def __enter__(self): def __exit__(self, *args): self.run() - def copy_from_job(self, other: 'Job'): - ''' - Copy settings from an ``other`` job to this job. - - Args: - other: a job containing the settings that will be copied to this job. - - .. note:: - Instead of calling this function yourself you can also use the ``base_job`` argument when constructing a :class:`Job` object. - ''' - [setattr(self, key, copy.copy(val)) for key, val in other.__dict__.items()] - # self.__dict__.update(other.__dict__) - def can_skip(self): ''' Check whether the job can be skipped. We check this by loading the calculation and checking if the job status was fatal. @@ -199,19 +156,8 @@ def add_postamble(self, line: str): self._postambles.append(line) def add_postscript(self, script): - ''' - Add a post-script to this job. The post-script is a Python script that will be executed after the job is finished. - - Args: - script: the script to be run after the job is finished. It can be a ``str`` or an imported Python module. - In the latter case it will take the ``__script__`` property of the module to get its location. - ''' - # to be sure we are in the correct directory we will switch to the working directory again self.add_postamble(f'cd {self.workdir}') - if isinstance(script, str): - self.add_postamble(f'python {script}') - else: - self.add_postamble(f'python {script.__file__}') + self.add_postamble(f'python {script.__file__}') def dependency(self, otherjob: 'Job'): ''' @@ -222,28 +168,28 @@ def dependency(self, otherjob: 'Job'): self.sbatch(kill_on_invalid_dep='Yes') @property - def workdir(self) -> str: + def workdir(self): ''' The working directory of this job. All important files are written here, for example the input file and runscript. ''' return j(os.path.abspath(self.rundir), self.name) @property - def runfile_path(self) -> str: + def runfile_path(self): ''' The file path to the runscript of this job. ''' return j(self.workdir, f'{self.name}.run') @property - def inputfile_path(self) -> str: + def inputfile_path(self): ''' The file path to the input file of this job. ''' return j(self.workdir, f'{self.name}.in') @property - def output_mol_path(self) -> str: + def output_mol_path(self): ''' This method should return the name of the output molecule if it makes sense to give it back. E.g. for ADF it will be output.xyz in the workdir for optimization jobs. @@ -273,10 +219,3 @@ def molecule(self, mol: Union[str, plams.Molecule, plams.Atom, list[plams.Atom]] elif isinstance(mol, plams.Atom): self._molecule = plams.Molecule() self._molecule.add_atom(mol) - - -if __name__ == '__main__': - with Job() as job2: - print(job2._sbatch) - print(job2.rundir) - print(job2._molecule) From 66bd6eb508181f08b05511e17dd735922be82993 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 8 Feb 2024 17:16:05 +0100 Subject: [PATCH 07/41] Revert "Moved main settings to a new place" This reverts commit dfb5685fd293667205d3cd14755cf17609ccab4c. --- src/tcutility/job/orca.py | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 4d24312b..366e1cfd 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -11,6 +11,7 @@ class ORCAJob(Job): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.settings.main = {'LARGEPRINT'} self._charge = 0 self._multiplicity = 1 self._ghosts = [] @@ -18,62 +19,56 @@ def __init__(self, *args, **kwargs): self.memory = None self.processes = None self.orca_path = None - self.main = set() - self.single_point() - def add_main(self, key: str): - ''' - Add a keyword to the main input of the ORCA input. - ''' - self.main.add(key) + self.single_point() def __casefold_main(self): - self.main = {key.casefold() for key in self.main} + self.settings.main = {key.casefold() for key in self.settings.main} def __remove_task(self): self.__casefold_main() - [self.main.discard(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] + [self.settings.main.discard(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] def method(self, method): spell_check.check(method, ['MP2', 'CCSD', 'CCSD(T)', 'CCSDT']) - self.main.append(method) + self.settings.main.append(method) self._method = method def reference(self, ref): spell_check.check(ref, ['UHF', 'UKS', 'RHF', 'RKS', 'ROHF', 'ROKS']) - self.main.append(ref) - self._reference = ref + self.settings.main.append(method) + self._method = method def QRO(self, enable=True): self.settings.MDCI.UseQROs = enable def basis_set(self, value): - self.main.append(value) + self.settings.main.append(value) def single_point(self): self.__remove_task() - self.main.add('sp') + self.settings.main.add('sp') def transition_state(self): self.__remove_task() self.vibrations() - self.main.add('optts') + self.settings.main.add('optts') def optimization(self): self.__remove_task() self.vibrations() - self.main.add('opt') + self.settings.main.add('opt') def vibrations(self, enable=True, numerical=False): self.__casefold_main() - self.main.discard('numfreq') - self.main.discard('freq') + self.settings.main.discard('numfreq') + self.settings.main.discard('freq') if not enable: return if numerical: - self.main.add('numfreq') + self.settings.main.add('numfreq') else: - self.main.add('freq') + self.settings.main.add('freq') def charge(self, val): self._charge = val @@ -113,7 +108,7 @@ def get_input(self): log.warn('MaxCore and nprocs not specified. Please use SBATCH settings or set job.processes and job.memory.') ret = '' - for key in self.main: + for key in self.settings.main: ret += f'!{key}\n' ret += '\n' From 5764dd0e46196561475a1659999d644dd63eadae Mon Sep 17 00:00:00 2001 From: YHordijk <42876712+YHordijk@users.noreply.github.com> Date: Sun, 11 Feb 2024 22:43:56 +0100 Subject: [PATCH 08/41] Added the recipe for the CP correction calculation using orca. I will clean it up --- recipes/job/orca_counter_poise.py | 62 +++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 recipes/job/orca_counter_poise.py diff --git a/recipes/job/orca_counter_poise.py b/recipes/job/orca_counter_poise.py new file mode 100644 index 00000000..57e3c8ff --- /dev/null +++ b/recipes/job/orca_counter_poise.py @@ -0,0 +1,62 @@ +from tcutility.job import ORCAJob +from tcutility import molecule + + + +test_mode = False +sbatch = dict(p='tc', n=32, mem=224000, t='120:00:00') + +mol = molecule.load('RC_C2H4_CH3.xyz') +frags = molecule.guess_fragments(mol) + +# for each fragment we do an optimization and single point on the complex geometry +for fragname, fragmol in frags.items(): + with ORCAJob(test_mode=test_mode) as job: + job.molecule(fragmol) + job.optimization() + job.name = f'frag_{fragname}_GO' + job.spin_polarization(fragmol.flags.get('spinpol', 0)) + job.sbatch(**sbatch) + job.rundir = 'calculations' + job.add_preamble('export PATH=/scistor/tc/dra480/bin/ompi411/bin:$PATH') + job.add_preamble('export LD_LIBRARY_PATH=/scistor/tc/dra480/bin/ompi411/lib/:$LD_LIBRARY_PATH') + job.orca_path = "/scistor/tc/dra480/bin/orca500/orca" + job.settings.mdci.UseQROs = 'True' + [job.settings.main.add(key) for key in 'CCSD(T) CC-pVTZ TightSCF UNO'.split()] + + with ORCAJob(test_mode=test_mode) as job: + job.molecule(fragmol) + job.name = f'frag_{fragname}_SP' + job.spin_polarization(fragmol.flags.get('spinpol', 0)) + job.sbatch(**sbatch) + job.rundir = 'calculations' + job.add_preamble('export PATH=/scistor/tc/dra480/bin/ompi411/bin:$PATH') + job.add_preamble('export LD_LIBRARY_PATH=/scistor/tc/dra480/bin/ompi411/lib/:$LD_LIBRARY_PATH') + job.orca_path = "/scistor/tc/dra480/bin/orca500/orca" + job.settings.mdci.UseQROs = 'True' + [job.settings.main.add(key) for key in 'CCSD(T) CC-pVTZ TightSCF UNO'.split()] + + with ORCAJob(test_mode=test_mode) as job: + job.molecule(mol) + job.name = f'complex_frag_{fragname}_BS' + job.spin_polarization(sum([fragmol.flags.get('spinpol', 0) for fragmol in frags.values()]) - fragmol.flags.get('spinpol', 0)) + job.ghost_atoms([mol.atoms.index(atom) + 1 for atom in fragmol]) + job.sbatch(**sbatch) + job.rundir = 'calculations' + job.add_preamble('export PATH=/scistor/tc/dra480/bin/ompi411/bin:$PATH') + job.add_preamble('export LD_LIBRARY_PATH=/scistor/tc/dra480/bin/ompi411/lib/:$LD_LIBRARY_PATH') + job.orca_path = "/scistor/tc/dra480/bin/orca500/orca" + job.settings.mdci.UseQROs = 'True' + [job.settings.main.add(key) for key in 'CCSD(T) CC-pVTZ TightSCF UNO'.split()] + +with ORCAJob(test_mode=test_mode) as job: + job.molecule(mol) + job.name = 'complex' + job.spin_polarization(sum([fragmol.flags.get('spinpol', 0) for fragmol in frags.values()])) + job.sbatch(**sbatch) + job.rundir = 'calculations' + job.add_preamble('export PATH=/scistor/tc/dra480/bin/ompi411/bin:$PATH') + job.add_preamble('export LD_LIBRARY_PATH=/scistor/tc/dra480/bin/ompi411/lib/:$LD_LIBRARY_PATH') + job.orca_path = "/scistor/tc/dra480/bin/orca500/orca" + job.settings.mdci.UseQROs = 'True' + [job.settings.main.add(key) for key in 'CCSD(T) CC-pVTZ TightSCF UNO'.split()] From f64dab8219e64371972d929bc93d6cd92feba4a5 Mon Sep 17 00:00:00 2001 From: YHordijk <42876712+YHordijk@users.noreply.github.com> Date: Sun, 11 Feb 2024 22:44:13 +0100 Subject: [PATCH 09/41] Fixed a typo in the reference method --- src/tcutility/job/orca.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 366e1cfd..97830ee2 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -36,8 +36,8 @@ def method(self, method): def reference(self, ref): spell_check.check(ref, ['UHF', 'UKS', 'RHF', 'RKS', 'ROHF', 'ROKS']) - self.settings.main.append(method) - self._method = method + self.settings.main.append(ref) + self._method = ref def QRO(self, enable=True): self.settings.MDCI.UseQROs = enable From f618e8da46fa7cff2d6d5aafd173feaa1b8f1136 Mon Sep 17 00:00:00 2001 From: YHordijk <42876712+YHordijk@users.noreply.github.com> Date: Sun, 11 Feb 2024 22:44:36 +0100 Subject: [PATCH 10/41] Added an issue with reading ORCA calculations that use ghost atoms --- src/tcutility/results/orca.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tcutility/results/orca.py b/src/tcutility/results/orca.py index d353b994..0f8af9ee 100644 --- a/src/tcutility/results/orca.py +++ b/src/tcutility/results/orca.py @@ -147,6 +147,7 @@ def get_input(info: Result) -> Result: if coordinates in ["xyz", "int"]: ret.system.molecule = plams.Molecule() for line in system_lines: + line = line.replace(':', '') ret.system.molecule.add_atom(plams.Atom(symbol=line.split()[0], coords=[float(x) for x in line.split()[1:4]])) info.task = "SinglePoint" From 8de298249a863db884b1f9f66e7d8b4f34043c1a Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 12 Feb 2024 11:30:13 +0100 Subject: [PATCH 11/41] Added a method to add main settings --- src/tcutility/job/orca.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 366e1cfd..c16f16ad 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -25,6 +25,20 @@ def __init__(self, *args, **kwargs): def __casefold_main(self): self.settings.main = {key.casefold() for key in self.settings.main} + def main(self, val: Union[str, list[str]]): + ''' + Add main options for this ORCA calculation, they will be added to the input prepended with exclamation marks. + + Args: + val: the main options to add. This can be a string or a list of strings with the main options. + ''' + # we want to split a string such as 'CC-pVTZ Opt CCSD(T)' into loose parts and add them separately + # this should always return a list + if isinstance(val, str): + val = val.split() + # add each + [self.settings.main.add(key) for key in val] + def __remove_task(self): self.__casefold_main() [self.settings.main.discard(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] From d966fea8c6ae4fc082aa89db1a527b07a88833af Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Tue, 13 Feb 2024 14:09:16 +0100 Subject: [PATCH 12/41] Removed LARGEPRINT by default --- src/tcutility/job/orca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 1333f0d5..62dc27bd 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -11,7 +11,7 @@ class ORCAJob(Job): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.settings.main = {'LARGEPRINT'} + self.settings.main = set() self._charge = 0 self._multiplicity = 1 self._ghosts = [] From e5dbaa0ef4e2c3acacbe274433c8a210bf822cf2 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Tue, 13 Feb 2024 14:09:31 +0100 Subject: [PATCH 13/41] Now checking if ghost atoms are used, if they are reduce number of processes used to prevent ORCA errors --- src/tcutility/job/orca.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 62dc27bd..bab4a5be 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -116,6 +116,8 @@ def get_input(self): if ntasks and mem: natoms = len(self._molecule) ntasks = min(ntasks, (natoms - 1) * 3) + if len(self._ghosts) > 0: + ntasks -= 6 self.settings.PAL.nprocs = ntasks self.settings.maxcore = int(mem / ntasks * 0.75) else: @@ -161,11 +163,11 @@ def _setup_job(self): if self.orca_path is None and not self.test_mode: self.orca_path = sp.check_output(['which', 'orca']).decode().strip() except sp.CalledProcessError: - log.error('Could not find the orca path. Please set it manually.') + log.error(f'Could not find the orca path. Call set the {self.__class__.__name__}.orca_path property to add it.') return if not self._molecule and not self._molecule_path: - log.error(f'You did not supply a molecule for this job. Call the {self.__class__}.molecule method to add one.') + log.error(f'You did not supply a molecule for this job. Call the {self.__class__.__name__}.molecule method to add one.') return os.makedirs(self.workdir, exist_ok=True) From 9c7b493b05626756901e6fb8dfdc8ca7af5ac1f0 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 19 Feb 2024 10:25:04 +0100 Subject: [PATCH 14/41] Added the remove_main method for removing keys from main without case-sensitivity --- src/tcutility/job/orca.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index bab4a5be..31d6df24 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -39,6 +39,15 @@ def main(self, val: Union[str, list[str]]): # add each [self.settings.main.add(key) for key in val] + def remove_main(self, val: Union[str, List[str]]): + if isinstance(val, str): + val = val.split() + + lower_main = {key.casefold(): key for key in self.settings.main} + for v in val: + if v.casefold() in lower_main: + self.settings.main.discard(lower_main[v.casefold()]) + def __remove_task(self): self.__casefold_main() [self.settings.main.discard(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] From d7f81d905a3389d8b23b50991998936cf9304559 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 19 Feb 2024 10:26:49 +0100 Subject: [PATCH 15/41] Added the __casefold_main method because it is no longer needed --- src/tcutility/job/orca.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 31d6df24..b7482f1f 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -22,10 +22,7 @@ def __init__(self, *args, **kwargs): self.single_point() - def __casefold_main(self): - self.settings.main = {key.casefold() for key in self.settings.main} - - def main(self, val: Union[str, list[str]]): + def main(self, val: Union[str, List[str]]): ''' Add main options for this ORCA calculation, they will be added to the input prepended with exclamation marks. @@ -49,8 +46,7 @@ def remove_main(self, val: Union[str, List[str]]): self.settings.main.discard(lower_main[v.casefold()]) def __remove_task(self): - self.__casefold_main() - [self.settings.main.discard(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] + [self.remove_main(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] def method(self, method): spell_check.check(method, ['MP2', 'CCSD', 'CCSD(T)', 'CCSDT']) @@ -83,9 +79,8 @@ def optimization(self): self.settings.main.add('opt') def vibrations(self, enable=True, numerical=False): - self.__casefold_main() - self.settings.main.discard('numfreq') - self.settings.main.discard('freq') + self.remove_main('NumFreq') + self.remove_main('Freq') if not enable: return if numerical: From aebb28c1fe46872923721c1922e6a08fb30fbefb Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 19 Feb 2024 10:27:11 +0100 Subject: [PATCH 16/41] Changed some default main keys to be upper case --- src/tcutility/job/orca.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index b7482f1f..6ea3705d 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -66,17 +66,17 @@ def basis_set(self, value): def single_point(self): self.__remove_task() - self.settings.main.add('sp') + self.settings.main.add('SP') def transition_state(self): self.__remove_task() self.vibrations() - self.settings.main.add('optts') + self.settings.main.add('OptTS') def optimization(self): self.__remove_task() self.vibrations() - self.settings.main.add('opt') + self.settings.main.add('Opt') def vibrations(self, enable=True, numerical=False): self.remove_main('NumFreq') @@ -84,9 +84,9 @@ def vibrations(self, enable=True, numerical=False): if not enable: return if numerical: - self.settings.main.add('numfreq') + self.settings.main.add('NumFreq') else: - self.settings.main.add('freq') + self.settings.main.add('Freq') def charge(self, val): self._charge = val From 146ff3d3d6a115d6f0428a12ffa03dd866b1b654 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 19 Feb 2024 10:27:25 +0100 Subject: [PATCH 17/41] Improved some error message --- src/tcutility/job/orca.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 6ea3705d..70e5daca 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -167,7 +167,7 @@ def _setup_job(self): if self.orca_path is None and not self.test_mode: self.orca_path = sp.check_output(['which', 'orca']).decode().strip() except sp.CalledProcessError: - log.error(f'Could not find the orca path. Call set the {self.__class__.__name__}.orca_path property to add it.') + log.error(f'Could not find the orca path. Set the {self.__class__.__name__}.orca_path attribute to add it.') return if not self._molecule and not self._molecule_path: @@ -191,5 +191,5 @@ def _setup_job(self): if __name__ == '__main__': job = ORCAJob() - job.molecule('water.xyz') - job._setup_job() + job.main('OPT cc-pVTZ') + job.remove_main('OPT OPTTS NEB') From 4b177fc1412daef12f6c3246ccec9061da5f0a08 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 18 Mar 2024 15:07:43 +0100 Subject: [PATCH 18/41] For now testing working with TMPDIRs --- src/tcutility/job/orca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 70e5daca..6f015aba 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -181,7 +181,7 @@ def _setup_job(self): with open(self.runfile_path, 'w+') as runf: runf.write('#!/bin/sh\n\n') # the shebang is not written by default by ADF runf.write('\n'.join(self._preambles) + '\n\n') - runf.write(f'{self.orca_path} {self.inputfile_path}\n') + runf.write(f'{self.orca_path} $TMPDIR/{self.name}.in\n') runf.write('\n'.join(self._postambles)) return True From 6571b4dd7f3cc1efada7d6c9306d5c9acd71395f Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 18 Mar 2024 16:06:46 +0100 Subject: [PATCH 19/41] Implemented an option to use tmpdir for ORCAJob --- src/tcutility/job/orca.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 6f015aba..84f58931 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -9,7 +9,7 @@ class ORCAJob(Job): - def __init__(self, *args, **kwargs): + def __init__(self, use_tmpdir=False, *args, **kwargs): super().__init__(*args, **kwargs) self.settings.main = set() self._charge = 0 @@ -19,6 +19,7 @@ def __init__(self, *args, **kwargs): self.memory = None self.processes = None self.orca_path = None + self.use_tmpdir = use_tmpdir self.single_point() @@ -181,7 +182,20 @@ def _setup_job(self): with open(self.runfile_path, 'w+') as runf: runf.write('#!/bin/sh\n\n') # the shebang is not written by default by ADF runf.write('\n'.join(self._preambles) + '\n\n') - runf.write(f'{self.orca_path} $TMPDIR/{self.name}.in\n') + + if self.use_tmpdir: + runf.write('export TMPDIR="$TMPDIR/$SLURM_JOB_ID"\n') + runf.write('mkdir -p $TMPDIR\n') + runf.write('cd $TMPDIR\n') + runf.write(f'cp {self.inputfile_path} $TMPDIR\n') + + runf.write(f'{self.orca_path} $TMPDIR/{self.name}.in\n') + + runf.write('cp $TMPDIR/* $SLURM_SUBMIT_DIR\n') + runf.write('rm -rf $TMPDIR\n') + else: + runf.write(f'{self.orca_path} {self.inputfile_path}.in\n') + runf.write('\n'.join(self._postambles)) return True From 7d747ca3e6bedb2a4910a838930078df9bb09a7a Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 18 Mar 2024 16:21:32 +0100 Subject: [PATCH 20/41] Now checking if we can use slurm before using TMPDIR --- src/tcutility/job/orca.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 84f58931..e15eaa65 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -1,4 +1,4 @@ -from tcutility import log, results, ensure_list, spell_check +from tcutility import log, results, ensure_list, spell_check, slurm from tcutility.job.generic import Job import subprocess as sp import os @@ -183,7 +183,7 @@ def _setup_job(self): runf.write('#!/bin/sh\n\n') # the shebang is not written by default by ADF runf.write('\n'.join(self._preambles) + '\n\n') - if self.use_tmpdir: + if self.use_tmpdir and slurm.has_slurm(): runf.write('export TMPDIR="$TMPDIR/$SLURM_JOB_ID"\n') runf.write('mkdir -p $TMPDIR\n') runf.write('cd $TMPDIR\n') From 7c5846d900813b1c8da4ee4648b985e86d6eecfb Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Wed, 20 Mar 2024 17:19:22 +0100 Subject: [PATCH 21/41] output was written to the wrong directory. Now writing to workdir instead of slurm_submitdir --- src/tcutility/job/orca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index e15eaa65..bdc7c416 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -191,7 +191,7 @@ def _setup_job(self): runf.write(f'{self.orca_path} $TMPDIR/{self.name}.in\n') - runf.write('cp $TMPDIR/* $SLURM_SUBMIT_DIR\n') + runf.write(f'cp $TMPDIR/* {self.workdir}\n') runf.write('rm -rf $TMPDIR\n') else: runf.write(f'{self.orca_path} {self.inputfile_path}.in\n') From 8ed5ba8b90eeed146dd2f42b35b7e49a3c05e912 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Wed, 20 Mar 2024 17:21:43 +0100 Subject: [PATCH 22/41] Now catching and ignoring errors for orca calculations --- src/tcutility/results/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tcutility/results/__init__.py b/src/tcutility/results/__init__.py index f4a07d2d..1a618da9 100644 --- a/src/tcutility/results/__init__.py +++ b/src/tcutility/results/__init__.py @@ -100,8 +100,15 @@ def read(calc_dir: Union[str, pl.Path]) -> Result: ret.dftb = dftb.get_calc_settings(ret) ret.properties = dftb.get_properties(ret) elif ret.engine == "orca": - ret.orca = orca.get_calc_settings(ret) - ret.properties = orca.get_properties(ret) + try: + ret.orca = orca.get_calc_settings(ret) + except: + ret.orca = None + + try: + ret.properties = orca.get_properties(ret) + except: + ret.properties = None # unload cached KFReaders associated with this calc_dir to_delete = [key for key in cache._cache if key.startswith(os.path.abspath(calc_dir))] From 071b13446aabdffd607d97c447c1951a4fdbc000 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 21 Mar 2024 10:45:32 +0100 Subject: [PATCH 23/41] Printing errors again --- src/tcutility/results/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tcutility/results/__init__.py b/src/tcutility/results/__init__.py index 1a618da9..1e07752b 100644 --- a/src/tcutility/results/__init__.py +++ b/src/tcutility/results/__init__.py @@ -104,11 +104,13 @@ def read(calc_dir: Union[str, pl.Path]) -> Result: ret.orca = orca.get_calc_settings(ret) except: ret.orca = None + raise try: ret.properties = orca.get_properties(ret) except: ret.properties = None + raise # unload cached KFReaders associated with this calc_dir to_delete = [key for key in cache._cache if key.startswith(os.path.abspath(calc_dir))] From 73a88df613126a218884a163614483c534c8f03f Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 21 Mar 2024 10:47:41 +0100 Subject: [PATCH 24/41] Printing errors again --- src/tcutility/results/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tcutility/results/__init__.py b/src/tcutility/results/__init__.py index 1e07752b..eab2a458 100644 --- a/src/tcutility/results/__init__.py +++ b/src/tcutility/results/__init__.py @@ -104,12 +104,14 @@ def read(calc_dir: Union[str, pl.Path]) -> Result: ret.orca = orca.get_calc_settings(ret) except: ret.orca = None + print('Error reading:', calc_dir) raise try: ret.properties = orca.get_properties(ret) except: ret.properties = None + print('Error reading:', calc_dir) raise # unload cached KFReaders associated with this calc_dir From 5872b88fa1aa8d4e17e6f80b4db68af0972ddce8 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 21 Mar 2024 11:18:18 +0100 Subject: [PATCH 25/41] Now orca reader will check if the job is managed by slurm. This prevents it from submitting double jobs --- src/tcutility/results/orca.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/tcutility/results/orca.py b/src/tcutility/results/orca.py index 0f8af9ee..2b6bd0e8 100644 --- a/src/tcutility/results/orca.py +++ b/src/tcutility/results/orca.py @@ -1,5 +1,5 @@ from tcutility.results import Result -from tcutility import constants +from tcutility import constants, slurm import os from scm import plams import numpy as np @@ -252,6 +252,25 @@ def get_calculation_status(info: Result) -> Result: ret.reasons.append("Calculation status unknown") ret.name = "UNKNOWN" ret.code = "U" + + # check if the job is being managed by slurm + if not slurm.has_slurm(): + return ret + + # get the statuscode from the workdir + state = slurm.workdir_info(os.path.abspath(calc_dir)).statuscode + state_name = { + 'CG': 'COMPLETING', + 'CF': 'CONFIGURING', + 'PD': 'PENDING', + 'R': 'RUNNING' + }.get(state, 'UNKNOWN') + + res.fatal = False + res.name = state_name + res.code = state + res.reasons = [] + return ret with open(info.files.out) as out: @@ -529,5 +548,10 @@ def get_properties(info: Result) -> Result: if __name__ == "__main__": - ret = get_info("/Users/yumanhordijk/Library/CloudStorage/OneDrive-VrijeUniversiteitAmsterdam/RadicalAdditionBenchmark/data/abinitio/P_C2H2_NH2/OPT_pVTZ") - print(ret.molecule) + # ret = get_info("/Users/yumanhordijk/Library/CloudStorage/OneDrive-VrijeUniversiteitAmsterdam/RadicalAdditionBenchmark/data/abinitio/P_C2H2_NH2/OPT_pVTZ") + # print(ret.molecule) + + ret = get_info('/Users/yumanhordijk/Library/CloudStorage/OneDrive-VrijeUniversiteitAmsterdam/RadicalAdditionBenchmark2/data/SP_CCSDpT_augCCpVQZ') + print(ret.input) + prop = get_properties(ret) + # print(ret) From 281ca4e84a14d3eebd19a245b8d3464c33d210b4 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 21 Mar 2024 11:21:53 +0100 Subject: [PATCH 26/41] Now orca reader will check if the job is managed by slurm. This prevents it from submitting double jobs --- src/tcutility/results/orca.py | 41 +++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/tcutility/results/orca.py b/src/tcutility/results/orca.py index 2b6bd0e8..112a52fe 100644 --- a/src/tcutility/results/orca.py +++ b/src/tcutility/results/orca.py @@ -248,31 +248,14 @@ def get_calculation_status(info: Result) -> Result: ret.code = None ret.reasons = [] + # if we do not have an output file the calculation failed if "out" not in info.files.out: ret.reasons.append("Calculation status unknown") ret.name = "UNKNOWN" ret.code = "U" - - # check if the job is being managed by slurm - if not slurm.has_slurm(): - return ret - - # get the statuscode from the workdir - state = slurm.workdir_info(os.path.abspath(calc_dir)).statuscode - state_name = { - 'CG': 'COMPLETING', - 'CF': 'CONFIGURING', - 'PD': 'PENDING', - 'R': 'RUNNING' - }.get(state, 'UNKNOWN') - - res.fatal = False - res.name = state_name - res.code = state - res.reasons = [] - return ret + # try to read if the calculation succeeded with open(info.files.out) as out: lines = out.readlines() if any(["ORCA TERMINATED NORMALLY" in line for line in lines]): @@ -281,8 +264,28 @@ def get_calculation_status(info: Result) -> Result: ret.code = "S" return ret + # if it didnt we default to failed ret.name = "FAILED" ret.code = "F" + + # otherwise we check if the job is being managed by slurm + if not slurm.workdir_info(os.path.abspath(info.files.root)): + return ret + + # get the statuscode from the workdir + state = slurm.workdir_info(os.path.abspath(info.files.root)).statuscode + state_name = { + 'CG': 'COMPLETING', + 'CF': 'CONFIGURING', + 'PD': 'PENDING', + 'R': 'RUNNING' + }.get(state, 'UNKNOWN') + + res.fatal = False + res.name = state_name + res.code = state + res.reasons = [] + return ret From a39adbd40595dc384efecad991fa2f89d342c448 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 21 Mar 2024 12:11:44 +0100 Subject: [PATCH 27/41] Added an option to set flags for sbatch --- src/tcutility/slurm.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/tcutility/slurm.py b/src/tcutility/slurm.py index 0858c841..84f6efc6 100644 --- a/src/tcutility/slurm.py +++ b/src/tcutility/slurm.py @@ -78,10 +78,17 @@ def sbatch(runfile: str, **options: dict) -> results.Result: cmd = 'sbatch ' for key, val in options.items(): key = key.replace('_', '-') - if len(key) > 1: - cmd += f'--{key}={val} ' + + if val == True: + if len(key) > 1: + cmd += f'--{key} ' + else: + cmd += f'-{key} ' else: - cmd += f'-{key} {val} ' + if len(key) > 1: + cmd += f'--{key}={val} ' + else: + cmd += f'-{key} {val} ' cmd = cmd + runfile From 9a7f16678aed338b8d31d7d016bd10a4e23a1581 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 21 Mar 2024 12:11:49 +0100 Subject: [PATCH 28/41] Added an option to set flags for sbatch --- src/tcutility/job/generic.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tcutility/job/generic.py b/src/tcutility/job/generic.py index 381f6097..340f8133 100644 --- a/src/tcutility/job/generic.py +++ b/src/tcutility/job/generic.py @@ -63,7 +63,11 @@ def sbatch(self, **kwargs): Change slurm settings, for example, to change the partition or change the number of cores to use. The arguments are the same as you would use for sbatch (`see sbatch manual `_). E.g. to change the partition to 'tc' call: - ``job.sbatch(p='tc')`` or ``job.sbatch(partition='tc')`` + ``job.sbatch(p='tc')`` or ``job.sbatch(partition='tc')``. + + Flags can be set as arguments with a boolean to enable or disable them: + + ``job.sbatch(exclusive=True)`` will set the ``--exclusive`` flag. .. warning:: From 71aa7ae16ce76aec99bf399cd23cb43ee65e5d3f Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Tue, 2 Apr 2024 15:04:37 +0200 Subject: [PATCH 29/41] Trying to simplify setting of memory and cores when using ghost atoms --- src/tcutility/job/orca.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index bdc7c416..71a677d7 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -119,10 +119,8 @@ def get_input(self): # set the correct memory usage and processes mem, ntasks = self.get_memory_usage() if ntasks and mem: - natoms = len(self._molecule) + natoms = len(self._molecule) - len(self._ghosts) ntasks = min(ntasks, (natoms - 1) * 3) - if len(self._ghosts) > 0: - ntasks -= 6 self.settings.PAL.nprocs = ntasks self.settings.maxcore = int(mem / ntasks * 0.75) else: From 75c4793480fbd1bb13dae6e89ac5e858e8366fe6 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 15 Apr 2024 19:06:05 +0200 Subject: [PATCH 30/41] ADded the output_mol_path to OrcaJob, similar to AMSJob --- src/tcutility/job/orca.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 71a677d7..f7b5686f 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -198,6 +198,13 @@ def _setup_job(self): return True + @property + def output_mol_path(self): + ''' + The default file path for output molecules when running ADF calculations. It will not be created for singlepoint calculations. + ''' + return j(self.workdir, 'OPT.xyz') + From 1f03f624af02648e5fe7bddd11985b1a968b2298 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Mon, 15 Apr 2024 19:14:05 +0200 Subject: [PATCH 31/41] You can now specify the number of atoms when supplying a molecule as a path --- src/tcutility/job/orca.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index f7b5686f..e06bbedc 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -3,6 +3,7 @@ import subprocess as sp import os from typing import List, Union +from scm import plams j = os.path.join @@ -115,11 +116,28 @@ def get_memory_usage(self): return mem, ntasks + def molecule(self, mol: Union[str, plams.Molecule, plams.Atom, list[plams.Atom]], natoms: int = None): + ''' + Add a molecule to this calculation in various formats. + + Args: + mol: the molecule to read, can be a path (str). If the path exists already we read it. If it does not exist yet, it will be read in later. mol can also be a plams.Molecule object or a single or a list of plams.Atom objects. + natoms: If the molecule is supplied as a path you should also give the number of atoms. + ''' + super().molecule(mol) + self.natoms = natoms + def get_input(self): # set the correct memory usage and processes mem, ntasks = self.get_memory_usage() if ntasks and mem: - natoms = len(self._molecule) - len(self._ghosts) + if self._molecule is not None: + natoms = len(self._molecule) - len(self._ghosts) + else: + if not hasattr(self, 'natoms') or self.natoms is None: + raise ValueError(f'You set the molecule as a path and did not supply the number of atoms.') + natoms = self.natoms + ntasks = min(ntasks, (natoms - 1) * 3) self.settings.PAL.nprocs = ntasks self.settings.maxcore = int(mem / ntasks * 0.75) From aa9c2bc06fb0099cd53a0c1d4e8bb80da2aa0f57 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Sat, 20 Apr 2024 13:38:29 +0200 Subject: [PATCH 32/41] Fixed a typo --- src/tcutility/results/orca.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tcutility/results/orca.py b/src/tcutility/results/orca.py index 112a52fe..c10c0b90 100644 --- a/src/tcutility/results/orca.py +++ b/src/tcutility/results/orca.py @@ -281,10 +281,10 @@ def get_calculation_status(info: Result) -> Result: 'R': 'RUNNING' }.get(state, 'UNKNOWN') - res.fatal = False - res.name = state_name - res.code = state - res.reasons = [] + ret.fatal = False + ret.name = state_name + ret.code = state + ret.reasons = [] return ret From cf58c3b4e137203e19ccf920a89136199d70476e Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Sat, 20 Apr 2024 13:39:10 +0200 Subject: [PATCH 33/41] Removed unused code --- src/tcutility/results/orca.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/tcutility/results/orca.py b/src/tcutility/results/orca.py index c10c0b90..1e32823a 100644 --- a/src/tcutility/results/orca.py +++ b/src/tcutility/results/orca.py @@ -549,12 +549,3 @@ def get_properties(info: Result) -> Result: return ret - -if __name__ == "__main__": - # ret = get_info("/Users/yumanhordijk/Library/CloudStorage/OneDrive-VrijeUniversiteitAmsterdam/RadicalAdditionBenchmark/data/abinitio/P_C2H2_NH2/OPT_pVTZ") - # print(ret.molecule) - - ret = get_info('/Users/yumanhordijk/Library/CloudStorage/OneDrive-VrijeUniversiteitAmsterdam/RadicalAdditionBenchmark2/data/SP_CCSDpT_augCCpVQZ') - print(ret.input) - prop = get_properties(ret) - # print(ret) From d45747a7dcd78dabc74bba9b0c65389d0fbf72af Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Sat, 20 Apr 2024 13:40:20 +0200 Subject: [PATCH 34/41] Fixed a ruff issue --- src/tcutility/job/orca.py | 2 +- src/tcutility/slurm.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index e06bbedc..22915f82 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -135,7 +135,7 @@ def get_input(self): natoms = len(self._molecule) - len(self._ghosts) else: if not hasattr(self, 'natoms') or self.natoms is None: - raise ValueError(f'You set the molecule as a path and did not supply the number of atoms.') + raise ValueError('You set the molecule as a path and did not supply the number of atoms.') natoms = self.natoms ntasks = min(ntasks, (natoms - 1) * 3) diff --git a/src/tcutility/slurm.py b/src/tcutility/slurm.py index 06f88d2b..e1eb3d0c 100644 --- a/src/tcutility/slurm.py +++ b/src/tcutility/slurm.py @@ -82,7 +82,7 @@ def sbatch(runfile: str, **options: dict) -> results.Result: for key, val in options.items(): key = key.replace('_', '-') - if val == True: + if val is True: if len(key) > 1: cmd += f'--{key} ' else: From 1454c58153305e63cc860709e17682fd5616bbfc Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Sat, 20 Apr 2024 13:51:39 +0200 Subject: [PATCH 35/41] Now we set the orca_path to $(which orca) by default, if it was not supplied or found --- src/tcutility/job/orca.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 22915f82..c7387bcc 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -184,8 +184,8 @@ def _setup_job(self): if self.orca_path is None and not self.test_mode: self.orca_path = sp.check_output(['which', 'orca']).decode().strip() except sp.CalledProcessError: - log.error(f'Could not find the orca path. Set the {self.__class__.__name__}.orca_path attribute to add it.') - return + log.warn(f'Could not find the orca path. Set the {self.__class__.__name__}.orca_path attribute to add it. Now setting it to "$(which orca)", make sure the orca executable is findable.') + self.orca_path = '$(which orca)' if not self._molecule and not self._molecule_path: log.error(f'You did not supply a molecule for this job. Call the {self.__class__.__name__}.molecule method to add one.') From b954ef84368f22ec61361ad933fb0e961c5a387c Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Sat, 20 Apr 2024 13:54:37 +0200 Subject: [PATCH 36/41] Added some documentation --- src/tcutility/job/orca.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index c7387bcc..6206ee7a 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -196,9 +196,12 @@ def _setup_job(self): inp.write(self.get_input()) with open(self.runfile_path, 'w+') as runf: - runf.write('#!/bin/sh\n\n') # the shebang is not written by default by ADF + runf.write('#!/bin/sh\n\n') runf.write('\n'.join(self._preambles) + '\n\n') + # when using temporary directories for SLURM we need to do some extra setup + # this is mainly moving the calculation directory to the TMPDIR location + # and after the jobs is finished we copy back the results and remove the TMPDIR if self.use_tmpdir and slurm.has_slurm(): runf.write('export TMPDIR="$TMPDIR/$SLURM_JOB_ID"\n') runf.write('mkdir -p $TMPDIR\n') @@ -209,6 +212,7 @@ def _setup_job(self): runf.write(f'cp $TMPDIR/* {self.workdir}\n') runf.write('rm -rf $TMPDIR\n') + else: runf.write(f'{self.orca_path} {self.inputfile_path}.in\n') From dfdf2c2f28d8e7432a45090c2ddb9d4c09854ed4 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Sat, 20 Apr 2024 15:30:15 +0200 Subject: [PATCH 37/41] Fixed an issue where molfiles were added using the xyz key instead of xyzfile --- src/tcutility/job/orca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 6206ee7a..b8d308ab 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -166,7 +166,7 @@ def get_input(self): ret += '\n' if self._molecule_path: - ret += f'* xyz {self._charge} {self._multiplicity} {os.path.abspath(self._molecule_path)}\n' + ret += f'* xyzfile {self._charge} {self._multiplicity} {os.path.abspath(self._molecule_path)}\n' else: ret += f'* xyz {self._charge} {self._multiplicity}\n' From 5bb8ef344a648b89b5e78fb946478ac9e3aa13ab Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Sat, 20 Apr 2024 20:54:25 +0200 Subject: [PATCH 38/41] Added the UNO reference wavefunction --- src/tcutility/job/orca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index b8d308ab..23123060 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -56,7 +56,7 @@ def method(self, method): self._method = method def reference(self, ref): - spell_check.check(ref, ['UHF', 'UKS', 'RHF', 'RKS', 'ROHF', 'ROKS']) + spell_check.check(ref, ['UNO', 'UHF', 'UKS', 'RHF', 'RKS', 'ROHF', 'ROKS']) self.settings.main.append(ref) self._method = ref From 8a5cc6787bdbbefc145ccdd671a99702baaa118b Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Sat, 20 Apr 2024 21:00:13 +0200 Subject: [PATCH 39/41] Fixed an issue where TSOpt was used instead of OptTS --- src/tcutility/job/orca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 23123060..192ba6bf 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -48,7 +48,7 @@ def remove_main(self, val: Union[str, List[str]]): self.settings.main.discard(lower_main[v.casefold()]) def __remove_task(self): - [self.remove_main(task) for task in ['sp', 'opt', 'tsopt', 'neb-ts']] + [self.remove_main(task) for task in ['sp', 'opt', 'optts', 'neb-ts']] def method(self, method): spell_check.check(method, ['MP2', 'CCSD', 'CCSD(T)', 'CCSDT']) From 505753fb78de44e499afdafc8683ecfe78c5944b Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Sat, 20 Apr 2024 21:00:26 +0200 Subject: [PATCH 40/41] Fixed an issue where append was called instead of add for set --- src/tcutility/job/orca.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index 192ba6bf..ad06fb6a 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -52,19 +52,19 @@ def __remove_task(self): def method(self, method): spell_check.check(method, ['MP2', 'CCSD', 'CCSD(T)', 'CCSDT']) - self.settings.main.append(method) + self.settings.main.add(method) self._method = method def reference(self, ref): spell_check.check(ref, ['UNO', 'UHF', 'UKS', 'RHF', 'RKS', 'ROHF', 'ROKS']) - self.settings.main.append(ref) + self.settings.main.add(ref) self._method = ref def QRO(self, enable=True): self.settings.MDCI.UseQROs = enable def basis_set(self, value): - self.settings.main.append(value) + self.settings.main.add(value) def single_point(self): self.__remove_task() From ba208f0cdbb9cdbd1e05445a03b6831686ab6890 Mon Sep 17 00:00:00 2001 From: Yuman Hordijk Date: Thu, 25 Apr 2024 14:42:48 +0200 Subject: [PATCH 41/41] Added HF as a method --- src/tcutility/job/orca.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tcutility/job/orca.py b/src/tcutility/job/orca.py index ad06fb6a..a7d4d857 100644 --- a/src/tcutility/job/orca.py +++ b/src/tcutility/job/orca.py @@ -51,7 +51,7 @@ def __remove_task(self): [self.remove_main(task) for task in ['sp', 'opt', 'optts', 'neb-ts']] def method(self, method): - spell_check.check(method, ['MP2', 'CCSD', 'CCSD(T)', 'CCSDT']) + spell_check.check(method, ['HF', 'MP2', 'CCSD', 'CCSD(T)', 'CCSDT']) self.settings.main.add(method) self._method = method