Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improving get_version for Molpro and Q-Chem #261

Merged
merged 2 commits into from Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 15 additions & 5 deletions qcengine/mdi_server.py
Expand Up @@ -11,11 +11,21 @@
from .compute import compute

try:
from mdi import MDI_Init, MDI_Accept_Communicator, MDI_Recv_Command
from mdi import MDI_Recv, MDI_Send, MDI_Get_Intra_Code_MPI_Comm
from mdi import MDI_Register_Node, MDI_Register_Command
from mdi import MDI_DOUBLE, MDI_CHAR, MDI_INT
from mdi import MDI_COMMAND_LENGTH, MDI_MAJOR_VERSION
from mdi import (
MDI_CHAR,
MDI_COMMAND_LENGTH,
MDI_DOUBLE,
MDI_INT,
MDI_MAJOR_VERSION,
MDI_Accept_Communicator,
MDI_Get_Intra_Code_MPI_Comm,
MDI_Init,
MDI_Recv,
MDI_Recv_Command,
MDI_Register_Command,
MDI_Register_Node,
MDI_Send,
)

use_mdi = True
except ImportError:
Expand Down
3 changes: 2 additions & 1 deletion qcengine/programs/dftd3.py
Expand Up @@ -20,9 +20,10 @@
from .model import ProgramHarness

if TYPE_CHECKING:
from ..config import TaskConfig
from qcelemental.models import AtomicInput

from ..config import TaskConfig


pp = pprint.PrettyPrinter(width=120, compact=True, indent=1)

Expand Down
3 changes: 2 additions & 1 deletion qcengine/programs/entos.py
Expand Up @@ -21,9 +21,10 @@
)

if TYPE_CHECKING:
from ..config import TaskConfig
from qcelemental.models import AtomicInput

from ..config import TaskConfig


def entos_ao_order_spherical(max_angular_momentum: int) -> Dict[int, List[int]]:
ao_order = {}
Expand Down
8 changes: 7 additions & 1 deletion qcengine/programs/molpro.py
Expand Up @@ -3,8 +3,8 @@
"""

import string
import xml.etree.ElementTree as ET
from typing import Any, Dict, List, Optional, Set, Tuple
from xml.etree import ElementTree as ET

from qcelemental.models import AtomicResult
from qcelemental.util import parse_version, safe_version, which
Expand Down Expand Up @@ -88,6 +88,12 @@ def get_version(self) -> str:
tree = ET.ElementTree(ET.fromstring(output["outfiles"]["version.xml"]))
root = tree.getroot()
version_tree = root.find("molpro_uri:job/molpro_uri:platform/molpro_uri:version", name_space)
if version_tree is None:
# some older schema
name_space = {"molpro_uri": "http://www.molpro.net/schema/molpro2006"}
version_tree = root.find("molpro_uri:job//molpro_uri:version", name_space)
if version_tree is None:
return safe_version("0.0.0")
year = version_tree.attrib["major"]
minor = version_tree.attrib["minor"]
molpro_version = year + "." + minor
Expand Down
12 changes: 6 additions & 6 deletions qcengine/programs/openmm.py
Expand Up @@ -8,19 +8,20 @@
import os
from typing import TYPE_CHECKING, Dict

import numpy as np
from qcelemental.models import AtomicResult, Provenance
from qcelemental.util import which_import

from ..exceptions import InputError
from ..util import capture_stdout
from .model import ProgramHarness
from .rdkit import RDKitHarness
import numpy as np

if TYPE_CHECKING:
from ..config import TaskConfig
from qcelemental.models import AtomicInput

from ..config import TaskConfig


class OpenMMHarness(ProgramHarness):

Expand Down Expand Up @@ -123,8 +124,8 @@ def _generate_openmm_system(
Generate an OpenMM System object from the input molecule method and basis.
"""
from openmmforcefields.generators import SystemGenerator
from simtk.openmm import app
from simtk import unit
from simtk.openmm import app

