From 6c3c247ede408eee441a06d7c91962dbd4f4e795 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Thu, 26 Sep 2024 15:46:45 -0500 Subject: [PATCH 1/5] almost r2g --- src/python/friendzone/__init__.py | 4 +- src/python/friendzone/nwx2nwchem/__init__.py | 58 ----------- .../chemical_system_conversions.py | 5 +- .../friendzone/nwx2qcengine/__init__.py | 97 ++++++++++++++++++- .../friendzone/nwx2qcengine/call_qcengine.py | 26 +++-- .../friendzone/nwx2qcengine/pt2driver.py | 35 ------- .../python/unit_tests/nwx2nwchem/__init__.py | 13 --- .../test_nwchem.py | 14 ++- .../unit_tests/nwx2qcengine/test_pt2driver.py | 31 ------ 9 files changed, 131 insertions(+), 152 deletions(-) delete mode 100644 src/python/friendzone/nwx2nwchem/__init__.py delete mode 100644 src/python/friendzone/nwx2qcengine/pt2driver.py delete mode 100644 tests/python/unit_tests/nwx2nwchem/__init__.py rename tests/python/unit_tests/{nwx2nwchem => nwx2qcengine}/test_nwchem.py (78%) delete mode 100644 tests/python/unit_tests/nwx2qcengine/test_pt2driver.py diff --git a/src/python/friendzone/__init__.py b/src/python/friendzone/__init__.py index 73dc0ef..20d3efc 100644 --- a/src/python/friendzone/__init__.py +++ b/src/python/friendzone/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .nwx2nwchem import load_nwchem_modules +from .nwx2qcengine import load_qcengine_modules from .nwx2qcelemental import load_qcelemental_modules @@ -25,5 +25,5 @@ def load_modules(mm): :param mm: The ModuleManager that the all Modules will be loaded into. :type mm: pluginplay.ModuleManager """ - load_nwchem_modules(mm) + load_qcengine_modules(mm) load_qcelemental_modules(mm) diff --git a/src/python/friendzone/nwx2nwchem/__init__.py b/src/python/friendzone/nwx2nwchem/__init__.py deleted file mode 100644 index 609302a..0000000 --- a/src/python/friendzone/nwx2nwchem/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2023 NWChemEx-Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ..friends import is_friend_enabled -import pluginplay as pp -from simde import TotalEnergy -from ..nwx2qcengine.call_qcengine import call_qcengine - - -class NWChemViaMolSSI(pp.ModuleBase): - - def __init__(self): - pp.ModuleBase.__init__(self) - self.satisfies_property_type(TotalEnergy()) - self.description("Calls NWChem via MolSSI's QCEngine") - self.add_input('method') - self.add_input("basis set") - - def run_(self, inputs, submods): - pt = TotalEnergy() - mol, = pt.unwrap_inputs(inputs) - method = inputs['method'].value() - basis = inputs['basis set'].value() - - e = call_qcengine(pt, mol, 'nwchem', method=method, basis=basis) - rv = self.results() - return pt.wrap_results(rv, e) - - -def load_nwchem_modules(mm): - """Loads the collection of modules that wrap NWChem calls. - - Currently, the modules in this collection are: - - #. NWChem : SCF - #. NWChem : MP2 - #. NWChem : CCSD - #. NWChem : CCSD(T) - - :param mm: The ModuleManager that the NWChem Modules will be loaded into. - :type mm: pluginplay.ModuleManager - """ - if is_friend_enabled('nwchem'): - for method in ['SCF', 'MP2', 'CCSD', 'CCSD(T)']: - mod_key = 'NWChem : ' + method - mm.add_module(mod_key, NWChemViaMolSSI()) - mm.change_input(mod_key, 'method', method) diff --git a/src/python/friendzone/nwx2qcelemental/chemical_system_conversions.py b/src/python/friendzone/nwx2qcelemental/chemical_system_conversions.py index 4e790aa..d73be76 100644 --- a/src/python/friendzone/nwx2qcelemental/chemical_system_conversions.py +++ b/src/python/friendzone/nwx2qcelemental/chemical_system_conversions.py @@ -43,7 +43,10 @@ def chemical_system2qc_mol(chem_sys): y = str(atom_i.y * au2ang) z = str(atom_i.z * au2ang) out += symbol + " " + x + " " + y + " " + z + "\n" - return qcel.models.Molecule.from_data(out) + return qcel.models.Molecule.from_data(out, + fix_com=True, + fix_orientation=True, + fix_symmetry="C1") def qc_mol2molecule(qc_mol): diff --git a/src/python/friendzone/nwx2qcengine/__init__.py b/src/python/friendzone/nwx2qcengine/__init__.py index cb2dc38..dea5160 100644 --- a/src/python/friendzone/nwx2qcengine/__init__.py +++ b/src/python/friendzone/nwx2qcengine/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 NWChemEx-Project +# Copyright 2024 NWChemEx-Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,3 +11,98 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +from ..friends import is_friend_enabled +import pluginplay as pp +from simde import TotalEnergy, EnergyNuclearGradientStdVectorD +from .call_qcengine import call_qcengine + + +class QCEngineEnergy(pp.ModuleBase): + + def __init__(self): + pp.ModuleBase.__init__(self) + self.satisfies_property_type(TotalEnergy()) + self.description("Driver module for calling friends through QCEngine") + + ddesc = 'Implementation detail DO NOT MANUALLY CHANGE!' + self.add_input('_driver').set_description(ddesc).set_default('energy') + self.add_input('program').set_description('Friend to call') + self.add_input('method').set_description('Level of theory') + self.add_input('basis set').set_description('Name of AO basis set') + + def run_(self, inputs, submods): + """ + Our strategy here is to use the fact that the inputs to the energy pt + are a subset of those to the gradient + """ + + egy_pt = TotalEnergy() + grad_pt = EnergyNuclearGradientStdVectorD() + _driver = inputs['_driver'].value() + + mol = None + if _driver == 'energy': + mol, = egy_pt.unwrap_inputs(inputs) + elif _driver == 'gradient': + mol, _ = grad_pt.unwrap_inputs(inputs) + #TODO verify points is equal to mol + else: + raise RuntimeError('Unexpected driver type') + + program = inputs['program'].value() + method = inputs['method'].value() + basis = inputs['basis set'].value() + model = {'method': method, 'basis': basis} + keywords = {} + outputs = call_qcengine(_driver, + mol, + program, + self.get_runtime(), + model=model, + keywords=keywords) + + rv = self.results() + if _driver == 'gradient': + grad = outputs['gradient'].flatten().tolist() + rv = grad_pt.wrap_results(rv, grad) + + return egy_pt.wrap_results(rv, outputs['energy']) + + +class QCEngineGradient(QCEngineEnergy): + + def __init__(self): + QCEngineEnergy.__init__(self) + self.satisfies_property_type(EnergyNuclearGradientStdVectorD()) + self.add_input('_driver').change('gradient') + + +def load_qcengine_modules(mm): + """Loads the collection of modules that wrap NWChem calls. + + Currently, the modules in this collection are: + + #. NWChem : SCF + #. NWChem : B3LYP + #. NWChem : MP2 + #. NWChem : CCSD + #. NWChem : CCSD(T) + + (and their gradients) + + :param mm: The ModuleManager that the NWChem Modules will be loaded into. + :type mm: pluginplay.ModuleManager + """ + + for program in ['nwchem']: + if is_friend_enabled(program): + for method in ['SCF', 'B3LYP', 'MP2', 'CCSD', 'CCSD(T)']: + egy_key = program + ' : ' + method + grad_key = egy_key + ' Gradient' + mm.add_module(egy_key, QCEngineEnergy()) + mm.add_module(grad_key, QCEngineGradient()) + + for key in [egy_key, grad_key]: + mm.change_input(key, 'program', program) + mm.change_input(key, 'method', method) diff --git a/src/python/friendzone/nwx2qcengine/call_qcengine.py b/src/python/friendzone/nwx2qcengine/call_qcengine.py index 1947c23..8515a70 100644 --- a/src/python/friendzone/nwx2qcengine/call_qcengine.py +++ b/src/python/friendzone/nwx2qcengine/call_qcengine.py @@ -15,10 +15,9 @@ import qcengine as qcng import qcelemental as qcel from ..nwx2qcelemental.chemical_system_conversions import chemical_system2qc_mol -from .pt2driver import pt2driver -def call_qcengine(pt, mol, program, **kwargs): +def call_qcengine(driver, mol, program, runtime, **kwargs): """ Wraps calling a program through the QCEngine API. .. note:: @@ -34,8 +33,8 @@ def call_qcengine(pt, mol, program, **kwargs): objects to their QCElemental equivalents. Right now those mappings include: - - property_type -> driver type - ChemicalSystem -> qcel.models.Molecule + - RuntimeView -> ??? While not supported at the moment, similar conversions for the AO basis set are possible. @@ -56,14 +55,21 @@ def call_qcengine(pt, mol, program, **kwargs): backend? :type program: str :param kwargs: Key-value pairs which will be forwarded to QCElemental's - ``AtomicInput`` class via the ``model`` key. + ``AtomicInput`` class as kwargs. - :return: The requested property. + :return: A dictionary containing the requested property and any other + property of potential interest. :rtype: Varies depending on the requested property """ - - driver = pt2driver(pt) qc_mol = chemical_system2qc_mol(mol) - inp = qcel.models.AtomicInput(molecule=qc_mol, driver=driver, model=kwargs) - results = qcng.compute(inp, program) - return results.return_result + inp = qcel.models.AtomicInput(molecule=qc_mol, driver=driver, **kwargs) + # TODO: figure out what task_config is supposed to be and get it from + # runtime https://github.com/MolSSI/QCEngine/blob/3b9ed2aee662424df6be12d9e7e23f51b9c6b6eb/qcengine/config.py#L152 + results = qcng.compute(inp, program, task_config=None) + if type(results) == qcel.models.common_models.FailedOperation: + print(results.error.error_message) + + rv = {driver: results.return_result} + if (driver == "gradient" and "qcvars" in results.extras): + rv['energy'] = float(results.extras["qcvars"]["CURRENT ENERGY"]) + return rv diff --git a/src/python/friendzone/nwx2qcengine/pt2driver.py b/src/python/friendzone/nwx2qcengine/pt2driver.py deleted file mode 100644 index 44215e3..0000000 --- a/src/python/friendzone/nwx2qcengine/pt2driver.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2023 NWChemEx-Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import simde - - -def pt2driver(pt): - """ Converts a SimDE property type to a QCElemental driver type. - - Within NWChemEx users pick the property to compute by specifying a - property type. Within QCElemental this is done by specifying a string. - This function maps SimDE property types to their corresponding QCElemental - string. - - :param pt: The property type we are converting. - :type pt: pluginplay.PropertyType - - :raises: Exception if ``pt`` is not a property type which has been - registered with this function. - """ - if pt.type() == simde.TotalEnergy().type(): - return 'energy' - - raise Exception('PropertyType is not registered') diff --git a/tests/python/unit_tests/nwx2nwchem/__init__.py b/tests/python/unit_tests/nwx2nwchem/__init__.py deleted file mode 100644 index cb2dc38..0000000 --- a/tests/python/unit_tests/nwx2nwchem/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2023 NWChemEx-Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tests/python/unit_tests/nwx2nwchem/test_nwchem.py b/tests/python/unit_tests/nwx2qcengine/test_nwchem.py similarity index 78% rename from tests/python/unit_tests/nwx2nwchem/test_nwchem.py rename to tests/python/unit_tests/nwx2qcengine/test_nwchem.py index c8e74bf..7bb82cc 100644 --- a/tests/python/unit_tests/nwx2nwchem/test_nwchem.py +++ b/tests/python/unit_tests/nwx2qcengine/test_nwchem.py @@ -14,7 +14,8 @@ from pluginplay import ModuleManager from friendzone import friends, load_modules -from simde import TotalEnergy +from simde import TotalEnergy, EnergyNuclearGradientStdVectorD +from chemist import PointSetD from molecules import make_h2 import unittest @@ -28,6 +29,17 @@ def test_scf(self): egy = self.mm.run_as(TotalEnergy(), key, mol) self.assertAlmostEqual(egy, -1.094184522864, places=5) + def test_scf_gradient(self): + mol = make_h2() + key = 'NWChem : SCF Gradient' + self.mm.change_input(key, 'basis set', 'sto-3g') + grad = self.mm.run_as(EnergyNuclearGradientStdVectorD(), key, mol, + PointSetD()) + + corr = [0.0, 0.0, -0.11827177600466043, 0.0, 0.0, 0.11827177600466043] + for g, c in zip(grad, corr): + self.assertAlmostEqual(g, c, places=4) + def test_mp2(self): mol = make_h2() key = 'NWChem : MP2' diff --git a/tests/python/unit_tests/nwx2qcengine/test_pt2driver.py b/tests/python/unit_tests/nwx2qcengine/test_pt2driver.py deleted file mode 100644 index 8398004..0000000 --- a/tests/python/unit_tests/nwx2qcengine/test_pt2driver.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2023 NWChemEx-Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from friendzone.nwx2qcengine.pt2driver import pt2driver -from simde import TotalEnergy -import unittest - - -class NotAPT: - pass - - -class Testpt2driver(unittest.TestCase): - - def test_pts_that_map_to_energy(self): - for pt in [TotalEnergy()]: - self.assertEqual(pt2driver(pt), 'energy') - - def test_bad_pt(self): - self.assertRaises(Exception, pt2driver, NotAPT()) From 1c632f053b656944b5d48a57ff06c88b7e5c1e43 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Thu, 26 Sep 2024 16:02:23 -0500 Subject: [PATCH 2/5] better error message --- .../friendzone/nwx2qcengine/__init__.py | 46 +++++++++++++------ .../friendzone/nwx2qcengine/call_qcengine.py | 16 +++++-- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/python/friendzone/nwx2qcengine/__init__.py b/src/python/friendzone/nwx2qcengine/__init__.py index dea5160..47d5736 100644 --- a/src/python/friendzone/nwx2qcengine/__init__.py +++ b/src/python/friendzone/nwx2qcengine/__init__.py @@ -23,9 +23,9 @@ class QCEngineEnergy(pp.ModuleBase): def __init__(self): pp.ModuleBase.__init__(self) self.satisfies_property_type(TotalEnergy()) - self.description("Driver module for calling friends through QCEngine") + self.description('Driver module for calling friends through QCEngine') - ddesc = 'Implementation detail DO NOT MANUALLY CHANGE!' + ddesc = 'Implementation detail. DO NOT MANUALLY CHANGE!' self.add_input('_driver').set_description(ddesc).set_default('energy') self.add_input('program').set_description('Friend to call') self.add_input('method').set_description('Level of theory') @@ -33,26 +33,30 @@ def __init__(self): def run_(self, inputs, submods): """ - Our strategy here is to use the fact that the inputs to the energy pt - are a subset of those to the gradient + Our strategy here is to use the fact that the inputs to the TotalEnergy + PT are a subset of those to other PTs """ - + # Step 0: Figure out the PT we're being run as egy_pt = TotalEnergy() grad_pt = EnergyNuclearGradientStdVectorD() _driver = inputs['_driver'].value() + # Step 1: Unwrap the inputs mol = None if _driver == 'energy': mol, = egy_pt.unwrap_inputs(inputs) elif _driver == 'gradient': mol, _ = grad_pt.unwrap_inputs(inputs) - #TODO verify points is equal to mol + #TODO: verify ignored second input (the point at which to take the + #derivative) is equal to the geometry of mol. else: raise RuntimeError('Unexpected driver type') program = inputs['program'].value() method = inputs['method'].value() basis = inputs['basis set'].value() + + # Step 2: Call QCEngine model = {'method': method, 'basis': basis} keywords = {} outputs = call_qcengine(_driver, @@ -62,6 +66,7 @@ def run_(self, inputs, submods): model=model, keywords=keywords) + # Step 3: Prepare results rv = self.results() if _driver == 'gradient': grad = outputs['gradient'].flatten().tolist() @@ -71,6 +76,13 @@ def run_(self, inputs, submods): class QCEngineGradient(QCEngineEnergy): + """ This class is largely implemented by QCEngineEnergy. The only difference + is in the ctor. The differences are: + + - Property type is set to EnergyNuclearGradientStdVectorD + - An internal implementation detail is modified to signal the modified + property type. + """ def __init__(self): QCEngineEnergy.__init__(self) @@ -79,17 +91,23 @@ def __init__(self): def load_qcengine_modules(mm): - """Loads the collection of modules that wrap NWChem calls. + """Loads the collection of modules that wrap QCElemental calls. + + Currently, the friends exported by this function are: + + #. NWChem + + the levels of theory are: - Currently, the modules in this collection are: + #. SCF + #. B3LYP + #. MP2 + #. CCSD + #. CCSD(T) - #. NWChem : SCF - #. NWChem : B3LYP - #. NWChem : MP2 - #. NWChem : CCSD - #. NWChem : CCSD(T) + and we have 0-th and 1-st derivatives. - (and their gradients) + The final set of modules is the Cartesian product of all of the above. :param mm: The ModuleManager that the NWChem Modules will be loaded into. :type mm: pluginplay.ModuleManager diff --git a/src/python/friendzone/nwx2qcengine/call_qcengine.py b/src/python/friendzone/nwx2qcengine/call_qcengine.py index 8515a70..ffb3c77 100644 --- a/src/python/friendzone/nwx2qcengine/call_qcengine.py +++ b/src/python/friendzone/nwx2qcengine/call_qcengine.py @@ -34,7 +34,7 @@ def call_qcengine(driver, mol, program, runtime, **kwargs): include: - ChemicalSystem -> qcel.models.Molecule - - RuntimeView -> ??? + - RuntimeView -> qcng.TaskConfig While not supported at the moment, similar conversions for the AO basis set are possible. @@ -61,14 +61,24 @@ def call_qcengine(driver, mol, program, runtime, **kwargs): property of potential interest. :rtype: Varies depending on the requested property """ + + # Step 1: Prepare the chemistry-related input qc_mol = chemical_system2qc_mol(mol) inp = qcel.models.AtomicInput(molecule=qc_mol, driver=driver, **kwargs) + + # Step 2: Prepare the runtime-related input # TODO: figure out what task_config is supposed to be and get it from # runtime https://github.com/MolSSI/QCEngine/blob/3b9ed2aee662424df6be12d9e7e23f51b9c6b6eb/qcengine/config.py#L152 - results = qcng.compute(inp, program, task_config=None) + task_config = None + + # Step 3: Run QCEngine + results = qcng.compute(inp, program, task_config=task_config) + + # Step 4: Verify the computation ran correctly if type(results) == qcel.models.common_models.FailedOperation: - print(results.error.error_message) + raise RuntimeError(results.error.error_message) + # Step 5: Prepare the results rv = {driver: results.return_result} if (driver == "gradient" and "qcvars" in results.extras): rv['energy'] = float(results.extras["qcvars"]["CURRENT ENERGY"]) From 57727415273d032d9f1d9c84c78886bc07ec9441 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Fri, 27 Sep 2024 09:39:33 -0500 Subject: [PATCH 3/5] "mpi support" and Jonathan's solution --- .../friendzone/nwx2qcengine/__init__.py | 95 ++++++++++--------- .../friendzone/nwx2qcengine/call_qcengine.py | 6 +- 2 files changed, 52 insertions(+), 49 deletions(-) diff --git a/src/python/friendzone/nwx2qcengine/__init__.py b/src/python/friendzone/nwx2qcengine/__init__.py index 47d5736..102770a 100644 --- a/src/python/friendzone/nwx2qcengine/__init__.py +++ b/src/python/friendzone/nwx2qcengine/__init__.py @@ -18,61 +18,61 @@ from .call_qcengine import call_qcengine +def _run_impl(driver, inputs, rv, runtime): + """ + Our strategy here is to use the fact that the inputs to the TotalEnergy + PT are a subset of those to other PTs + """ + # Step 0: Figure out the PT we're being run as + egy_pt = TotalEnergy() + grad_pt = EnergyNuclearGradientStdVectorD() + + # Step 1: Unwrap the inputs + mol = None + if driver == 'energy': + mol, = egy_pt.unwrap_inputs(inputs) + elif driver == 'gradient': + mol, _ = grad_pt.unwrap_inputs(inputs) + #TODO: verify ignored second input (the point at which to take the + #derivative) is equal to the geometry of mol. + else: + raise RuntimeError('Unexpected driver type') + + program = inputs['program'].value() + method = inputs['method'].value() + basis = inputs['basis set'].value() + + # Step 2: Call QCEngine + model = {'method': method, 'basis': basis} + keywords = {} + outputs = call_qcengine(driver, + mol, + program, + runtime, + model=model, + keywords=keywords) + + # Step 3: Prepare results + if driver == 'gradient': + grad = outputs['gradient'].flatten().tolist() + rv = grad_pt.wrap_results(rv, grad) + + return egy_pt.wrap_results(rv, outputs['energy']) + + class QCEngineEnergy(pp.ModuleBase): + """ Driver module for computing energies with QCEngine""" def __init__(self): pp.ModuleBase.__init__(self) self.satisfies_property_type(TotalEnergy()) - self.description('Driver module for calling friends through QCEngine') - - ddesc = 'Implementation detail. DO NOT MANUALLY CHANGE!' - self.add_input('_driver').set_description(ddesc).set_default('energy') + self.description(QCEngineEnergy.__doc__) self.add_input('program').set_description('Friend to call') self.add_input('method').set_description('Level of theory') self.add_input('basis set').set_description('Name of AO basis set') def run_(self, inputs, submods): - """ - Our strategy here is to use the fact that the inputs to the TotalEnergy - PT are a subset of those to other PTs - """ - # Step 0: Figure out the PT we're being run as - egy_pt = TotalEnergy() - grad_pt = EnergyNuclearGradientStdVectorD() - _driver = inputs['_driver'].value() - - # Step 1: Unwrap the inputs - mol = None - if _driver == 'energy': - mol, = egy_pt.unwrap_inputs(inputs) - elif _driver == 'gradient': - mol, _ = grad_pt.unwrap_inputs(inputs) - #TODO: verify ignored second input (the point at which to take the - #derivative) is equal to the geometry of mol. - else: - raise RuntimeError('Unexpected driver type') - - program = inputs['program'].value() - method = inputs['method'].value() - basis = inputs['basis set'].value() - - # Step 2: Call QCEngine - model = {'method': method, 'basis': basis} - keywords = {} - outputs = call_qcengine(_driver, - mol, - program, - self.get_runtime(), - model=model, - keywords=keywords) - - # Step 3: Prepare results - rv = self.results() - if _driver == 'gradient': - grad = outputs['gradient'].flatten().tolist() - rv = grad_pt.wrap_results(rv, grad) - - return egy_pt.wrap_results(rv, outputs['energy']) + return _run_impl('energy', inputs, self.results(), self.get_runtime()) class QCEngineGradient(QCEngineEnergy): @@ -87,7 +87,10 @@ class QCEngineGradient(QCEngineEnergy): def __init__(self): QCEngineEnergy.__init__(self) self.satisfies_property_type(EnergyNuclearGradientStdVectorD()) - self.add_input('_driver').change('gradient') + + def run_(self, inputs, submods): + return _run_impl('gradient', inputs, self.results(), + self.get_runtime()) def load_qcengine_modules(mm): diff --git a/src/python/friendzone/nwx2qcengine/call_qcengine.py b/src/python/friendzone/nwx2qcengine/call_qcengine.py index ffb3c77..463a370 100644 --- a/src/python/friendzone/nwx2qcengine/call_qcengine.py +++ b/src/python/friendzone/nwx2qcengine/call_qcengine.py @@ -15,6 +15,7 @@ import qcengine as qcng import qcelemental as qcel from ..nwx2qcelemental.chemical_system_conversions import chemical_system2qc_mol +from qcengine.config import TaskConfig def call_qcengine(driver, mol, program, runtime, **kwargs): @@ -67,9 +68,8 @@ def call_qcengine(driver, mol, program, runtime, **kwargs): inp = qcel.models.AtomicInput(molecule=qc_mol, driver=driver, **kwargs) # Step 2: Prepare the runtime-related input - # TODO: figure out what task_config is supposed to be and get it from - # runtime https://github.com/MolSSI/QCEngine/blob/3b9ed2aee662424df6be12d9e7e23f51b9c6b6eb/qcengine/config.py#L152 - task_config = None + # I *think* ncores is supposed to be the number of threads per MPI rank + task_config = {'nnodes': runtime.size(), 'ncores': 1, 'retries': 0} # Step 3: Run QCEngine results = qcng.compute(inp, program, task_config=task_config) From 0c93e4de4a3cecc65201d6fb21bfec982d64d3e4 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Fri, 27 Sep 2024 09:56:41 -0500 Subject: [PATCH 4/5] move to docstrings for descriptions --- docs/source/conf.py | 2 +- .../friendzone/nwx2qcelemental/__init__.py | 8 ++++---- src/python/friendzone/nwx2qcengine/__init__.py | 17 +++++++++++------ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 9eaa135..b4576a4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -176,7 +176,7 @@ # This skips classes that derived from ModuleBase, because those classes will -# have Module API documentation producable by PluginPlay +# have Module API documentation producible by PluginPlay def skip_pluginplay_modules(app, what, name, obj, skip, options): bases = obj.obj['bases'] if 'bases' in obj.obj.keys() else [] if 'pluginplay.ModuleBase' in bases: diff --git a/src/python/friendzone/nwx2qcelemental/__init__.py b/src/python/friendzone/nwx2qcelemental/__init__.py index dc63b3b..7169e0f 100644 --- a/src/python/friendzone/nwx2qcelemental/__init__.py +++ b/src/python/friendzone/nwx2qcelemental/__init__.py @@ -19,14 +19,14 @@ class SystemViaMolSSI(pp.ModuleBase): + """Creates an NWChemEx ChemicalSystem by going through MolSSI's string + parser. + """ def __init__(self): pp.ModuleBase.__init__(self) self.satisfies_property_type(MoleculeFromString()) - self.description(""" - Creates an NWChemEx ChemicalSystem by going through MolSSI's - string parser. - """) + self.description(SystemViaMolSSI.__doc__) def run_(self, inputs, submods): pt = MoleculeFromString() diff --git a/src/python/friendzone/nwx2qcengine/__init__.py b/src/python/friendzone/nwx2qcengine/__init__.py index 102770a..6ad0354 100644 --- a/src/python/friendzone/nwx2qcengine/__init__.py +++ b/src/python/friendzone/nwx2qcengine/__init__.py @@ -61,7 +61,10 @@ def _run_impl(driver, inputs, rv, runtime): class QCEngineEnergy(pp.ModuleBase): - """ Driver module for computing energies with QCEngine""" + """ Driver module for computing energies with QCEngine. + + This class relies on _run_impl to actually implement run_. + """ def __init__(self): pp.ModuleBase.__init__(self) @@ -76,12 +79,14 @@ def run_(self, inputs, submods): class QCEngineGradient(QCEngineEnergy): - """ This class is largely implemented by QCEngineEnergy. The only difference - is in the ctor. The differences are: + """ Driver module for computing gradients with QCEngine. + + This class extends QCEngineEnergy (QCEngine always computes the energy + when computing the gradient thus this module will also compute the + energy). Relative to QCEngineEnergy the main differences are: - - Property type is set to EnergyNuclearGradientStdVectorD - - An internal implementation detail is modified to signal the modified - property type. + - Addition of gradient property type + - Invocation of _run_impl with 'gradient' instead of 'energy' """ def __init__(self): From 3932098c498724d29abfb36c31020ebf33c8ff7b Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Fri, 27 Sep 2024 10:15:19 -0500 Subject: [PATCH 5/5] add documentation exception --- docs/source/nitpick_exceptions | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/nitpick_exceptions b/docs/source/nitpick_exceptions index 8779a5a..accf631 100644 --- a/docs/source/nitpick_exceptions +++ b/docs/source/nitpick_exceptions @@ -5,3 +5,5 @@ py:class chemist.ChemicalSystem py:class Varies depending on the requested property py:class qcelemental.models.Molecule + +py:obj QCEngineEnergy \ No newline at end of file