Skip to content

Commit

Permalink
support two qca branches (#2821)
Browse files Browse the repository at this point in the history
* support two qca branches

* more mamba

* mamba

* mamba 2

* mamba 3

* mamba4

* mamba 4

* mamba 5

* mamba6

* tidy and next check
  • Loading branch information
loriab committed Dec 5, 2022
1 parent 38cca14 commit 32ccbbe
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 23 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/ecosystem.yml
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,22 @@ jobs:
- openfermionpsi4
- pymdi
- qcengine=0.25.0
- qcfractal
- qcportal
# toml needed for psi4 to load parameters from dftd4-python
EOF
if [[ "${{ runner.os }}" == "Linux" ]]; then
# "next" packages aren't self-contained, and "0.15.8" not available fro py310. try again someday
sed -i "s;- qcfractal;;g" aux.yaml
sed -i "s;- qcportal;;g" aux.yaml
#sed -i "s;- qcfractal;- qcarchive/label/next::qcfractal;g" aux.yaml
#sed -i "s;- qcportal;- qcarchive/label/next::qcportal;g" aux.yaml
fi
if [[ "${{ runner.os }}" == "macOS" ]]; then
sed -E -i.bak "s;- qcfractal;- qcfractal=0.15.8.1;g" aux.yaml
sed -E -i.bak "s;- qcportal;- qcportal=0.15.8;g" aux.yaml
fi
fi
#
# Runtime for single-channel (W)
Expand Down Expand Up @@ -310,6 +323,12 @@ jobs:
conda info
conda list
- name: Switch to Mamba solver (L, M)
if: matrix.cfg.base-channel != 'conda-forge'
run: |
conda install -n base conda-libmamba-solver
conda config --set solver libmamba
- name: Configure Psi4, Conda defaults GNU (L)
if: runner.os == 'Linux'
run: |
Expand Down Expand Up @@ -455,6 +474,13 @@ jobs:
qcengine info
fi
- name: Patch QCFractal for Python
if: runner.os == 'macOS'
run: |
cat /usr/local/miniconda/envs/auxenv/lib/python${{ matrix.cfg.python-version }}/site-packages/qcfractal/queue/executor_adapter.py
sed -E -i.bak "s;for result in self.queue.values\(\);for result in list(self.queue.values());g" /usr/local/miniconda/envs/auxenv/lib/python${{ matrix.cfg.python-version }}/site-packages/qcfractal/queue/executor_adapter.py
cat /usr/local/miniconda/envs/auxenv/lib/python${{ matrix.cfg.python-version }}/site-packages/qcfractal/queue/executor_adapter.py
- name: Test OpenMP
working-directory: ./install/lib
run: python -c "from psi4 import core; core.set_num_threads(42); assert core.get_num_threads() == 42"
Expand Down
1 change: 1 addition & 0 deletions doc/sphinxman/source/nitpick-exceptions
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ py:class pydantic.utils.Representation
# FractalClient is temp until https://github.com/MolSSI/QCFractal/pull/716
py:class qcportal.client.FractalClient
#py:class qcportal.FractalClient
py:class qcportal.PortalClient

## numpy inherited docstrings
#py:obj dtype
Expand Down
4 changes: 2 additions & 2 deletions psi4/driver/driver_cbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1635,10 +1635,10 @@ def _prepare_results(self, client: Optional["qcportal.FractalClient"] = None):
mc['f_gradient'] = task.extras['qcvars']['CURRENT GRADIENT']

if 'CURRENT DIPOLE' in task.extras['qcvars']:
mc['f_dipole'] = task.extras['qcvars']['CURRENT DIPOLE']
mc['f_dipole'] = np.array(task.extras['qcvars']['CURRENT DIPOLE'])

if 'CURRENT DIPOLE GRADIENT' in task.extras['qcvars']:
mc['f_dipder'] = task.extras['qcvars']['CURRENT DIPOLE GRADIENT']
mc['f_dipder'] = np.array(task.extras['qcvars']['CURRENT DIPOLE GRADIENT'])

# Fill in energies for subsumed methods
if self.metameta['ptype'] == 'energy':
Expand Down
123 changes: 102 additions & 21 deletions psi4/driver/task_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,31 +128,64 @@ def compute(self, client: Optional["qcportal.client.FractalClient"] = None):

if client:
self.computed = True
from qcportal.models import KeywordSet, Molecule

# Build the keywords
keyword_id = client.add_keywords([KeywordSet(values=self.keywords)])[0]
try:
# QCFractal v0.15.8
from qcportal.models import KeywordSet, Molecule
qca_next_branch = False
except ImportError:
# QCFractal `next`
from qcelemental.models import Molecule
qca_next_branch = True

# Build the molecule
mol = Molecule(**self.molecule.to_schema(dtype=2))

r = client.add_compute(
"psi4", self.method, self.basis, self.driver, keyword_id, [mol]
)
self.result_id = r.ids[0]
# NOTE: The following will re-run errored jobs by default
if self.result_id in r.existing:
ret = client.query_tasks(base_result=self.result_id)
if ret:
if ret[0].status == "ERROR":
client.modify_tasks("restart", base_result=self.result_id)
logger.info("Resubmitting Errored Job {}".format(self.result_id))
elif ret[0].status == "COMPLETE":
if not qca_next_branch:
# QCFractal v0.15.8

# Build the keywords
keyword_id = client.add_keywords([KeywordSet(values=self.keywords)])[0]

r = client.add_compute("psi4", self.method, self.basis, self.driver, keyword_id, [mol])
self.result_id = r.ids[0]
# NOTE: The following will re-run errored jobs by default
if self.result_id in r.existing:
ret = client.query_tasks(base_result=self.result_id)
if ret:
if ret[0].status == "ERROR":
client.modify_tasks("restart", base_result=self.result_id)
logger.info("Resubmitting Errored Job {}".format(self.result_id))
elif ret[0].status == "COMPLETE":
logger.debug("Job already completed {}".format(self.result_id))
else:
logger.debug("Job already completed {}".format(self.result_id))
else:
logger.debug("Job already completed {}".format(self.result_id))
logger.debug("Submitting AtomicResult {}".format(self.result_id))

else:
logger.debug("Submitting AtomicResult {}".format(self.result_id))
# QCFractal `next`

meta, ids = client.add_singlepoints(
molecules=mol,
program="psi4",
driver=self.driver,
method=self.method,
basis=self.basis,
keywords=self.keywords,
# protocols,
)
self.result_id = ids[0]
# NOTE: The following will re-run errored jobs by default
if meta.existing_idx:
rec = client.get_singlepoints(self.result_id)
if rec.status == "error":
client.reset_records(self.result_id)
logger.info("Resubmitting Errored Job {}".format(self.result_id))
elif rec.status == "complete":
logger.debug("Job already completed {}".format(self.result_id))
else:
logger.debug("Submitting AtomicResult {}".format(self.result_id))

return

Expand Down Expand Up @@ -192,15 +225,63 @@ def get_results(self, client: Optional["qcportal.FractalClient"] = None) -> Atom
return self.result

if client:
result = client.query_results(id=self.result_id)
try:
# QCFractal/QCPortal v0.15.8
result = client.query_results(id=self.result_id)
qca_next_branch = False
except AttributeError:
# QCFractal/QCPortal `next`
record = client.get_singlepoints(record_ids=self.result_id)
qca_next_branch = True

logger.debug(f"Querying AtomicResult {self.result_id}")
if len(result) == 0:
return self.result

self.result = result[0]
if not qca_next_branch:
# QCFractal v0.15.8
if len(result) == 0:
return self.result

self.result = result[0]

else:
# QCFractal `next`
if record.status != "complete":
return self.result

self.result = _singlepointrecord_to_atomicresult(record)

return self.result


def _singlepointrecord_to_atomicresult(spr) -> AtomicResult:
extras = spr.raw_data.extras
extras.pop("_qcfractal_compressed_outputs", None)

atres = {
"driver": spr.raw_data.specification.driver,
"model": {
"method": spr.raw_data.specification.method,
"basis": spr.raw_data.specification.basis,
},
"molecule": spr.molecule,
"keywords": spr.raw_data.specification.keywords,
"properties": spr.properties,
"protocols": spr.raw_data.specification.protocols,
"return_result": spr.return_result,
"extras": extras,
"stdout": spr.stdout,
"native_files": spr.native_files,
"wavefunction": spr.wavefunction,
"provenance": spr.raw_data.compute_history[0].provenance,
"success": spr.status == "complete",
}

from qcelemental.models import AtomicResult
atres = AtomicResult(**atres)

return atres


def _drink_filter(stdout: str) -> str:
"""Don't mess up the widespread ``grep beer`` test of Psi4 doneness by printing multiple drinks per outfile."""

Expand Down
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,7 @@ markers =
snsmp2
v2rdm_casscf
qcdb
qcfractal: "tests suing QCFractal software; skip if unavailable"
qcportal: "tests suing QCPortal software; skip if unavailable"
psi4

2 changes: 2 additions & 0 deletions tests/pytests/addons.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ def is_nvidia_gpu_present():
"snsmp2": which_import("snsmp2", return_bool=True),
"v2rdm_casscf": which_import("v2rdm_casscf", return_bool=True),
"qcdb": False, # capabilities of in-psi and out-of-psi qcdb not aligned
"qcfractal": which_import("qcfractal", return_bool=True),
"qcportal": which_import("qcportal", return_bool=True),
}


