diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index a2b39cab0..e6f6f24f6 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -25,6 +25,11 @@ organisation on `GitHub `__. * Fix recursion bug in :func:`sire.base.wrap()` function. +* Add support for passing cell vectors to ``PyQMForce`` and ``TorchQMForce``. + +* Add ``--install-metadata`` option to ``setup.py`` to register development source installations + with ``conda``. + * Fix :meth:`Dynamics.get_rest2_scale()` method. `2025.3.0 `__ - November 2025 diff --git a/doc/source/tutorial/part08/01_intro.rst b/doc/source/tutorial/part08/01_intro.rst index 632cbd442..f68beef45 100644 --- a/doc/source/tutorial/part08/01_intro.rst +++ b/doc/source/tutorial/part08/01_intro.rst @@ -63,6 +63,7 @@ signature: charges_mm: List[float], xyz_qm: List[List[float]], xyz_mm: List[List[float]], + cell: Optional[List[List[float]]] = None, idx_mm: Optional[List[int]] = None, ) -> Tuple[float, List[List[float]], List[List[float]]]: diff --git a/setup.py b/setup.py index c5cfa5722..ec4f838e0 100644 --- a/setup.py +++ b/setup.py @@ -20,12 +20,13 @@ You can use `--skip-build` to skip the building of the corelib and wrappers """ -import sys +import glob +import json import os import platform import subprocess import shutil -import glob +import sys try: # We have to check the version, but we can't do this by @@ -282,6 +283,14 @@ def parse_args(): help="Skip the build of the C++ code (only use if you know that " "the C++ code is already built)", ) + parser.add_argument( + "--install-metadata", + action="store_true", + default=False, + help="Install package metadata. This is useful when you are building " + "from source but still want to be able to query the installation using " + "conda list sire.", + ) parser.add_argument( "action", nargs="*", @@ -383,8 +392,8 @@ def conda_install( dependencies_to_skip = [] for dependency in dependencies: - # remove any quotes from the dependency - dependency = dependency.replace("\"", "") + # remove any quotes from the dependency + dependency = dependency.replace('"', "") if dependency == "python" or is_installed(dependency, conda_exe): # no need to install again @@ -472,10 +481,8 @@ def install_requires(install_bss_reqs=False, install_emle_reqs=False, yes=True): # this didn't import - we are missing setuptools print("Installing setuptools") conda_install( - ["setuptools"], - install_bss_reqs, - install_emle_reqs=False, - yes=yes) + ["setuptools"], install_bss_reqs, install_emle_reqs=False, yes=yes + ) try: import pkg_resources except Exception: @@ -581,24 +588,32 @@ def build(ncores: int = 1, npycores: int = 1, coredefs=[], pydefs=[]): if conda_build: print("This is a conda build") - CXX = os.environ["CXX"] - CC = os.environ["CC"] - - # make sure that these compilers are in the path - CXX_bin = shutil.which(CXX) - CC_bin = shutil.which(CC) - - print(f"{CXX} => {CXX_bin}") - print(f"{CC} => {CC_bin}") - - if CXX_bin is None or CC_bin is None: - print("Cannot find the compilers requested by conda-build in the PATH") - print("Please check that the compilers are installed and available.") - sys.exit(-1) - - # use the full paths, in case CMake struggles - CXX = CXX_bin - CC = CC_bin + # Try to get compilers from environment + CXX = os.environ.get("CXX") + CC = os.environ.get("CC") + + # Fallback to finding cl.exe on Windows + if (CXX is None or CC is None) and is_windows: + import shutil + cl_path = shutil.which("cl.exe") or shutil.which("cl") + if cl_path: + print(f"Compiler not in environment, using found compiler: {cl_path}") + if CXX is None: + CXX = cl_path + if CC is None: + CC = cl_path + else: + raise ValueError( + "Conda build on Windows requires CXX and CC environment variables. " + "Ensure your conda recipe includes {{ compiler('c') }} and {{ compiler('cxx') }} " + "in build requirements and that Visual Studio is properly installed." + ) + elif CXX is None or CC is None: + raise ValueError( + f"Conda build detected but compiler environment variables not set. " + f"CXX={CXX}, CC={CC}. " + f"Ensure your conda recipe includes compiler requirements." + ) elif is_macos: try: @@ -920,6 +935,8 @@ def install(ncores: int = 1, npycores: int = 1): if __name__ == "__main__": + OLDPWD = os.getcwd() + args = parse_args() if len(args.action) != 1: @@ -985,3 +1002,28 @@ def install(ncores: int = 1, npycores: int = 1): f"Unrecognised action '{action}'. Please use 'install_requires', " "'build', 'install' or 'install_module'" ) + + # Create minimist package metadata so that 'conda list sire' works. + if args.install_metadata: + os.chdir(OLDPWD) + if "CONDA_PREFIX" in os.environ: + metadata_dir = os.path.join(os.environ["CONDA_PREFIX"], "conda-meta") + if os.path.exists(metadata_dir): + # Get the Python version. + pyver = f"py{sys.version_info.major}{sys.version_info.minor}" + metadata = { + "name": "sire", + "version": open("version.txt").readline().strip(), + "build": pyver, + "build_number": 0, + "channel": "local", + "size": 0, + "license": "GPL-3.0-or-later", + "subdir": platform_string, + } + metadata_file = os.path.join( + metadata_dir, f"sire-{metadata['version']}-{pyver}.json" + ) + with open(metadata_file, "w") as f: + json.dump(metadata, f, indent=2) + print(f"Created conda package metadata file: {metadata_file}") diff --git a/src/sire/mol/__init__.py b/src/sire/mol/__init__.py index 2f4d09770..20a0d5ca8 100644 --- a/src/sire/mol/__init__.py +++ b/src/sire/mol/__init__.py @@ -1667,9 +1667,10 @@ def _dynamics( Whether or not to swap the end states. If this is True, then the perturbation will run from the perturbed back to the reference molecule (the perturbed molecule will be at lambda=0, - while the reference molecule will be at lambda=1). This will - use the coordinates of the perturbed molecule as the - starting point. + while the reference molecule will be at lambda=1). Note that this + will still use the coordinates of the reference state as the + starting point for the simulation, since it is assumed that + this reflects the current equilibrated state of the system. ignore_perturbations: bool Whether or not to ignore perturbations. If this is True, then diff --git a/tests/qm/test_qm.py b/tests/qm/test_qm.py index 555b86857..897d81f75 100644 --- a/tests/qm/test_qm.py +++ b/tests/qm/test_qm.py @@ -25,7 +25,7 @@ def test_callback_method(): """Makes sure that a callback method works correctly""" class Test: - def callback(self, a, b, c, d, e=None): + def callback(self, a, b, c, d, e=None, f=None): return (42, d, c) # Instantiate the class. @@ -39,19 +39,20 @@ def callback(self, a, b, c, d, e=None): b = [3, 4] c = [a, b] d = [b, a] - e = [4, 5] + e = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + f = [4, 7] # Call the callback. - result = cb.call(a, b, c, d, e) + result = cb.call(a, b, c, d, e, f) # Make sure the result is correct. - assert result == (42, d, c) == test.callback(a, b, c, d) + assert result == (42, d, c) == test.callback(a, b, c, d, e, f) def test_callback_function(): """Makes sure that a callback function works correctly""" - def callback(a, b, c, d, e=None): + def callback(a, b, c, d, e=None, f=None): return (42, d, c) # Create a callback object. @@ -62,13 +63,14 @@ def callback(a, b, c, d, e=None): b = [3, 4] c = [a, b] d = [b, a] - e = [4, 5] + e = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + f = [4, 5] # Call the callback. - result = cb.call(a, b, c, d, e) + result = cb.call(a, b, c, d, e, f) # Make sure the result is correct. - assert result == (42, d, c) == callback(a, b, c, d) + assert result == (42, d, c) == callback(a, b, c, d, e, f) @pytest.mark.parametrize( @@ -419,7 +421,7 @@ def test_create_engine(ala_mols): """ # A test callback function. Returns a known energy and dummy forces. - def callback(numbers_qm, charges_mm, xyz_qm, xyz_mm, idx_mm=None): + def callback(numbers_qm, charges_mm, xyz_qm, xyz_mm, cell=None, idx_mm=None): return (42, xyz_qm, xyz_mm) # Create a local copy of the test system. diff --git a/wrapper/Convert/SireOpenMM/PyQMCallback.pypp.cpp b/wrapper/Convert/SireOpenMM/PyQMCallback.pypp.cpp index 1264f0745..885a15bda 100644 --- a/wrapper/Convert/SireOpenMM/PyQMCallback.pypp.cpp +++ b/wrapper/Convert/SireOpenMM/PyQMCallback.pypp.cpp @@ -65,18 +65,18 @@ void register_PyQMCallback_class(){ typedef bp::class_< SireOpenMM::PyQMCallback > PyQMCallback_exposer_t; PyQMCallback_exposer_t PyQMCallback_exposer = PyQMCallback_exposer_t( "PyQMCallback", "A callback wrapper class to interface with external QM engines\nvia the CustomCPPForceImpl.", bp::init< >("Default constructor.") ); bp::scope PyQMCallback_scope( PyQMCallback_exposer ); - PyQMCallback_exposer.def( bp::init< bp::api::object, bp::optional< QString > >(( bp::arg("arg0"), bp::arg("name")="" ), "Constructor\nPar:am py_object\nA Python object that contains the callback function.\n\nPar:am name\nThe name of a callback method that take the following arguments:\n- numbers_qm: A list of atomic numbers for the atoms in the ML region.\n- charges_mm: A list of the MM charges in mod electron charge.\n- xyz_qm: A list of positions for the atoms in the ML region in Angstrom.\n- xyz_mm: A list of positions for the atoms in the MM region in Angstrom.\n- idx_mm: A list of indices for the MM atoms in the QM/MM region.\nThe callback should return a tuple containing:\n- The energy in kJmol.\n- A list of forces for the QM atoms in kJmolnm.\n- A list of forces for the MM atoms in kJmolnm.\nIf empty, then the object is assumed to be a callable.\n") ); + PyQMCallback_exposer.def( bp::init< bp::api::object, bp::optional< QString > >(( bp::arg("arg0"), bp::arg("name")="" ), "Constructor\nPar:am py_object\nA Python object that contains the callback function.\n\nPar:am name\nThe name of a callback method that take the following arguments:\n- numbers_qm: A list of atomic numbers for the atoms in the ML region.\n- charges_mm: A list of the MM charges in mod electron charge.\n- xyz_qm: A list of positions for the atoms in the ML region in Angstrom.\n- xyz_mm: A list of positions for the atoms in the MM region in Angstrom.\n- cell: A list of cell vectors in Angstrom.\n- idx_mm: A list of indices for the MM atoms in the QM/MM region.\nThe callback should return a tuple containing:\n- The energy in kJmol.\n- A list of forces for the QM atoms in kJmolnm.\n- A list of forces for the MM atoms in kJmolnm.\nIf empty, then the object is assumed to be a callable.\n") ); { //::SireOpenMM::PyQMCallback::call - typedef ::boost::tuples::tuple< double, QVector< QVector< double > >, QVector< QVector< double > >, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type > ( ::SireOpenMM::PyQMCallback::*call_function_type)( ::QVector< int >,::QVector< double >,::QVector< QVector< double > >,::QVector< QVector< double > >,::QVector< int > ) const; + typedef ::boost::tuples::tuple< double, QVector< QVector< double > >, QVector< QVector< double > >, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type > ( ::SireOpenMM::PyQMCallback::*call_function_type)( ::QVector< int >,::QVector< double >,::QVector< QVector< double > >,::QVector< QVector< double > >,::QVector >, ::QVector< int > ) const; call_function_type call_function_value( &::SireOpenMM::PyQMCallback::call ); PyQMCallback_exposer.def( "call" , call_function_value - , ( bp::arg("numbers_qm"), bp::arg("charges_mm"), bp::arg("xyz_qm"), bp::arg("xyz_mm"), bp::arg("idx_mm") ) + , ( bp::arg("numbers_qm"), bp::arg("charges_mm"), bp::arg("xyz_qm"), bp::arg("xyz_mm"), bp::arg("cell"), bp::arg("idx_mm") ) , bp::release_gil_policy() - , "Call the callback function.\nPar:am numbers_qm\nA vector of atomic numbers for the atoms in the ML region.\n\nPar:am charges_mm\nA vector of the charges on the MM atoms in mod electron charge.\n\nPar:am xyz_qm\nA vector of positions for the atoms in the ML region in Angstrom.\n\nPar:am xyz_mm\nA vector of positions for the atoms in the MM region in Angstrom.\n\nPar:am idx_mm A vector of indices for the MM atoms in the QM/MM region. Note that len(idx_mm) <= len(charges_mm) since it only contains the indices of true MM atoms, not link atoms or virtual charges.\n\nReturn:s\nA tuple containing:\n- The energy in kJmol.\n- A vector of forces for the QM atoms in kJmolnm.\n- A vector of forces for the MM atoms in kJmolnm.\n" ); + , "Call the callback function.\nPar:am numbers_qm\nA vector of atomic numbers for the atoms in the ML region.\n\nPar:am charges_mm\nA vector of the charges on the MM atoms in mod electron charge.\n\nPar:am xyz_qm\nA vector of positions for the atoms in the ML region in Angstrom.\n\nPar:am xyz_mm\nA vector of positions for the atoms in the MM region in Angstrom.\n\nPar:am cell A list of cell vectors in Angstrom.\n\nPar:am idx_mm A vector of indices for the MM atoms in the QM/MM region. Note that len(idx_mm) <= len(charges_mm) since it only contains the indices of true MM atoms, not link atoms or virtual charges.\n\nReturn:s\nA tuple containing:\n- The energy in kJmol.\n- A vector of forces for the QM atoms in kJmolnm.\n- A vector of forces for the MM atoms in kJmolnm.\n" ); } { //::SireOpenMM::PyQMCallback::typeName diff --git a/wrapper/Convert/SireOpenMM/PyQMEngine.pypp.cpp b/wrapper/Convert/SireOpenMM/PyQMEngine.pypp.cpp index b4589667e..ee197ff51 100644 --- a/wrapper/Convert/SireOpenMM/PyQMEngine.pypp.cpp +++ b/wrapper/Convert/SireOpenMM/PyQMEngine.pypp.cpp @@ -67,15 +67,15 @@ void register_PyQMEngine_class(){ PyQMEngine_exposer.def( bp::init< SireOpenMM::PyQMEngine const & >(( bp::arg("other") ), "Copy constructor.") ); { //::SireOpenMM::PyQMEngine::call - typedef ::boost::tuples::tuple< double, QVector< QVector< double > >, QVector< QVector< double > >, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type > ( ::SireOpenMM::PyQMEngine::*call_function_type)( ::QVector< int >,::QVector< double >,::QVector< QVector< double > >,::QVector< QVector< double > >,::QVector < int > ) const; + typedef ::boost::tuples::tuple< double, QVector< QVector< double > >, QVector< QVector< double > >, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type > ( ::SireOpenMM::PyQMEngine::*call_function_type)( ::QVector< int >,::QVector< double >,::QVector< QVector< double > >,::QVector< QVector< double > >,::QVector< QVector< double > >,::QVector < int > ) const; call_function_type call_function_value( &::SireOpenMM::PyQMEngine::call ); PyQMEngine_exposer.def( "call" , call_function_value - , ( bp::arg("numbers_qm"), bp::arg("charges_mm"), bp::arg("xyz_qm"), bp::arg("xyz_mm"), bp::arg("idx_mm") ) + , ( bp::arg("numbers_qm"), bp::arg("charges_mm"), bp::arg("xyz_qm"), bp::arg("xyz_mm"), bp::arg("cell"), bp::arg("idx_mm") ) , bp::release_gil_policy() - , "Call the callback function.\nPar:am numbers_qm\nA vector of atomic numbers for the atoms in the ML region.\n\nPar:am charges_mm\nA vector of the charges on the MM atoms in mod electron charge.\n\nPar:am xyz_qm\nA vector of positions for the atoms in the ML region in Angstrom.\n\nPar:am xyz_mm\nA vector of positions for the atoms in the MM region in Angstrom.\n\nPar:am idx_mm A vector of indices for the MM atoms in the QM/MM region. Note that len(idx_mm) <= len(charges_mm) since it only contains the indices of the true MM atoms, not link atoms or virtual charges.\n\nReturn:s\nA tuple containing:\n- The energy in kJmol.\n- A vector of forces for the QM atoms in kJmolnm.\n- A vector of forces for the MM atoms in kJmolnm.\n" ); + , "Call the callback function.\nPar:am numbers_qm\nA vector of atomic numbers for the atoms in the ML region.\n\nPar:am charges_mm\nA vector of the charges on the MM atoms in mod electron charge.\n\nPar:am xyz_qm\nA vector of positions for the atoms in the ML region in Angstrom.\n\nPar:am xyz_mm\nA vector of positions for the atoms in the MM region in Angstrom.\n\nPar:am cell A list of cell vectors in Angstrom.\n\nPar:am idx_mm A vector of indices for the MM atoms in the QM/MM region. Note that len(idx_mm) <= len(charges_mm) since it only contains the indices of the true MM atoms, not link atoms or virtual charges.\n\nReturn:s\nA tuple containing:\n- The energy in kJmol.\n- A vector of forces for the QM atoms in kJmolnm.\n- A vector of forces for the MM atoms in kJmolnm.\n" ); } { //::SireOpenMM::PyQMEngine::getAtoms diff --git a/wrapper/Convert/SireOpenMM/PyQMForce.pypp.cpp b/wrapper/Convert/SireOpenMM/PyQMForce.pypp.cpp index 18797552d..b8b8bebd3 100644 --- a/wrapper/Convert/SireOpenMM/PyQMForce.pypp.cpp +++ b/wrapper/Convert/SireOpenMM/PyQMForce.pypp.cpp @@ -69,15 +69,15 @@ void register_PyQMForce_class(){ PyQMForce_exposer.def( bp::init< SireOpenMM::PyQMForce const & >(( bp::arg("other") ), "Copy constructor.") ); { //::SireOpenMM::PyQMForce::call - typedef ::boost::tuples::tuple< double, QVector< QVector< double > >, QVector< QVector< double > >, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type > ( ::SireOpenMM::PyQMForce::*call_function_type)( ::QVector< int >,::QVector< double >,::QVector< QVector< double > >,::QVector< QVector< double > >, ::QVector < int > ) const; + typedef ::boost::tuples::tuple< double, QVector< QVector< double > >, QVector< QVector< double > >, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type > ( ::SireOpenMM::PyQMForce::*call_function_type)( ::QVector< int >,::QVector< double >,::QVector< QVector< double > >,::QVector< QVector< double > >, ::QVector< QVector< double > >, ::QVector < int > ) const; call_function_type call_function_value( &::SireOpenMM::PyQMForce::call ); PyQMForce_exposer.def( "call" , call_function_value - , ( bp::arg("numbers_qm"), bp::arg("charges_mm"), bp::arg("xyz_qm"), bp::arg("xyz_mm"), bp::arg("idx_mm") ) + , ( bp::arg("numbers_qm"), bp::arg("charges_mm"), bp::arg("xyz_qm"), bp::arg("xyz_mm"), bp::arg("cell"), bp::arg("idx_mm") ) , bp::release_gil_policy() - , "Call the callback function.\nPar:am numbers_qm\nA vector of atomic numbers for the atoms in the ML region.\n\nPar:am charges_mm\nA vector of the charges on the MM atoms in mod electron charge.\n\nPar:am xyz_qm\nA vector of positions for the atoms in the ML region in Angstrom.\n\nPar:am xyz_mm\nA vector of positions for the atoms in the MM region in Angstrom.\n\nPar:am idx_mm A vector of indices for the MM atoms in the QM/MM region. Note that len(idx_mm) <= len(charges_mm) since it only contains the indices of true MM atoms, not link atoms or virtual charges.\n\nReturn:s\nA tuple containing:\n- The energy in kJmol.\n- A vector of forces for the QM atoms in kJmolnm.\n- A vector of forces for the MM atoms in kJmolnm.\n" ); + , "Call the callback function.\nPar:am numbers_qm\nA vector of atomic numbers for the atoms in the ML region.\n\nPar:am charges_mm\nA vector of the charges on the MM atoms in mod electron charge.\n\nPar:am xyz_qm\nA vector of positions for the atoms in the ML region in Angstrom.\n\nPar:am xyz_mm\nA vector of positions for the atoms in the MM region in Angstrom.\n\nPar:am cell A list of cell vectors in Angstrom.\n\nPar:am idx_mm A vector of indices for the MM atoms in the QM/MM region. Note that len(idx_mm) <= len(charges_mm) since it only contains the indices of true MM atoms, not link atoms or virtual charges.\n\nReturn:s\nA tuple containing:\n- The energy in kJmol.\n- A vector of forces for the QM atoms in kJmolnm.\n- A vector of forces for the MM atoms in kJmolnm.\n" ); } { //::SireOpenMM::PyQMForce::getAtoms diff --git a/wrapper/Convert/SireOpenMM/pyqm.cpp b/wrapper/Convert/SireOpenMM/pyqm.cpp index 8c37cc2ad..c8e0f1334 100644 --- a/wrapper/Convert/SireOpenMM/pyqm.cpp +++ b/wrapper/Convert/SireOpenMM/pyqm.cpp @@ -153,6 +153,7 @@ PyQMCallback::call( QVector charges_mm, QVector> xyz_qm, QVector> xyz_mm, + QVector> cell, QVector idx_mm) const { @@ -170,6 +171,7 @@ PyQMCallback::call( charges_mm, xyz_qm, xyz_mm, + cell, idx_mm ); } @@ -191,6 +193,7 @@ PyQMCallback::call( charges_mm, xyz_qm, xyz_mm, + cell, idx_mm ); } @@ -403,9 +406,10 @@ PyQMForce::call( QVector charges_mm, QVector> xyz_qm, QVector> xyz_mm, + QVector> cell, QVector idx_mm) const { - return this->callback.call(numbers_qm, charges_mm, xyz_qm, xyz_mm, idx_mm); + return this->callback.call(numbers_qm, charges_mm, xyz_qm, xyz_mm, cell, idx_mm); } ///////// @@ -546,6 +550,13 @@ double PyQMForceImpl::computeForce( Vector(10*box_z[0], 10*box_z[1], 10*box_z[2]) ); + // Store the cell vectors in Angstrom. + QVector> cell = { + {10*box_x[0], 10*box_x[1], 10*box_x[2]}, + {10*box_y[0], 10*box_y[1], 10*box_y[2]}, + {10*box_z[0], 10*box_z[1], 10*box_z[2]} + }; + // Store the QM atomic indices and numbers. auto qm_atoms = this->owner.getAtoms(); auto numbers = this->owner.getNumbers(); @@ -786,6 +797,7 @@ double PyQMForceImpl::computeForce( charges_mm, xyz_qm, xyz_mm, + cell, idx_mm ); @@ -1073,9 +1085,10 @@ PyQMEngine::call( QVector charges_mm, QVector> xyz_qm, QVector> xyz_mm, + QVector> cell, QVector idx_mm) const { - return this->callback.call(numbers_qm, charges_mm, xyz_qm, xyz_mm, idx_mm); + return this->callback.call(numbers_qm, charges_mm, xyz_qm, xyz_mm, cell, idx_mm); } QMForce* PyQMEngine::createForce() const diff --git a/wrapper/Convert/SireOpenMM/pyqm.h b/wrapper/Convert/SireOpenMM/pyqm.h index f4e94a658..5b149add6 100644 --- a/wrapper/Convert/SireOpenMM/pyqm.h +++ b/wrapper/Convert/SireOpenMM/pyqm.h @@ -88,6 +88,7 @@ namespace SireOpenMM - charges_mm: A list of the MM charges in mod electron charge. - xyz_qm: A list of positions for the atoms in the ML region in Angstrom. - xyz_mm: A list of positions for the atoms in the MM region in Angstrom. + - cell: A list of the 3 cell vectors in Angstrom. - idx_mm: A list of indices for MM atom indices in the QM/MM region. The callback should return a tuple containing: - The energy in kJ/mol. @@ -110,6 +111,9 @@ namespace SireOpenMM \param xyz_mm A vector of positions for the atoms in the MM region in Angstrom. + \param cell + A vector of the 3 cell vectors in Angstrom. + \param idx_mm A vector of MM atom indices. Note that len(idx_mm) <= len(charges_mm) since it only contains the indices of true MM atoms, not link atoms @@ -126,6 +130,7 @@ namespace SireOpenMM QVector charges_mm, QVector> xyz_qm, QVector> xyz_mm, + QVector> cell, QVector idx_mm ) const; @@ -323,6 +328,9 @@ namespace SireOpenMM \param xyz_mm A vector of positions for the atoms in the MM region in Angstrom. + \param cell + A vector of the 3 cell vectors in Angstrom. + \param idx_mm A vector of MM atom indices. Note that len(idx_mm) <= len(charges_mm) since it only contains the indices of true MM atoms, not link atoms @@ -339,6 +347,7 @@ namespace SireOpenMM QVector charges_mm, QVector> xyz_qm, QVector> xyz_mm, + QVector> cell, QVector idx_mm ) const; @@ -590,6 +599,9 @@ namespace SireOpenMM \param xyz_mm A vector of positions for the atoms in the MM region in Angstrom. + \param cell + A vector of the 3 cell vectors in Angstrom. + \param idx_mm A vector of MM atom indices. Note that len(idx_mm) <= len(charges_mm) since it only contains the indices of true MM atoms, not link atoms @@ -606,6 +618,7 @@ namespace SireOpenMM QVector charges_mm, QVector> xyz_qm, QVector> xyz_mm, + QVector> cell, QVector idx_mm ) const; diff --git a/wrapper/Convert/SireOpenMM/torchqm.cpp b/wrapper/Convert/SireOpenMM/torchqm.cpp index c75456c75..8b36db7c8 100644 --- a/wrapper/Convert/SireOpenMM/torchqm.cpp +++ b/wrapper/Convert/SireOpenMM/torchqm.cpp @@ -429,6 +429,13 @@ double TorchQMForceImpl::computeForce( Vector(10*box_z[0], 10*box_z[1], 10*box_z[2]) ); + // Store the cell vectors in Angstrom. + QVector> cell = { + {10*box_x[0], 10*box_x[1], 10*box_x[2]}, + {10*box_y[0], 10*box_y[1], 10*box_y[2]}, + {10*box_z[0], 10*box_z[1], 10*box_z[2]} + }; + // Store the QM atomic indices and numbers. auto qm_atoms = this->owner.getAtoms(); auto numbers = this->owner.getNumbers(); @@ -715,12 +722,19 @@ double TorchQMForceImpl::computeForce( .to(device); xyz_mm_torch.requires_grad_(true); + // Cell vectors. + torch::Tensor cell_torch = torch::from_blob(cell.data(), {3, 3}, + torch::TensorOptions().dtype(torch::kFloat64)) + .to(torch::kFloat32).to(device); + cell_torch.requires_grad_(false); + // Create the input vector. auto input = std::vector{ atomic_numbers_torch, charges_mm_torch, xyz_qm_torch, - xyz_mm_torch + xyz_mm_torch, + cell_torch }; // Compute the energies.