diff --git a/README.md b/README.md index faed8f4..9fe640c 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,8 @@ support the use of [DeePMD-kit](https://docs.deepmodeling.com/projects/deepmd/en/master/index.html), [ORCA](https://sites.google.com/site/orcainputlibrary/interfaces-and-qmm), [SQM](https://ambermd.org/AmberTools.php), -[XTB](https://xtb-docs.readthedocs.io/en/latest), or +[XTB](https://xtb-docs.readthedocs.io/en/latest), +[ACE]( https://acesuit.github.io/) (implemented using [PyJulip](https://github.com/casv2/pyjulip)), or [PySander](https://ambermd.org/AmberTools.php) for the backend, providing reference MM or QM with EMLE embedding, and pure EMLE implementations. To specify a backend, use the `--backend` argument when launching diff --git a/bin/emle-server b/bin/emle-server index 4d04c73..9c4b104 100755 --- a/bin/emle-server +++ b/bin/emle-server @@ -76,6 +76,7 @@ save_settings = os.getenv("EMLE_SAVE_SETTINGS") orca_template = os.getenv("EMLE_ORCA_TEMPLATE") deepmd_model = os.getenv("EMLE_DEEPMD_MODEL") rascal_model = os.getenv("EMLE_RASCAL_MODEL") +ace_model = os.getenv("EMLE_ACE_MODEL") parm7 = os.getenv("EMLE_PARM7") try: lambda_interpolate = [ @@ -122,6 +123,7 @@ env = { "device": device, "deepmd_model": deepmd_model, "rascal_model": rascal_model, + "ace_model": ace_model, "lambda_interpolate": lambda_interpolate, "interpolate_steps": interpolate_steps, "parm7": parm7, @@ -217,6 +219,12 @@ parser.add_argument( help="path to Rascal model file", required=False, ) +parser.add_argument( + "--ace-model", + type=str, + help="path to ACE model file", + required=False, +) parser.add_argument( "--lambda-interpolate", type=float, diff --git a/emle/emle.py b/emle/emle.py index 2fb99f6..764f9dc 100644 --- a/emle/emle.py +++ b/emle/emle.py @@ -344,6 +344,7 @@ class EMLECalculator: "sander", "sqm", "xtb", + "ace", "external", ] @@ -369,6 +370,7 @@ def __init__( mm_charges=None, deepmd_model=None, rascal_model=None, + ace_model=None, parm7=None, qm_indices=None, sqm_theory="DFTB3", @@ -428,6 +430,10 @@ def __init__( Path to the Rascal model file used to apply delta-learning corrections to the in vacuo energies and gradients computed by the backed. + ace_model : str + Path to the ACE model file to use for in vacuo calculations. This + must be specified if "ace" is the selected backend. + lambda_interpolate : float, [float, float] The value of lambda to use for end-state correction calculations. This must be between 0 and 1, which is used to interpolate between a full MM @@ -732,6 +738,26 @@ def __init__( # Flag that delta-learning corrections will be applied. self._is_delta = True + # Validate and load the ACE model. + if ace_model is not None: + if not isinstance(ace_model, str): + raise TypeError("'ace_model' must be of type 'str'") + + # Convert to an absolute path. + abs_ace_model = os.path.abspath(ace_model) + + # Make sure the model file exists. + if not os.path.isfile(abs_ace_model): + raise IOError(f"Unable to locate ACE model file: '{ace_model}'") + + # Load the model. + try: + import pyjulip + + self._ace_calc = pyjulip.ACE(abs_ace_model) + except: + raise RuntimeError("Unable to create ACE calculator!") + if restart is not None: if not isinstance(restart, bool): raise TypeError("'restart' must be of type 'bool'") @@ -974,6 +1000,7 @@ def __init__( "mm_charges": None if mm_charges is None else self._mm_charges.tolist(), "deepmd_model": deepmd_model, "rascal_model": rascal_model, + "ace_model": ace_model, "parm7": parm7, "qm_indices": None if qm_indices is None else self._qm_indices, "sqm_theory": sqm_theory, @@ -1121,6 +1148,15 @@ def run(self, path=None): "Failed to calculate in vacuo energies using XTB backend!" ) + # ACE. + elif self._backend == "ace": + try: + E_vac, grad_vac = self._run_ace(atoms) + except: + raise RuntimeError( + "Failed to calculate in vacuo energies using ACE backend!" + ) + # External backend. else: try: @@ -2810,6 +2846,50 @@ def _run_xtb(atoms): return energy, gradient + @staticmethod + def _run_ace(atoms): + """ + Internal function to compute in vacuo energies and gradients using + ACE (via PyJulip). + + Parameters + ---------- + + atoms : ase.atoms.Atoms + The atoms in the QM region. + + Returns + ------- + + energy : float + The in vacuo ML energy in Eh. + + gradients : numpy.array + The in vacuo gradient in Eh/Bohr. + """ + + if not isinstance(atoms, ase.Atoms): + raise TypeError("'atoms' must be of type 'ase.atoms.Atoms'") + + # ACE requires periodic box information so we translate the atoms so that + # the lowest (x, y, z) position is zero, then set the cell to the maximum + # position. + atoms.positions -= np.min(atoms.positions, axis=0) + atoms.cell = np.max(atoms.positions, axis=0) + + # Bind the calculator to the atoms. + atoms.calc = self._ace_calc + + # Get the energy and forces in atomic units. + energy = atoms.get_potential_energy() + forces = atoms.get_forces() + + # Convert to Hartree and Eh/Bohr. + energy *= EV_TO_HARTREE + gradient = -forces * EV_TO_HARTREE * BOHR_TO_ANGSTROM + + return energy, gradient + def _run_rascal(self, atoms): """ Internal function to compute delta-learning corrections using Rascal. diff --git a/environment.yaml b/environment.yaml index 5b5e05f..4c384ca 100644 --- a/environment.yaml +++ b/environment.yaml @@ -16,4 +16,6 @@ dependencies: - torchani - xtb-python - pip: + - julia - git+https://github.com/lab-cosmo/librascal.git + - git+https://github.com/casv2/pyjulip.git