Expand Down
63 changes: 63 additions & 0 deletions tests/pytests/test_qcfractal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import numpy as np

import pytest
from utils import *
from addons import uusing

import psi4

pytestmark = [pytest.mark.psi, pytest.mark.api]


@uusing("qcfractal")
@uusing("qcportal")
@pytest.mark.parametrize("parallel", [
pytest.param(False, id="internal"),
pytest.param(True, id="snowflake"),
])
def test_qcf_cbs_mbe(parallel):
if parallel:
try:
from qcfractal import FractalSnowflake
qca_next_branch = False
except ImportError:
from qcfractal.snowflake import FractalSnowflake
qca_next_branch = True

if not qca_next_branch:
snowflake = FractalSnowflake(logging=True, max_workers=4)
else:
snowflake = FractalSnowflake()
client = snowflake.client()

import psi4
dimer = psi4.geometry("""
He 2 0 0
--
He -2 0 0
""")

ref_grad = [[-3.987697668786454e-07, 0.0, 0.0], [3.987697668787284e-07, 0.0, 0.0]]

if parallel:
plan = psi4.gradient("HF/cc-pV[D,T]Z", bsse_type="CP", return_plan=True, return_total_data=True)
plan.compute(client)

snowflake.await_results()
ret = plan.get_results(client)
assert compare_values(ref_grad, ret.return_result)

else:
grad = psi4.gradient("HF/cc-pV[D,T]Z", bsse_type="CP", return_total_data=True)
assert compare_values(ref_grad, grad)

if parallel:
print(f'Final energy = {ret.extras["qcvars"]["CURRENT ENERGY"]} [E_h]')
print(f'Final gradient = {ret.extras["qcvars"]["CURRENT GRADIENT"]} [E_h/a0]')
print(f'Final gradient = {ret.return_result} [E_h/a0]')
print(f'Final energy = {ret.properties.return_energy} [E_h]')

assert compare_values(-5.72527135604184, ret.extras["qcvars"]["CURRENT ENERGY"], atol=1e-5)
assert compare_values(ref_grad, ret.extras["qcvars"]["CURRENT GRADIENT"], atol=1e-4)

snowflake.stop()

0 comments on commit 32ccbbe

Please sign in to comment.