# create a hash based on the input options
hashstring = molecule.to_smiles(isomeric=True, explicit_hydrogens=True, mapped=True) + method
Expand Down Expand Up @@ -191,11 +192,10 @@ def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicRes
"""
self.found(raise_error=True)

from simtk import openmm
from simtk import unit
from simtk import openmm, unit

with capture_stdout():
import openforcefield.topology as offtop
from openforcefield import topology as offtop

# Failure flag
ret_data = {"success": False}
Expand Down
3 changes: 2 additions & 1 deletion qcengine/programs/psi4.py
Expand Up @@ -15,9 +15,10 @@
from .model import ProgramHarness

if TYPE_CHECKING:
from ..config import TaskConfig
from qcelemental.models import AtomicInput

from ..config import TaskConfig


class Psi4Harness(ProgramHarness):

Expand Down
60 changes: 46 additions & 14 deletions qcengine/programs/qchem.py
Expand Up @@ -11,12 +11,14 @@

import numpy as np
from qcelemental import constants
from qcelemental.models import AtomicInput, AtomicResult, Molecule
from qcelemental.models import AtomicInput, AtomicResult, Molecule, Provenance
from qcelemental.molparse import regex
from qcelemental.util import parse_version, provenance_stamp, safe_version, which
from qcelemental.util import parse_version, safe_version, which

from qcengine.config import TaskConfig, get_config

from ..exceptions import InputError, UnknownError
from ..util import disk_files, execute, popen, temporary_directory
from ..util import disk_files, execute, temporary_directory
from .model import ProgramHarness

NUMBER = r"(?x:" + regex.NUMBER + ")"
Expand Down Expand Up @@ -59,29 +61,45 @@ def _get_qc_path(self, config: Optional["TaskConfig"] = None):
return paths

def get_version(self) -> str:
# excuse missing Q-Chem for QCEngineRecords tests
found = self.found()
if not found:
return safe_version("0.0.0")

self.found(raise_error=True)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps remove this one since we have the above now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the found()s? second is normal behavior and has the raise_error behavior. first is the crazy escape path for qcer.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But will the second ever have any effect? If qchem isn't found, the first will return the safe_version("0.0.0") call and exit the method. If qchem is found, it will sail through? I might be misunderstanding the subtleties of qchem here...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, you're right. the return v0.0 was newly needed to appease qcer after I made qchem run for version. but the second with raise_error is the correct harness behavior. proper solution would be for the qcer tests to set an envvar that could be used to narrowly trigger the v0.0 special behavior. I'd like to keep the second found in memory of what we should return to.

I don't fully understand the subtleties of the qchem harness myself. it's one of those programs that settled into an idiosyncratic groove long ago, and it looks like DGAS had to add extra hoops to to stuff it into a harness.


# Get the node configuration
config = get_config()

which_prog = which("qchem")
if which_prog not in self.version_cache:
with popen([which_prog, "-h"], popen_kwargs={"env": self._get_qc_path()}) as exc:
exc["proc"].wait(timeout=15)
success, exc = execute(
[which_prog, "v.in"],
{"v.in": "$rem\n"},
scratch_directory=config.scratch_directory,
environment=self._get_qc_path(),
timeout=15,
)

mobj = re.search(r"Q-Chem\s+([\d.]+)\s+for", exc["stdout"])
if mobj:
self.version_cache[which_prog] = safe_version(mobj.group(1))

if "QC not defined" in exc["stdout"]:
# if "QC not defined" in exc["stdout"]:
else:
return safe_version("0.0.0")

self.version_cache[which_prog] = safe_version(exc["stdout"].splitlines()[0].split()[-1])

return self.version_cache[which_prog]

def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicResult":
def compute(self, input_model: "AtomicInput", config: TaskConfig) -> "AtomicResult":
"""
Run qchem
"""
# Check if qchem executable is found
self.found(raise_error=True)

# Check qchem version
qceng_ver = "5.2"
qceng_ver = "5.1"
if parse_version(self.get_version()) < parse_version(qceng_ver):
raise TypeError(f"Q-Chem version <{qceng_ver} not supported (found version {self.get_version()})")

Expand Down Expand Up @@ -165,7 +183,7 @@ def execute(
return exe_success, proc

def build_input(
self, input_model: "AtomicInput", config: "TaskConfig", template: Optional[str] = None
self, input_model: "AtomicInput", config: TaskConfig, template: Optional[str] = None
) -> Dict[str, Any]:

# Check some bounds on what cannot be parsed
Expand Down Expand Up @@ -257,9 +275,20 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") ->
"return_energy": bdata["99.0"][-1],
}

qcvars = {}
_mp2_methods = {"mp2", "rimp2"}
if input_model.model.method.lower() in _mp2_methods:
properties["mp2_total_energy"] = properties["return_energy"]
emp2 = bdata["99.0"][-1]
properties["mp2_total_energy"] = emp2
qcvars["MP2 TOTAL ENERGY"] = emp2
qcvars["CURRENT ENERGY"] = emp2
ehf = bdata["99.0"][1]
qcvars["HF TOTAL ENERGY"] = ehf
qcvars["SCF TOTAL ENERGY"] = ehf
qcvars["CURRENT REFERENCE ENERGY"] = ehf
qcvars["MP2 CORRELATION ENERGY"] = emp2 - ehf
qcvars["CURRENT CORRELATION ENERGY"] = emp2 - ehf
properties["mp2_correlation_energy"] = emp2 - ehf

# Correct CCSD because its odd?
# if input_model.model.method.lower() == "ccsd":
Expand All @@ -273,15 +302,18 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") ->
output_data["stdout"] = outfiles["dispatch.out"]
output_data["success"] = True

return AtomicResult(**{**input_model.dict(), **output_data})
merged_data = {**input_model.dict(), **output_data}
merged_data["extras"]["qcvars"] = qcvars

return AtomicResult(**merged_data)

def _parse_logfile_common(self, outtext: str, input_dict: Dict[str, Any]):
"""
Parses fields from log file that are not parsed from QCSCRATCH in parse_output
"""

properties = {}
provenance = provenance_stamp(__name__)
provenance = Provenance(creator="QChem", version=self.get_version(), routine="qchem").dict()
mobj = re.search(r"This is a multi-thread run using ([0-9]+) threads", outtext)
if mobj:
provenance["nthreads"] = int(mobj.group(1))
Expand Down
5 changes: 3 additions & 2 deletions qcengine/programs/qcvar_identities_resources.py
@@ -1,10 +1,11 @@
from decimal import Decimal as Dm
from typing import Any, Dict, List
import numpy as np
from .util import PreservingDict

import numpy as np
from qcelemental.models import AtomicResultProperties

from .util import PreservingDict


def _difference(args):
minuend, subtrahend = args
Expand Down
3 changes: 2 additions & 1 deletion qcengine/programs/rdkit.py
Expand Up @@ -12,9 +12,10 @@
from .model import ProgramHarness

if TYPE_CHECKING:
from ..config import TaskConfig
from qcelemental.models import AtomicInput

from ..config import TaskConfig


class RDKitHarness(ProgramHarness):

Expand Down
2 changes: 1 addition & 1 deletion qcengine/programs/terachem.py
Expand Up @@ -9,7 +9,7 @@
from qcelemental.molparse.regex import DECIMAL, NUMBER
from qcelemental.util import parse_version, safe_version, which

import qcengine.util as uti
from qcengine import util as uti

from ..exceptions import UnknownError
from ..util import popen
Expand Down
7 changes: 6 additions & 1 deletion qcengine/programs/tests/standard_suite_contracts.py
Expand Up @@ -145,7 +145,12 @@ def contractual_mp2(
and pv in ["MP2 SAME-SPIN CORRELATION ENERGY", "MP2 OPPOSITE-SPIN CORRELATION ENERGY"]
)
or (
((qc_module == "psi4-detci" and method == "mp2"))
(
(qc_module == "psi4-detci" and method == "mp2")
or (
qc_module == "qchem" and method == "mp2"
) # for structured -- can probably get these from parsing
)
and pv
in [
"MP2 SAME-SPIN CORRELATION ENERGY",
Expand Down
4 changes: 3 additions & 1 deletion qcengine/programs/tests/standard_suite_runner.py
Expand Up @@ -81,7 +81,9 @@ def runner_asserter(inp, subject, method, basis, tnm):
# <<< Comparison Tests >>>

assert wfn["success"] is True
assert wfn["provenance"]["creator"].lower() == qcprog
assert (
wfn["provenance"]["creator"].lower() == qcprog
), f'Creator ({wfn["provenance"]["creator"].lower()}) not expected ({qcprog})'

ref_block = std_suite[chash]

Expand Down
4 changes: 4 additions & 0 deletions qcengine/programs/tests/test_qchem.py
Expand Up @@ -14,6 +14,8 @@
"root.molecule.provenance.routine",
"root.provenance.version",
"root.provenance.routine",
"root.provenance.creator",
"root.extras",
]


Expand All @@ -30,6 +32,8 @@ def test_qchem_output_parser(test_case):

output_ref = qcel.models.AtomicResult.parse_raw(data["output.json"]).dict()
output_ref.pop("provenance", None)
output_ref.pop("extras", None)
output.pop("extras", None)

check, message = compare_recursive(output_ref, output, return_message=True)
assert check, message
Expand Down
3 changes: 2 additions & 1 deletion qcengine/programs/tests/test_standard_suite.py
Expand Up @@ -31,9 +31,10 @@
# result: runner checks assertion fails at expected result and triggers pytest.xfail


import sys

import pytest
import qcelemental as qcel
import sys

import qcengine as qcng
from qcengine.programs.tests.standard_suite_ref import std_molecules, std_refs
Expand Down
4 changes: 1 addition & 3 deletions qcengine/programs/tests/test_xtb.py
Expand Up @@ -5,10 +5,8 @@
to test as much different interactions as possible.
"""

import pytest

import numpy as np

import pytest
import qcelemental as qcel
from qcelemental.testing import compare_recursive

Expand Down
5 changes: 3 additions & 2 deletions qcengine/programs/torchani.py
Expand Up @@ -12,9 +12,10 @@
from .model import ProgramHarness

if TYPE_CHECKING:
from ..config import TaskConfig
from qcelemental.models import AtomicInput

from ..config import TaskConfig


class TorchANIHarness(ProgramHarness):

Expand Down Expand Up @@ -95,9 +96,9 @@ def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicRes
if parse_version(self.get_version()) < parse_version("0.9"):
raise ResourceError("QCEngine's TorchANI wrapper requires version 0.9 or greater.")

import numpy as np
import torch
import torchani
import numpy as np

device = torch.device("cpu")

Expand Down
4 changes: 2 additions & 2 deletions qcengine/programs/xtb.py
Expand Up @@ -12,10 +12,10 @@
from typing import Dict

from qcelemental.models import AtomicInput, AtomicResult
from qcelemental.util import which_import, safe_version
from qcelemental.util import safe_version, which_import

from .model import ProgramHarness
from ..config import TaskConfig
from .model import ProgramHarness


class XTBHarness(ProgramHarness):
Expand Down