Skip to content

Commit

Permalink
Merge pull request #281 from SCM-NV/cp2k_job
Browse files Browse the repository at this point in the history
TST: Print the CP2K .out and .err files whenever a test job crashes
  • Loading branch information
BvB93 committed Jan 21, 2022
2 parents e02a689 + 08050d6 commit f61366e
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 29 deletions.
4 changes: 2 additions & 2 deletions src/qmflows/packages/SCM.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def __init__(self, settings: Optional[Settings],
dill_path: "None | str | os.PathLike[str]" = None,
plams_dir: "None | str | os.PathLike[str]" = None,
work_dir: "None | str | os.PathLike[str]" = None,
status: str = 'done',
status: str = 'successful',
warnings: Optional[WarnMap] = None) -> None:
# Load available property parser from yaml file.
super().__init__(settings, molecule, job_name, dill_path,
Expand Down Expand Up @@ -157,7 +157,7 @@ def __init__(self, settings: Optional[Settings],
dill_path: "None | str | os.PathLike[str]" = None,
plams_dir: "None | str | os.PathLike[str]" = None,
work_dir: "None | str | os.PathLike[str]" = None,
status: str = 'done',
status: str = 'successful',
warnings: Optional[WarnMap] = None) -> None:
# Read available propiety parsers from a yaml file
super().__init__(settings, molecule, job_name, dill_path,
Expand Down
2 changes: 1 addition & 1 deletion src/qmflows/packages/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(self, settings: Optional[Settings],
dill_path: "None | str | os.PathLike[str]" = None,
plams_dir: "None | str | os.PathLike[str]" = None,
work_dir: "None | str | os.PathLike[str]" = None,
status: str = 'done',
status: str = 'successful',
warnings: Optional[WarnMap] = None) -> None:
"""Initialize a :class:`Result` instance.
Expand Down
61 changes: 61 additions & 0 deletions src/qmflows/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
.. autosummary::
fill_cp2k_defaults
get_mm_settings
validate_status
PATH
PATH_MOLECULES
HAS_RDKIT
Expand All @@ -18,6 +19,7 @@
---
.. autofunction:: fill_cp2k_defaults
.. autofunction:: get_mm_settings
.. autofunction:: validate_status
.. autodata:: PATH
:annotation: : pathlib.Path
.. autodata:: PATH_MOLECULES
Expand All @@ -32,19 +34,22 @@
"""

import os
import textwrap
from pathlib import Path

import pytest
from distutils.spawn import find_executable

from .settings import Settings
from .warnings_qmflows import Assertion_Warning
from .packages import Result

__all__ = [
'get_mm_settings',
'PATH',
'PATH_MOLECULES',
'Assertion_Warning',
'validate_status',
'HAS_RDKIT',
'requires_cp2k',
'requires_orca'
Expand Down Expand Up @@ -126,6 +131,62 @@ def get_mm_settings() -> Settings:
return s


def _read_result_file(result: Result, extension: str, max_line: int = 100) -> "None | str":
"""Find and read the first file in ``result`` with the provided file extension.
Returns ``None`` if no such file can be found.
"""
root = result.archive["plams_dir"]
if root is None:
return None

iterator = (os.path.join(root, i) for i in os.listdir(root)
if os.path.splitext(i)[1] == extension)
for i in iterator:
with open(i, "r") as f:
ret_list = f.readlines()
ret = "..." if len(ret_list) > max_line else ""
ret += "".join(ret_list[-max_line:])
return textwrap.indent(ret, 4 * " ")
else:
return None


def validate_status(result: Result, *, print_out: bool = True, print_err: bool = True) -> None:
"""Validate the status of the ``qmflows.Result`` object is set to ``"successful"``.
Parameters
----------
result : qmflows.Result
The to-be validated ``Result`` object.
print_out : bool
Whether to included the content of the ``Result`` objects' .out file in the exception.
print_err : bool
Whether to included the content of the ``Result`` objects' .err file in the exception.
Raises
------
AssertionError
Raised when :code:`result.status != "successful"`.
"""
if result.status == "successful":
return None

indent = 4 * " "
msg = f"Unexpected {result.job_name} status: {result.status!r}"

if print_out:
out = _read_result_file(result, ".out")
if out is not None:
msg += f"\n\nout_file:\n{out}"
if print_err:
err = _read_result_file(result, ".err")
if err is not None:
msg += f"\n\nerr_file:\n{err}"
raise AssertionError(msg)


def _has_exec(executable: str) -> bool:
"""Check if the passed executable is installed."""
path = find_executable(executable)
Expand Down
16 changes: 9 additions & 7 deletions test/test_cp2k_mm_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import os
import shutil
from typing import Callable

import numpy as np
from assertionlib import assertion
Expand All @@ -11,7 +12,7 @@
from qmflows import Settings, cp2k_mm, singlepoint, geometry, freq, md, cell_opt
from qmflows.utils import InitRestart
from qmflows.packages.cp2k_mm import CP2KMM_Result
from qmflows.test_utils import get_mm_settings, PATH, PATH_MOLECULES
from qmflows.test_utils import get_mm_settings, validate_status, PATH, PATH_MOLECULES

MOL = Molecule(PATH_MOLECULES / 'Cd68Cl26Se55__26_acetate.xyz')
WORKDIR = PATH / 'output_cp2k_mm'
Expand Down Expand Up @@ -46,7 +47,7 @@ def overlap_coords(xyz1: np.ndarray, xyz2: np.ndarray) -> np.ndarray:

def mock_runner(mocker_instance: MockFixture,
settings: Settings = SETTINGS,
jobname: str = 'job') -> CP2KMM_Result:
jobname: str = 'job') -> Callable[..., CP2KMM_Result]:
"""Create a Result instance using a mocked runner."""
run_mocked = mocker_instance.patch("qmflows.run")

Expand All @@ -72,7 +73,7 @@ def test_cp2k_singlepoint_mock(mocker: MockFixture) -> None:
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_sp")

result = run_mocked(job)
assertion.eq(result.status, 'successful')
validate_status(result)

# Compare energies
ref = -15.4431781758
Expand All @@ -88,7 +89,7 @@ def test_c2pk_opt_mock(mocker: MockFixture) -> None:
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_opt")

result = run_mocked(job)
assertion.eq(result.status, 'successful')
validate_status(result)

# Compare energies
ref = -16.865587192150834
Expand All @@ -114,7 +115,7 @@ def test_c2pk_freq_mock(mocker: MockFixture) -> None:
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_freq")

result = run_mocked(job)
assertion.eq(result.status, 'successful')
validate_status(result)

freqs = result.frequencies
freqs_ref = np.load(PATH / 'Cd68Cl26Se55__26_acetate.freq.npy')
Expand All @@ -136,7 +137,7 @@ def test_c2pk_md_mock(mocker: MockFixture) -> None:
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_md")

result = run_mocked(job)
assertion.eq(result.status, 'successful')
validate_status(result)

assertion.isfile(result.results['cp2k-1_1000.restart'])

Expand Down Expand Up @@ -172,7 +173,7 @@ def test_c2pk_cell_opt_mock(mocker: MockFixture) -> None:
job = cp2k_mm(s, mol)
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_cell_opt")
result = run_mocked(job)
assertion.eq(result.status, 'successful')
validate_status(result)

ref_volume = np.load(PATH / 'volume.npy')
ref_coordinates = np.load(PATH / 'coordinates.npy')
Expand All @@ -191,6 +192,7 @@ def test_c2pk_npt_mock(mocker: MockFixture) -> None:
job = cp2k_mm(s, None)
run_mocked = mock_runner(mocker, settings=s, jobname="cp2k_mm_npt")
result = run_mocked(job)
validate_status(result)

ref_pressure = np.load(PATH / 'pressure.npy')
np.testing.assert_allclose(result.pressure, ref_pressure)
29 changes: 22 additions & 7 deletions test/test_cp2k_mock.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Mock CP2K funcionality."""
import copy
from typing import Callable

import pytest
import numpy as np
Expand All @@ -9,25 +10,34 @@

from qmflows import cp2k, templates
from qmflows.packages.cp2k_package import CP2K_Result
from qmflows.test_utils import PATH, PATH_MOLECULES, fill_cp2k_defaults, requires_cp2k
from qmflows.utils import init_restart
from qmflows.common import CP2KVersion
from qmflows.parsers.cp2KParser import get_cp2k_version_run
from qmflows.test_utils import (
PATH,
PATH_MOLECULES,
fill_cp2k_defaults,
requires_cp2k,
validate_status,
)

try:
CP2K_VERSION = get_cp2k_version_run("cp2k.popt")
except Exception:
CP2K_VERSION = CP2KVersion(0, 0)


def mock_runner(mocker_instance, jobname: str) -> CP2K_Result:
def mock_runner(mocker_instance, jobname: str) -> Callable[..., CP2K_Result]:
"""Create a Result instance using a mocked runner."""
run_mocked = mocker_instance.patch("qmflows.run")
dill_path = WORKDIR / jobname / f"{jobname}.dill"
plams_dir = WORKDIR / jobname
run_mocked.return_value = CP2K_Result(templates.geometry, ETHYLENE, jobname,
dill_path=dill_path, plams_dir=plams_dir)

run_mocked.return_value = CP2K_Result(
templates.geometry,
ETHYLENE,
jobname,
status="successful",
dill_path=WORKDIR / jobname / f"{jobname}.dill",
plams_dir=WORKDIR / jobname,
)
return run_mocked

# module constants
Expand Down Expand Up @@ -71,6 +81,7 @@ def test_cp2k_singlepoint_mock(mocker: MockFixture):
jobname = "cp2k_job"
run_mocked = mock_runner(mocker, jobname)
rs = run_mocked(job)
validate_status(rs)

# electronic energy
assertion.isfinite(rs.energy)
Expand All @@ -91,6 +102,8 @@ def test_c2pk_opt_mock(mocker: MockFixture):

job = cp2k(s, ETHYLENE, job_name=jobname)
rs = run_mocked(job)
validate_status(rs)

# check the optimized geometry
mol = rs.geometry
assertion.len_eq(mol, 6)
Expand All @@ -110,6 +123,7 @@ def test_c2pk_freq_mock(mocker: MockFixture):
run_mocked = mock_runner(mocker, jobname)
job = cp2k(s, ETHYLENE, job_name=jobname)
rs = run_mocked(job)
validate_status(rs)

# check properties
assertion.isfinite(rs.enthalpy)
Expand All @@ -123,5 +137,6 @@ def test_dir(mocker: MockFixture) -> None:

job = cp2k(s, ETHYLENE, job_name=jobname)
r = run_mocked(job)
validate_status(r)

assertion.issubset(CP2K_Result.prop_mapping.keys(), dir(r))
14 changes: 10 additions & 4 deletions test/test_using_plams/test_cp2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
from scm.plams import Molecule

from qmflows import Settings, cp2k, run, templates
from qmflows.test_utils import PATH, PATH_MOLECULES, fill_cp2k_defaults, requires_cp2k
from qmflows.test_utils import (
PATH,
PATH_MOLECULES,
fill_cp2k_defaults,
requires_cp2k,
validate_status,
)


@requires_cp2k
Expand All @@ -28,7 +34,7 @@ def test_cp2k_opt(tmp_path: Path) -> None:
job = cp2k(s, water)
result = run(job, path=tmp_path, folder="test_cp2k_opt")

assertion.eq(result.status, "successful")
validate_status(result)
assertion.isinstance(result.molecule, Molecule)


Expand Down Expand Up @@ -57,7 +63,7 @@ def test_cp2k_singlepoint(tmp_path: Path, mo_index_range: str) -> None:
key = f"test_using_plams/test_cp2k/test_cp2k_singlepoint/{mo_index_range}"
ref = f[key][...].view(np.recarray)

assertion.eq(result.status, "successful")
validate_status(result)
orbitals = result.orbitals
assertion.is_not(orbitals, None)
np.testing.assert_allclose(orbitals.eigenvalues, ref.eigenvalues)
Expand All @@ -74,7 +80,7 @@ def test_cp2k_freq(tmp_path: Path) -> None:
job = cp2k(s, mol)
result = run(job, path=tmp_path, folder="test_cp2k_freq")

assertion.eq(result.status, "successful")
validate_status(result)
assertion.isclose(result.free_energy, -10801.971213467135)
assertion.isclose(result.enthalpy, -10790.423489727531)
np.testing.assert_allclose(result.frequencies, [1622.952012, 3366.668885, 3513.89377])
12 changes: 6 additions & 6 deletions test/test_using_plams/test_cp2k_mm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from scm.plams import Molecule

from qmflows import Settings, run, cp2k_mm, singlepoint, geometry, freq, md, cell_opt
from qmflows.test_utils import get_mm_settings, PATH, PATH_MOLECULES, requires_cp2k
from qmflows.test_utils import get_mm_settings, validate_status, PATH, PATH_MOLECULES, requires_cp2k

MOL = Molecule(PATH_MOLECULES / 'Cd68Cl26Se55__26_acetate.xyz')

Expand Down Expand Up @@ -41,7 +41,7 @@ def test_singlepoint(tmp_path: Path) -> None:

job = cp2k_mm(settings=s, mol=MOL, job_name='cp2k_mm_sp')
result = run(job, path=tmp_path, folder="test_singlepoint")
assertion.eq(result.status, 'successful')
validate_status(result)

# Compare energies
ref = -15.4431781758
Expand All @@ -57,7 +57,7 @@ def test_geometry(tmp_path: Path) -> None:

job = cp2k_mm(settings=s, mol=MOL, job_name='cp2k_mm_opt')
result = run(job, path=tmp_path, folder="test_geometry")
assertion.eq(result.status, 'successful')
validate_status(result)

# Compare energies
ref = -16.865587192150834
Expand All @@ -84,7 +84,7 @@ def test_freq(tmp_path: Path) -> None:

job = cp2k_mm(settings=s, mol=mol, job_name='cp2k_mm_freq')
result = run(job, path=tmp_path, folder="test_freq")
assertion.eq(result.status, 'successful')
validate_status(result)

freqs = result.frequencies
freqs_ref = np.load(PATH / 'Cd68Cl26Se55__26_acetate.freq.npy')
Expand All @@ -109,7 +109,7 @@ def test_md(tmp_path: Path) -> None:

job = cp2k_mm(settings=s, mol=mol, job_name='cp2k_mm_md')
result = run(job, path=tmp_path, folder="test_md")
assertion.eq(result.status, 'successful')
validate_status(result)

plams_results = result.results
assertion.isfile(plams_results['cp2k-1_1000.restart'])
Expand Down Expand Up @@ -147,4 +147,4 @@ def test_c2pk_cell_opt(tmp_path: Path) -> None:

job = cp2k_mm(settings=s, mol=mol, job_name='cp2k_mm_cell_opt')
result = run(job, path=tmp_path, folder="test_c2pk_cell_opt")
assertion.eq(result.status, 'successful')
validate_status(result)

0 comments on commit f61366e

Please sign in to comment.