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

efp: start pylibefp interface #144

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions devtools/conda-envs/psi-nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ dependencies:
- psi4
- dftd3 3.2.1
- mp2d >=1.1
- pylibefp
- blas=*=mkl # not needed but an example of disuading solver from openblas and old psi

# Core
Expand Down
2 changes: 2 additions & 0 deletions qcengine/programs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ..exceptions import InputError, ResourceError
from .cfour import CFOURHarness
from .dftd3 import DFTD3Harness
from .pylibefp import PylibEFPHarness
from .entos import EntosHarness
from .gamess import GAMESSHarness
from .molpro import MolproHarness
Expand Down Expand Up @@ -101,3 +102,4 @@ def list_available_programs() -> Set[str]:
register_program(NWChemHarness())
register_program(CFOURHarness())
register_program(EntosHarness())
register_program(PylibEFPHarness())
159 changes: 159 additions & 0 deletions qcengine/programs/pylibefp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""
Calls the PylibEFP interface to LibEFP.
"""
import pprint
from typing import Dict

import qcelemental as qcel
from qcelemental.models import Provenance, Result
from qcelemental.util import safe_version, which_import

from ..exceptions import InputError #, RandomError, ResourceError, UnknownError
from .model import ProgramHarness

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


class PylibEFPHarness(ProgramHarness):

_defaults = {
"name": "PylibEFP",
"scratch": False,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is probably thread safe?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

all in memory and all functions acting on same struct instance, so yes.

"thread_safe": False,
"thread_parallel": False, # can be but not the way Psi usually builds it
"node_parallel": False,
"managed_memory": False,
}
version_cache: Dict[str, str] = {}

class Config(ProgramHarness.Config):
pass

@staticmethod
def found(raise_error: bool = False) -> bool:
return which_import('pylibefp',
return_bool=True,
raise_error=raise_error,
raise_msg='Please install via `conda install pylibefp -c psi4`.')

def get_version(self) -> str:
self.found(raise_error=True)

which_prog = which_import('pylibefp')
if which_prog not in self.version_cache:
import pylibefp
self.version_cache[which_prog] = safe_version(pylibefp.__version__)

candidate_version = self.version_cache[which_prog]

if "undef" in candidate_version:
raise TypeError(
"Using custom build without tags. Please pull git tags with `git pull origin master --tags`.")

return candidate_version

def compute(self, input_model: 'ResultInput', config: 'JobConfig') -> 'Result':
"""
Runs PylibEFP in API mode
"""
self.found(raise_error=True)

# if parse_version(self.get_version()) < parse_version("1.2"):
# raise ResourceError("Psi4 version '{}' not understood.".format(self.get_version()))

# Setup the job
input_data = input_model.dict(encoding="json")
# input_data["nthreads"] = config.ncores
# input_data["memory"] = int(config.memory * 1024 * 1024 * 1024 * 0.95) # Memory in bytes
input_data["success"] = False
input_data["return_output"] = True

# initialize EFP fragments
import pylibefp
# fix units when parsing efp string
efpobj = pylibefp.from_dict({**input_model.molecule.extras['efp_molecule']['extras'], 'units':'Bohr'})

# print efp geom in [A]
print(efpobj.banner())
print(efpobj.geometry_summary(units_to_bohr=qcel.constants.bohr2angstroms))

if input_model.model.method != 'efpefp':
raise InputError(f"Method not efpefp: {input_model.model.method}")

# set keywords
# * make keywords keys case insensitive
# * label changes the accepted names of the keywords (xr vs. exch)
# * append changes the defaults upon which they act (off for libefp vs. on for psi)
opts = {k.lower(): v for k, v in input_model.keywords.items()}
keywords_label = opts.pop('keywords_label', 'libefp').lower()
results_label = opts.pop('results_label', 'libefp').lower()
try:
efpobj.set_opts(opts, label=keywords_label, append=keywords_label)
except pylibefp.EFPSyntaxError as e:
raise InputError(e.message)

if input_model.driver == 'energy':
do_gradient = False
elif input_model.driver == 'gradient':
do_gradient = True
else:
raise InputError

# compute and report
efpobj.compute(do_gradient=do_gradient)
print(efpobj.energy_summary(label=results_label))

ene = efpobj.get_energy(label=results_label)

pp.pprint(ene)
print('<<< get_opts(): ', efpobj.get_opts(), '>>>')
#print('<<< summary(): ', efpobj.summary(), '>>>')
print('<<< get_energy():', ene, '>>>')
print('<<< get_atoms(): ', efpobj.get_atoms(), '>>>')
print(efpobj.energy_summary())
print(efpobj.geometry_summary(units_to_bohr=qcel.constants.bohr2angstroms))
print(efpobj.geometry_summary(units_to_bohr=1.0))

###### psi4 proc
#def run_efp(name, **kwargs):
# try:
# efpobj = efp_molecule.EFP
# except AttributeError:
# raise ValidationError("""Method 'efp' not available without EFP fragments in molecule""")
#
# core.set_variable('EFP ELST ENERGY', ene['electrostatic'] + ene['charge_penetration'] + ene['electrostatic_point_charges'])
# core.set_variable('EFP IND ENERGY', ene['polarization'])
# core.set_variable('EFP DISP ENERGY', ene['dispersion'])
# core.set_variable('EFP EXCH ENERGY', ene['exchange_repulsion'])
# core.set_variable('EFP TOTAL ENERGY', ene['total'])
# core.set_variable('CURRENT ENERGY', ene['total'])
#
# if do_gradient:
# core.print_out(efpobj.gradient_summary())
#
# core.set_variable('EFP TORQUE', torq)
#
# output_data = input_data

if input_model.driver == 'energy':
retres = ene['total']


# elif input_model.driver == 'gradient':
# torq = efpobj.get_gradient()

output_data = {
'schema_name': 'qcschema_output',
'schema_version': 1,
'extras': {
'local_properties': ene,
# 'outfiles': outfiles,
},
'properties': {},
'provenance': Provenance(creator="PylibEFP", version=self.get_version(), routine="pylibefp"),
'return_result': retres,
#'stdout': stdout,
}

output_data['success'] = True
return Result(**{**input_model.dict(), **output_data})
3 changes: 1 addition & 2 deletions qcengine/programs/tests/test_dftd3_mp2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -889,8 +889,7 @@ def test_dftd3__run_dftd3__3body(inp, subjects, request):
},
'keywords': {},
}
jrec = qcng.compute(resinp, 'dftd3', raise_error=True)
jrec = jrec.dict()
jrec = qcng.compute(resinp, 'dftd3', raise_error=True, return_dict=True)

assert len(jrec['extras']['qcvars']) == 8

Expand Down
Loading