From 6ec67e852e2b67f9c53d9a096d70426a25e9a29e Mon Sep 17 00:00:00 2001 From: Ilyes Batatia <48651863+ilyes319@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:38:25 +0000 Subject: [PATCH 1/6] add mace models --- mace.sh | 19 +++ mace/README.md | 39 +++++ mace/install.sh | 49 ++++++ mace/pyproject.toml | 30 ++++ mace/src/maceexttool/__init__.py | 4 + mace/src/maceexttool/calculator.py | 59 +++++++ mace/src/maceexttool/client.py | 69 +++++++++ mace/src/maceexttool/common.py | 229 ++++++++++++++++++++++++++++ mace/src/maceexttool/server.py | 95 ++++++++++++ mace/src/maceexttool/standalone.py | 100 ++++++++++++ mace/test/H2O_goat_extclient.inp | 10 ++ mace/test/HF.engrad | 17 +++ mace/test/HF.xyz | 5 + mace/test/HF_exttool.inp | 6 + mace/test/HF_orca_ext.inp | 9 ++ mace/test/HF_orca_extclient.inp | 9 ++ mace/test/pid.txt | 1 + mace/test/run_tests.sh | 64 ++++++++ mace/test/s.out | 5 + mace/tests/conftest.py | 11 ++ mace/tests/test_mace_exttool.py | 209 +++++++++++++++++++++++++ mace/tests/test_mace_integration.py | 108 +++++++++++++ maceclient.sh | 19 +++ maceserver.sh | 21 +++ 24 files changed, 1187 insertions(+) create mode 100755 mace.sh create mode 100644 mace/README.md create mode 100755 mace/install.sh create mode 100644 mace/pyproject.toml create mode 100644 mace/src/maceexttool/__init__.py create mode 100644 mace/src/maceexttool/calculator.py create mode 100644 mace/src/maceexttool/client.py create mode 100644 mace/src/maceexttool/common.py create mode 100644 mace/src/maceexttool/server.py create mode 100644 mace/src/maceexttool/standalone.py create mode 100644 mace/test/H2O_goat_extclient.inp create mode 100644 mace/test/HF.engrad create mode 100644 mace/test/HF.xyz create mode 100644 mace/test/HF_exttool.inp create mode 100644 mace/test/HF_orca_ext.inp create mode 100644 mace/test/HF_orca_extclient.inp create mode 100644 mace/test/pid.txt create mode 100755 mace/test/run_tests.sh create mode 100644 mace/test/s.out create mode 100644 mace/tests/conftest.py create mode 100644 mace/tests/test_mace_exttool.py create mode 100644 mace/tests/test_mace_integration.py create mode 100755 maceclient.sh create mode 100755 maceserver.sh diff --git a/mace.sh b/mace.sh new file mode 100755 index 0000000..2267401 --- /dev/null +++ b/mace.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +SCRIPT_PATH="$(dirname -- "${BASH_SOURCE[0]}")" +src="${SCRIPT_PATH}/mace" +venv="$src/.venv" + +need_setup=0 +if [ ! -d "$venv" ]; then + need_setup=1 +elif [ ! -x "$venv/bin/maceexttool" ]; then + need_setup=1 +fi + +if [ $need_setup -eq 1 ]; then + "$src"/install.sh +fi + +source "$venv"/bin/activate +maceexttool "$@" diff --git a/mace/README.md b/mace/README.md new file mode 100644 index 0000000..1cbaabd --- /dev/null +++ b/mace/README.md @@ -0,0 +1,39 @@ +# MACE ExtTool for ORCA + +Wrapper around MACE calculators to use ORCA's `otool_external` interface. Mirrors the UMA tool interface with standalone and server–client modes. + +- Suites: `mace-mp` (Materials Project) and `mace-omol` (OMOL foundation model) +- Extras: `dispersion` (MP only), `default_dtype` (`float32` or `float64`), optional `device`, and `head` for advanced MP heads. + +## Quick start + +- Standalone (single call per ORCA task): + - `./mace.sh -s mp -m medium-mpa-0 your_job_EXT.extinp.tmp` + - `./mace.sh -s omol your_job_EXT.extinp.tmp` + +- Server–client (faster for many calls in one ORCA job): + - Start: `./maceserver.sh -s mp -m medium-mpa-0 -n 2 -b 127.0.0.1:8888` + - Use in ORCA input: `progext ".../maceclient.sh"` (ORCA passes the temp input filepath) + +## Arguments + +Common: +- `-s, --suite`: `mp` or `omol` (default: `omol`) +- `-m, --model`: model spec or local path (MP: e.g. `medium-mpa-0`, `medium`, `small`; OMOL: `extra_large` or path) +- `--default-dtype`: `float32` (MD speed) or `float64` (opt accuracy) +- `--device`: `cpu`, `cuda`, etc. + +MP only: +- `--dispersion`: enable D3 dispersion (off by default) +- `--damping`, `--dispersion-xc`, `--dispersion-cutoff` advanced dispersion controls +- `--head`: MACE head selection for multi-head variants + +Server: +- `-b, --bind`: `host:port` (default: `127.0.0.1:8888`) +- `-n, --nthreads`: number of threads per server + +## Install + +- `cd mace && ./install.sh` +- This installs a venv, the wrapper, and tries to `pip install -e ../../mace` if the local MACE repo exists. Otherwise it relies on `mace-torch`. + diff --git a/mace/install.sh b/mace/install.sh new file mode 100755 index 0000000..7e6153a --- /dev/null +++ b/mace/install.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +#!/usr/bin/env bash +# Get the location of this script +SCRIPT_PATH="$(dirname -- "${BASH_SOURCE[0]}")" +# Name of the virtual environment +VENV=.venv + +set -e + +cd "$SCRIPT_PATH" + +choose_python() { + for exe in python3.12 python3.11 python3.10 python3 python; do + if command -v "$exe" >/dev/null 2>&1; then + if "$exe" -c 'import sys; raise SystemExit(int(sys.version_info< (3,10)))'; then + echo "$exe"; return 0 + fi + fi + done + echo ""; return 1 +} + +PYEXE="$(choose_python)" || { + echo "No suitable Python >=3.10 found to create venv. Please install Python 3.10+." >&2 + exit 1 +} +echo "Using Python interpreter: $PYEXE" + +# Reset venv +rm -rf "$VENV" 2>/dev/null || true +"$PYEXE" -m venv "$VENV" +source "$VENV/bin/activate" + +# Ensure modern pip/setuptools for editable installs with pyproject +python -m pip install --upgrade pip setuptools wheel + +# Install wrapper +pip install -e . + +# Try to install local MACE if available; otherwise rely on dependency +LOCAL_MACE_DIR="$(cd "$SCRIPT_PATH/../.." && pwd)/mace" +if [ -d "$LOCAL_MACE_DIR" ]; then + echo "Installing local MACE from $LOCAL_MACE_DIR" + pip install -e "$LOCAL_MACE_DIR" || echo "Warning: failed to install local MACE; ensure mace-torch is available." +else + echo "Local MACE repo not found. Using mace-torch from PyPI if available." +fi + +echo "Setup complete. Activate venv with: source $SCRIPT_PATH/$VENV/bin/activate" diff --git a/mace/pyproject.toml b/mace/pyproject.toml new file mode 100644 index 0000000..1be550a --- /dev/null +++ b/mace/pyproject.toml @@ -0,0 +1,30 @@ +[project] +name = "maceexttool" +version = "0.1.0" +description = "MACE wrapper for ORCA's ExtTool interface (standalone/server-client)" +requires-python = ">=3.10" +dependencies = [ + "ase>=3.22.1", + "Flask>=3,<4", + "numpy<3.0.0,>=1.24", + "requests>=2,<3", + "waitress>=3,<4", + # Prefer local install, but allow PyPI fallback if available + "mace-torch>=0.3.14", +] + +[project.scripts] +maceexttool = "maceexttool.standalone:main" +maceserver = "maceexttool.server:main" +maceclient = "maceexttool.client:main" + +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] + diff --git a/mace/src/maceexttool/__init__.py b/mace/src/maceexttool/__init__.py new file mode 100644 index 0000000..f1f235b --- /dev/null +++ b/mace/src/maceexttool/__init__.py @@ -0,0 +1,4 @@ +__all__ = [ + "common", +] + diff --git a/mace/src/maceexttool/calculator.py b/mace/src/maceexttool/calculator.py new file mode 100644 index 0000000..28eb97c --- /dev/null +++ b/mace/src/maceexttool/calculator.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from typing import Optional + +from ase import Atoms + + +def init( + suite: str, + model: Optional[str] = None, + *, + device: str = "", + default_dtype: Optional[str] = None, + dispersion: bool = False, + damping: str = "bj", + dispersion_xc: str = "pbe", + dispersion_cutoff: Optional[float] = None, + head: Optional[str] = None, +): + """Initialize a MACE calculator based on suite and options. + + Returns an ASE calculator compatible object which can be attached to Atoms. + """ + # Lazy import to avoid heavy deps at import-time + from mace.calculators.foundations_models import mace_mp, mace_omol + from ase import units + + if suite == "mp": + kwargs = dict( + model=model, + device=device or ("cuda" if _torch_cuda_available() else "cpu"), + default_dtype=default_dtype or "float32", + dispersion=dispersion, + damping=damping, + dispersion_xc=dispersion_xc, + dispersion_cutoff=(dispersion_cutoff if dispersion_cutoff is not None else 40.0 * units.Bohr), + ) + if head: + kwargs["head"] = head + calc = mace_mp(**kwargs) + return calc + elif suite == "omol": + calc = mace_omol( + model=model, + device=device or ("cuda" if _torch_cuda_available() else "cpu"), + default_dtype=default_dtype or "float64", + ) + return calc + else: + raise ValueError(f"Unknown suite: {suite}. Expected 'mp' or 'omol'.") + + +def _torch_cuda_available() -> bool: + try: + import torch # type: ignore + + return torch.cuda.is_available() + except Exception: + return False diff --git a/mace/src/maceexttool/client.py b/mace/src/maceexttool/client.py new file mode 100644 index 0000000..1b4edc1 --- /dev/null +++ b/mace/src/maceexttool/client.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +import json +import sys +from argparse import Namespace +from typing import Tuple + +import requests + +from maceexttool import common + + +def submit_mace(server_url: str, + atom_types: list[str], + coordinates: list[tuple[float, float, float]], + charge: int, + mult: int, + nthreads: int) -> Tuple[float, list[float]]: + payload = { + "atom_types": atom_types, + "coordinates": coordinates, + "charge": charge, + "mult": mult, + "nthreads": nthreads, + } + + try: + response = requests.post('http://' + server_url + "/calculate", json=payload) + response.raise_for_status() + except requests.exceptions.ConnectionError: + print("The server is probably not running.") + print("Please start the server with the maceserver.sh script.") + raise + except requests.exceptions.Timeout as timeout_err: + print("Request to MACE server timed out:", timeout_err) + raise + + data = response.json() + return data['energy'], data['gradient'] + + +def run(arglist: list[str]): + args: Namespace = common.cli_parse(arglist, mode=common.RunMode.Client) + + # read ORCA-generated input + xyzname, charge, mult, ncores, dograd = common.read_input(args.inputfile) + basename = xyzname.removesuffix(".xyz") + orca_engrad = basename + ".engrad" + + atom_types, coordinates = common.read_xyzfile(xyzname) + natoms = len(atom_types) + + energy, gradient = submit_mace(server_url=args.bind, + atom_types=atom_types, + coordinates=coordinates, + charge=charge, + mult=mult, + nthreads=ncores) + + common.write_engrad(orca_engrad, natoms, energy, dograd, gradient) + + +def main(): + run(sys.argv[1:]) + + +if __name__ == '__main__': + main() + diff --git a/mace/src/maceexttool/common.py b/mace/src/maceexttool/common.py new file mode 100644 index 0000000..b01738c --- /dev/null +++ b/mace/src/maceexttool/common.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import subprocess +from enum import StrEnum, auto +from argparse import ArgumentParser, Namespace +from pathlib import Path +from typing import Iterable +import os +import socket +import numpy as np +from ase import Atoms + + +# Energy and length conversion to atomic units (same as UMA) +ENERGY_CONVERSION = {"eV": 27.21138625} +LENGTH_CONVERSION = {"Ang": 0.529177210903} + + +def strip_comments(s: str) -> str: + return s.split("#")[0].strip() + + +def enforce_path_object(fname: str | Path) -> Path: + if isinstance(fname, str): + return Path(fname) + elif isinstance(fname, Path): + return fname + else: + raise TypeError("Input must be a string or a Path object.") + + +def read_input(inpfile: str | Path) -> tuple[str, int, int, int, bool]: + inpfile = enforce_path_object(inpfile) + with inpfile.open() as f: + xyzname = strip_comments(f.readline()) + charge = int(strip_comments(f.readline())) + mult = int(strip_comments(f.readline())) + ncores = int(strip_comments(f.readline())) + dograd = bool(int(strip_comments(f.readline()))) + return xyzname, charge, mult, ncores, dograd + + +def write_engrad( + outfile: str | Path, + natoms: int, + energy: float, + dograd: bool, + gradient: Iterable[float] = None, +) -> None: + outfile = enforce_path_object(outfile) + with outfile.open("w") as f: + output = "#\n" + output += "# Number of atoms\n" + output += "#\n" + output += f"{natoms}\n" + output += "#\n" + output += "# Total energy [Eh]\n" + output += "#\n" + output += f"{energy:.12e}\n" + if dograd: + output += "#\n" + output += "# Gradient [Eh/Bohr] A1X, A1Y, A1Z, A2X, ...\n" + output += "#\n" + output += "\n".join(f"{g: .12e}" for g in gradient) + "\n" + f.write(output) + + +def run_command(command: str | Path, outname: str | Path, *args: tuple[str, ...]) -> None: + command = enforce_path_object(command) + outname = enforce_path_object(outname) + with outname.open("w") as of: + try: + subprocess.run( + [command] + list(args), stdout=of, stderr=subprocess.STDOUT, check=True + ) + except subprocess.CalledProcessError as err: + print(err) + exit(err.returncode) + + +def clean_output(outfile: str | Path, namespace: str) -> None: + outfile = enforce_path_object(outfile) + with outfile.open() as f: + for line in f: + print(line, end="") + for f in Path(".").glob(namespace + "*"): + f.unlink() + + +def read_xyzfile(xyzname: str | Path) -> tuple[list[str], list[tuple[float, float, float]]]: + atom_types: list[str] = [] + coordinates: list[tuple[float, float, float]] = [] + xyzname = enforce_path_object(xyzname) + with xyzname.open() as xyzf: + natoms = int(xyzf.readline().strip()) + xyzf.readline() + for _ in range(natoms): + line = xyzf.readline() + if not line: + break + parts = line.split() + atom_types.append(parts[0]) + coords = tuple(float(c) for c in parts[1:4]) + coordinates.append(coords) + return atom_types, coordinates + + +def process_output(atoms: Atoms) -> tuple[float, list[float]]: + """Convert ASE outputs (eV, Ang) to ORCA units (Eh, Eh/Bohr).""" + energy = atoms.get_potential_energy() / ENERGY_CONVERSION["eV"] + gradient: list[float] = [] + try: + forces = atoms.get_forces() + fac = -LENGTH_CONVERSION["Ang"] / ENERGY_CONVERSION["eV"] + gradient = (fac * np.asarray(forces)).flatten().tolist() + except Exception: + pass + return energy, gradient + + +class RunMode(StrEnum): + Server = auto() + Client = auto() + Standalone = auto() + + +ProgName = { + RunMode.Server: "maceserver", + RunMode.Client: "maceclient", + RunMode.Standalone: "maceexttool", +} + + +def is_port_available(host: str, port: int) -> bool: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + result = sock.connect_ex((host, port)) + return result != 0 + + +def get_free_port() -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(("", 0)) + return sock.getsockname()[1] + + +def cli_parse(args: list[str], mode: RunMode) -> Namespace: + """Parse CLI for MACE wrapper; options differ slightly by mode.""" + parser = ArgumentParser( + prog=ProgName[mode], + description=f'ORCA "external tool" interface for MACE calculations ({mode} mode)' + ) + + if mode in (RunMode.Standalone, RunMode.Client): + parser.add_argument("inputfile", help="ORCA-generated input file.") + + # Common MACE options (for Standalone and Server) + if mode in (RunMode.Standalone, RunMode.Server): + parser.add_argument( + "-s", "--suite", + choices=["mp", "omol", "mace-mp", "mace-omol"], + default="omol", + help="Select MACE suite: mp/mace-mp or omol/mace-omol. Default: omol" + ) + parser.add_argument( + "-m", "--model", + type=str, + default=None, + help="Model spec or local path. MP: small/medium/large/medium-mpa-0/... OMOL: extra_large or path." + ) + parser.add_argument( + "--default-dtype", + choices=["float32", "float64"], + default=None, + help="Default float precision (recommended: float64 for opt, float32 for MD)." + ) + parser.add_argument( + "--device", + type=str, + default="", + help="Device string for torch/ASE calculator (e.g., cuda, cpu)." + ) + # MP-specific extras + parser.add_argument( + "--dispersion", + action="store_true", + help="Enable D3 dispersion (MP suite only)." + ) + parser.add_argument("--damping", type=str, default="bj", + help="D3 damping (zero,bj,zerom,bjm). MP only.") + parser.add_argument("--dispersion-xc", type=str, default="pbe", + help="XC functional for D3. MP only.") + parser.add_argument("--dispersion-cutoff", type=float, default=None, + help="Cutoff radius for D3 in Bohr (default: 40 Bohr). MP only.") + parser.add_argument("--head", type=str, default=None, + help="Advanced: select MACE head (MP only).") + + if mode in RunMode.Server: + parser.add_argument( + "-b", "--bind", metavar="hostname:port", default="127.0.0.1:8888", + help="Server bind address and port. Default: 127.0.0.1:8888." + ) + if mode is RunMode.Client: + default_bind = os.getenv("MACE_BIND", "127.0.0.1:8888") + parser.add_argument( + "-b", "--bind", metavar="hostname:port", default=default_bind, + help="Server bind address and port." + ) + if mode is RunMode.Server: + parser.add_argument("-n", "--nthreads", metavar="N", type=int, default=1, + help="Number of threads to use. Default: 1") + + parsed = parser.parse_args(args) + + if mode is RunMode.Server: + try: + host, port = parsed.bind.split(":") + port = int(port) + except ValueError: + parser.error("Invalid --bind format. Use host:port") + if not is_port_available(host, port): + print(f"Port {port} on {host} is already in use. Selecting a free one...") + port = get_free_port() + parsed.bind = f"{host}:{port}" + os.system(f"export MACE_BIND={host}:{port}") + print(f"Using new port: {parsed.bind}") + + return parsed diff --git a/mace/src/maceexttool/server.py b/mace/src/maceexttool/server.py new file mode 100644 index 0000000..8422616 --- /dev/null +++ b/mace/src/maceexttool/server.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +import logging +import threading +import sys +from typing import Callable + +import torch +from flask import Flask, request, jsonify +from ase import Atoms + +from maceexttool import common, calculator + +app = Flask('maceserver') + +# Global configuration chosen at server startup +_suite: str = 'omol' +_model: str | None = None +_default_dtype: str | None = None +_device: str = '' +_dispersion: bool = False +_damping: str = 'bj' +_dispersion_xc: str = 'pbe' +_dispersion_cutoff: float | None = None +_head: str | None = None + +# One calculator per server thread +calculators: dict[int | Callable] = {} + + +@app.route('/calculate', methods=['POST']) +def run_mace(): + input = request.get_json() + + atoms = Atoms(symbols=input["atom_types"], positions=input["coordinates"]) + atoms.info = {"charge": input["charge"], "spin": input["mult"]} + + nthreads = input.get('nthreads', 1) + torch.set_num_threads(nthreads) + + thread_id = threading.get_ident() + global calculators + if thread_id not in calculators: + calculators[thread_id] = calculator.init( + suite=_suite, + model=_model, + device=_device, + default_dtype=_default_dtype, + dispersion=_dispersion, + damping=_damping, + dispersion_xc=_dispersion_xc, + dispersion_cutoff=_dispersion_cutoff, + head=_head, + ) + calc = calculators[thread_id] + atoms.calc = calc + + energy, gradient = common.process_output(atoms) + return jsonify({'energy': energy, 'gradient': gradient}) + + +def run(arglist: list[str]): + args = common.cli_parse(arglist, mode=common.RunMode.Server) + + global _suite, _model, _default_dtype, _device, _dispersion, _damping, _dispersion_xc, _dispersion_cutoff, _head + _suite = args.suite + if _suite.startswith("mace-"): + _suite = _suite.split("-", 1)[1] + _model = args.model + _default_dtype = args.default_dtype or ("float64" if _suite == "omol" else "float32") + _device = args.device + _dispersion = bool(args.dispersion) + _damping = args.damping + _dispersion_xc = args.dispersion_xc + _dispersion_cutoff = args.dispersion_cutoff + _head = args.head + + # Try waitress; fallback to Flask dev server for testing if waitress missing + try: + import waitress # type: ignore + logger = logging.getLogger('waitress') + logger.setLevel(logging.INFO) + waitress.serve(app, listen=args.bind, threads=args.nthreads) + except Exception: + host, port = args.bind.split(":") + port = int(port) + app.run(host=host, port=port, threaded=True) + + +def main(): + run(sys.argv[1:]) + + +if __name__ == '__main__': + main() diff --git a/mace/src/maceexttool/standalone.py b/mace/src/maceexttool/standalone.py new file mode 100644 index 0000000..35de2b1 --- /dev/null +++ b/mace/src/maceexttool/standalone.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import sys +import time + +import torch +from ase import Atoms + +from maceexttool import common, calculator + + +def run_mace( + atom_types: list[str], + coordinates: list[tuple[float, float, float]], + charge: int, + mult: int, + suite: str, + model: str | None, + dograd: bool, + nthreads: int, + default_dtype: str | None, + device: str, + dispersion: bool, + damping: str, + dispersion_xc: str, + dispersion_cutoff: float | None, + head: str | None, +) -> tuple[float, list[float]]: + """Run a MACE calculation and return energy and gradient in ORCA units.""" + calc = calculator.init( + suite=suite, + model=model, + device=device, + default_dtype=default_dtype, + dispersion=dispersion, + damping=damping, + dispersion_xc=dispersion_xc, + dispersion_cutoff=dispersion_cutoff, + head=head, + ) + + torch.set_num_threads(nthreads) + + atoms = Atoms(symbols=atom_types, positions=coordinates) + atoms.info = {"charge": charge, "spin": mult} + atoms.calc = calc + + return common.process_output(atoms) + + +def run(arglist: list[str]): + args = common.cli_parse(arglist, mode=common.RunMode.Standalone) + + # Canonicalize suite and set defaults + suite = args.suite + if suite.startswith("mace-"): + suite = suite.split("-", 1)[1] + default_dtype = args.default_dtype + if default_dtype is None: + default_dtype = "float64" if suite == "omol" else "float32" + + # read ORCA input + xyzname, charge, mult, ncores, dograd = common.read_input(args.inputfile) + basename = xyzname.removesuffix(".xyz") + orca_engrad = basename + ".engrad" + + atom_types, coordinates = common.read_xyzfile(xyzname) + natoms = len(atom_types) + + start_time = time.perf_counter() + energy, gradient = run_mace( + atom_types=atom_types, + coordinates=coordinates, + charge=charge, + mult=mult, + suite=suite, + model=args.model, + dograd=dograd, + nthreads=ncores, + default_dtype=default_dtype, + device=args.device, + dispersion=args.dispersion, + damping=args.damping, + dispersion_xc=args.dispersion_xc, + dispersion_cutoff=args.dispersion_cutoff, + head=args.head, + ) + + common.write_engrad(orca_engrad, natoms, energy, dograd, gradient) + print("Total time: {:6.3f} seconds".format(time.perf_counter() - start_time)) + + +def main(): + run(sys.argv[1:]) + + +if __name__ == "__main__": + main() diff --git a/mace/test/H2O_goat_extclient.inp b/mace/test/H2O_goat_extclient.inp new file mode 100644 index 0000000..d20336d --- /dev/null +++ b/mace/test/H2O_goat_extclient.inp @@ -0,0 +1,10 @@ +! extopt goat pal4 +%method + progext "../../maceclient.sh" +end +*xyz 0 1 + O 0.000000 0.000000 0.000000 + H 0.000000 0.000000 0.960000 + H 0.000000 0.750000 -0.240000 +* + diff --git a/mace/test/HF.engrad b/mace/test/HF.engrad new file mode 100644 index 0000000..daeb669 --- /dev/null +++ b/mace/test/HF.engrad @@ -0,0 +1,17 @@ +# +# Number of atoms +# +2 +# +# Total energy [Eh] +# +-1.004614677992e+02 +# +# Gradient [Eh/Bohr] A1X, A1Y, A1Z, A2X, ... +# +-0.000000000000e+00 +-0.000000000000e+00 + 2.938162452069e-02 +-0.000000000000e+00 +-0.000000000000e+00 +-2.938162452069e-02 diff --git a/mace/test/HF.xyz b/mace/test/HF.xyz new file mode 100644 index 0000000..e6c48e8 --- /dev/null +++ b/mace/test/HF.xyz @@ -0,0 +1,5 @@ +2 + +H 0 0 0 +F 0 0 0.9 + diff --git a/mace/test/HF_exttool.inp b/mace/test/HF_exttool.inp new file mode 100644 index 0000000..b9dd279 --- /dev/null +++ b/mace/test/HF_exttool.inp @@ -0,0 +1,6 @@ +HF.xyz +0 +1 +1 +1 + diff --git a/mace/test/HF_orca_ext.inp b/mace/test/HF_orca_ext.inp new file mode 100644 index 0000000..6c21cb7 --- /dev/null +++ b/mace/test/HF_orca_ext.inp @@ -0,0 +1,9 @@ +! extopt opt +%method + progext "../../mace.sh" +end +*xyz 0 1 + H 0 0 0 + F 0 0 0.9 +* + diff --git a/mace/test/HF_orca_extclient.inp b/mace/test/HF_orca_extclient.inp new file mode 100644 index 0000000..ab127ef --- /dev/null +++ b/mace/test/HF_orca_extclient.inp @@ -0,0 +1,9 @@ +! extopt opt +%method + progext "../../maceclient.sh" +end +*xyz 0 1 + H 0 0 0 + F 0 0 0.9 +* + diff --git a/mace/test/pid.txt b/mace/test/pid.txt new file mode 100644 index 0000000..3a0fbc2 --- /dev/null +++ b/mace/test/pid.txt @@ -0,0 +1 @@ +25378 diff --git a/mace/test/run_tests.sh b/mace/test/run_tests.sh new file mode 100755 index 0000000..609ee37 --- /dev/null +++ b/mace/test/run_tests.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# Direct execution of standalone wrapper script +echo "These tests might take a few minutes." +echo "To check the progress, please see the respective outputs." +cmd="../../mace.sh HF_exttool.inp > HF_exttool.out" +echo "Test command: ${cmd}" +eval $cmd + +# Check if ORCA is available; if not, skip ORCA-based tests +if ! command -v orca >/dev/null 2>&1 ; then + echo "ORCA not found in PATH. Skipping ORCA-based tests (orca_ext, client, GOAT)." + exit 0 +fi + +# Execution of standalone wrapper script via ORCA optimization +cmd="$(which orca) HF_orca_ext.inp > HF_orca_ext.out" +echo "Test command: ${cmd}" +eval $cmd + +# Server/client test via ORCA +# - function that kills the server on exit +killserver(){ + cmd="killall maceserver" + echo "Stopping server: ${cmd}" + eval $cmd +} +trap "killserver; exit" INT TERM EXIT +# - start the server +sf=HF_orca_extclient.serverout +cmd="../../maceserver.sh > $sf 2>&1 &" +echo "Starting server: ${cmd}" +eval $cmd +# - initialize the output file +of=HF_orca_extclient.out +> $of +# - wait for the server to start +WAITED=0 +while [ -z "$(grep -E "Serving|Running on" $sf)" ]; do echo "Waiting for server" >> $of; sleep 1s; WAITED=$((WAITED+1)); if [ $WAITED -gt 30 ]; then echo "Timeout waiting for server" >> $of; break; fi; done +# - start the ORCA job +cmd="$(which orca) HF_orca_extclient.inp >> $of" +echo "Test command: ${cmd}" +eval $cmd + +# stop the server between tests +killall maceserver || true + +# Parallel server/client GOAT test +# - start the server (4 threads) +sf=H2O_goat_extclient.serverout +cmd="../../maceserver.sh -n 4 > $sf 2>&1 &" +echo "Starting server: ${cmd}" +eval $cmd +# - initialize the output file +of=H2O_goat_extclient.out +> $of +# - wait for the server to start +WAITED=0 +while [ -z "$(grep -E "Serving|Running on" $sf)" ]; do echo "Waiting for server" >> $of; sleep 1s; WAITED=$((WAITED+1)); if [ $WAITED -gt 30 ]; then echo "Timeout waiting for server" >> $of; break; fi; done +# - start the ORCA job +cmd="$(which orca) H2O_goat_extclient.inp >> $of" +echo "Test command: ${cmd}" +eval $cmd + diff --git a/mace/test/s.out b/mace/test/s.out new file mode 100644 index 0000000..92a9125 --- /dev/null +++ b/mace/test/s.out @@ -0,0 +1,5 @@ +MACESERVER_PID: 25381 +INFO:waitress:Serving on http://127.0.0.1:64207/Users/Lilyes/Documents/GitHub/orca-external-tools/mace/.venv/lib/python3.12/site-packages/e3nn/o3/_wigner.py:10: UserWarning: Environment variable TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD detected, since the`weights_only` argument was not explicitly passed to `torch.load`, forcing weights_only=False. + _Jd, _W3j_flat, _W3j_indices = torch.load(os.path.join(os.path.dirname(__file__), 'constants.pt')) +/Users/Lilyes/Documents/GitHub/orca-external-tools/mace/.venv/lib/python3.12/site-packages/mace/calculators/mace.py:197: UserWarning: Environment variable TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD detected, since the`weights_only` argument was not explicitly passed to `torch.load`, forcing weights_only=False. + torch.load(f=model_path, map_location=device) diff --git a/mace/tests/conftest.py b/mace/tests/conftest.py new file mode 100644 index 0000000..38fcb0e --- /dev/null +++ b/mace/tests/conftest.py @@ -0,0 +1,11 @@ +import os +import sys +from pathlib import Path + + +# Ensure local src is importable as maceexttool +SRC = Path(__file__).resolve().parents[1] / "src" +if str(SRC) not in sys.path: + sys.path.insert(0, str(SRC)) + +# No dummy calculator: use real MACE in integration tests diff --git a/mace/tests/test_mace_exttool.py b/mace/tests/test_mace_exttool.py new file mode 100644 index 0000000..e4e203a --- /dev/null +++ b/mace/tests/test_mace_exttool.py @@ -0,0 +1,209 @@ +import io +import os +from pathlib import Path + +import numpy as np +import pytest +from ase import Atoms +from ase.calculators.calculator import Calculator, all_changes + + +EV_TO_EH = 1.0 / 27.21138625 +ANG_TO_BOHR = 0.529177210903 +FORCE_TO_GRAD = -ANG_TO_BOHR / 27.21138625 + + +class DummyCalc(Calculator): + implemented_properties = ["energy", "forces"] + + def __init__(self, energy_eV: float, forces_eVA: np.ndarray): + super().__init__() + self._energy = float(energy_eV) + self._forces = np.array(forces_eVA, dtype=float) + + def calculate(self, atoms=None, properties=("energy", "forces"), system_changes=all_changes): + super().calculate(atoms, properties, system_changes) + self.results["energy"] = self._energy + self.results["forces"] = self._forces + + +def write_extinp(tmpdir: Path, name: str, symbols: list[str], coords: list[tuple[float, float, float]], charge=0, mult=1, ncores=1, dograd=1) -> Path: + xyz = tmpdir / f"{name}_EXT.xyz" + with xyz.open("w") as fh: + fh.write(f"{len(symbols)}\n") + fh.write("generated by test\n") + for s, (x, y, z) in zip(symbols, coords): + fh.write(f"{s} {x} {y} {z}\n") + + extinp = tmpdir / f"{name}_EXT.extinp.tmp" + with extinp.open("w") as fh: + fh.write(str(xyz) + "\n") + fh.write(str(charge) + "\n") + fh.write(str(mult) + "\n") + fh.write(str(ncores) + "\n") + fh.write(str(dograd) + "\n") + return extinp + + +def read_engrad(path: Path): + with path.open() as fh: + lines = [l.strip() for l in fh.readlines()] + # lines structure: comments + natoms + comments + energy + [grad] + # find the first numeric after the "Total energy" header + energy_idx = None + for i, l in enumerate(lines): + if l.startswith("# Total energy"): + energy_idx = i + 2 # next non-comment is energy line + break + assert energy_idx is not None + energy = float(lines[energy_idx]) + # gradient lines are all numeric after the gradient header + grad = [] + grad_header_idx = None + for i, l in enumerate(lines): + if l.startswith("# Gradient"): + grad_header_idx = i + 2 + break + if grad_header_idx is not None: + for l in lines[grad_header_idx:]: + if not l or l.startswith("#"): + break + grad.append(float(l)) + return energy, grad + + +def test_standalone_omol_basic(tmp_path, monkeypatch): + from maceexttool import standalone + + # One H2 molecule; energy 1 eV, zero forces + extinp = write_extinp(tmp_path, "h2", ["H", "H"], [(0, 0, 0), (0, 0, 0.75)]) + + def fake_init(**kwargs): + # energy in eV; forces in eV/Ang + return DummyCalc(energy_eV=1.0, forces_eVA=np.zeros((2, 3))) + + monkeypatch.setattr("maceexttool.calculator.init", lambda **kwargs: fake_init(**kwargs)) + + standalone.run(["-s", "omol", str(extinp)]) + + engrad = Path(str(extinp).replace(".extinp.tmp", ".engrad")).with_suffix(".engrad") + energy, grad = read_engrad(engrad) + assert pytest.approx(1.0 * EV_TO_EH, rel=1e-8) == energy + assert all(abs(g) < 1e-15 for g in grad) + + +def test_standalone_mp_extras_and_defaults(tmp_path, monkeypatch): + from maceexttool import standalone + + extinp = write_extinp(tmp_path, "h1", ["H"], [(0, 0, 0)]) + + calls = {} + + def rec_init(**kwargs): + calls.update(kwargs) + # one-atom, unit forces + return DummyCalc(energy_eV=2.0, forces_eVA=np.ones((1, 3))) + + monkeypatch.setattr("maceexttool.calculator.init", lambda **kwargs: rec_init(**kwargs)) + + standalone.run(["-s", "mp", "--dispersion", "--head", "mh0", str(extinp)]) + + # defaults for mp: float32 if not provided + assert calls.get("default_dtype") == "float32" + assert calls.get("dispersion") is True + assert calls.get("head") == "mh0" + + engrad = Path(str(extinp).replace(".extinp.tmp", ".engrad")).with_suffix(".engrad") + energy, grad = read_engrad(engrad) + assert pytest.approx(2.0 * EV_TO_EH, rel=1e-8) == energy + # gradient per component should be FORCE_TO_GRAD + assert len(grad) == 3 + for g in grad: + assert pytest.approx(FORCE_TO_GRAD, rel=1e-8) == g + + +def test_suite_synonyms_are_normalized(tmp_path, monkeypatch): + from maceexttool import standalone + + extinp = write_extinp(tmp_path, "w", ["H"], [(0, 0, 0)]) + + seen = {} + + def check_suite(**kwargs): + seen["suite"] = kwargs.get("suite") + return DummyCalc(energy_eV=0.0, forces_eVA=np.zeros((1, 3))) + + monkeypatch.setattr("maceexttool.calculator.init", lambda **kwargs: check_suite(**kwargs)) + standalone.run(["-s", "mace-omol", str(extinp)]) + assert seen.get("suite") == "omol" + + +def test_server_route_with_dummy_calc(monkeypatch): + import json + from maceexttool import server + + server._suite = "omol" + server._model = None + server._default_dtype = "float64" + server._device = "" + server._dispersion = False + server._head = None + server.calculators.clear() + + monkeypatch.setattr( + "maceexttool.calculator.init", + lambda **kwargs: DummyCalc(energy_eV=3.0, forces_eVA=np.zeros((2, 3))), + ) + + client = server.app.test_client() + payload = { + "atom_types": ["H", "H"], + "coordinates": [[0, 0, 0], [0, 0, 0.74]], + "charge": 0, + "mult": 1, + "nthreads": 1, + } + resp = client.post("/calculate", json=payload) + assert resp.status_code == 200 + data = resp.get_json() + assert pytest.approx(3.0 * EV_TO_EH, rel=1e-8) == data["energy"] + assert all(abs(g) < 1e-15 for g in data["gradient"]) # zero forces + + +def test_client_writes_engrad_via_stubbed_requests(tmp_path, monkeypatch): + from maceexttool import client, server + + # Prepare ORCA input + extinp = write_extinp(tmp_path, "h2c", ["H", "H"], [(0, 0, 0), (0, 0, 0.74)]) + + # stub requests.post to route to Flask test client + flask_client = server.app.test_client() + def post_stub(url, json=None, **kwargs): # noqa: A002 + # ignore url, directly call app + resp = flask_client.post("/calculate", json=json) + # mimic requests.Response subset + class R: + def __init__(self, resp): + self._resp = resp + self.status_code = resp.status_code + def raise_for_status(self): + if not (200 <= self.status_code < 300): + raise RuntimeError("HTTP error") + def json(self): + return self._resp.get_json() + return R(resp) + + # route server to a dummy calc + server.calculators.clear() + monkeypatch.setattr( + "maceexttool.calculator.init", + lambda **kwargs: DummyCalc(energy_eV=4.0, forces_eVA=np.zeros((2, 3))), + ) + monkeypatch.setattr("maceexttool.client.requests.post", post_stub) + + client.run([str(extinp)]) + + engrad = Path(str(extinp).replace(".extinp.tmp", ".engrad")).with_suffix(".engrad") + energy, grad = read_engrad(engrad) + assert pytest.approx(4.0 * EV_TO_EH, rel=1e-8) == energy + assert all(abs(g) < 1e-15 for g in grad) diff --git a/mace/tests/test_mace_integration.py b/mace/tests/test_mace_integration.py new file mode 100644 index 0000000..0c7d808 --- /dev/null +++ b/mace/tests/test_mace_integration.py @@ -0,0 +1,108 @@ +import os +import socket +import subprocess +import sys +import time +from pathlib import Path + +import pytest + + +def _free_port(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("", 0)) + return s.getsockname()[1] + + +def _write_extinp(tmp: Path, name: str, xyz_content: str, charge=0, mult=1, ncores=1, dograd=1): + xyz_path = tmp / f"{name}_EXT.xyz" + xyz_path.write_text(xyz_content) + ext = tmp / f"{name}_EXT.extinp.tmp" + ext.write_text("\n".join([ + str(xyz_path), + str(charge), + str(mult), + str(ncores), + str(dograd), + ]) + "\n") + return ext + + +def test_integration_standalone_cli(tmp_path, monkeypatch): + # Prepare simple H2 in XYZ + xyz = """2 +H2 +H 0 0 0 +H 0 0 0.75 +""" + ext = _write_extinp(tmp_path, "h2_cli", xyz) + + env = os.environ.copy() + src_path = Path(__file__).resolve().parents[1] / "src" + mace_repo = Path(__file__).resolve().parents[3] / "mace" + env["PYTHONPATH"] = os.pathsep.join([str(src_path), str(mace_repo), env.get("PYTHONPATH", "")]) + # call entry module instead of venv shell script + cmd = [sys.executable, "-m", "maceexttool.standalone", "-s", "omol", "-m", "extra_large", "--device", "cpu", "--default-dtype", "float64", str(ext)] + cp = subprocess.run(cmd, cwd=tmp_path, env=env, capture_output=True, text=True, timeout=600) + assert cp.returncode == 0, cp.stderr + + engrad = Path(str(ext).replace(".extinp.tmp", ".engrad")).with_suffix(".engrad") + assert engrad.exists() + # sanity: energy is written, gradient lines exist + out = engrad.read_text() + assert "Total energy" in out + + +def test_integration_server_client_cli(tmp_path): + # simple water molecule + xyz = """3 +H2O +O 0.0 0.0 0.0 +H 0.0 0.0 0.96 +H 0.0 0.75 -0.24 +""" + ext = _write_extinp(tmp_path, "h2o_cli", xyz) + + env = os.environ.copy() + src_path = Path(__file__).resolve().parents[1] / "src" + mace_repo = Path(__file__).resolve().parents[3] / "mace" + env["PYTHONPATH"] = os.pathsep.join([str(src_path), str(mace_repo), env.get("PYTHONPATH", "")]) + + port = _free_port() + bind = f"127.0.0.1:{port}" + + # Start server + server_cmd = [sys.executable, "-m", "maceexttool.server", "-s", "omol", "-m", "extra_large", "--device", "cpu", "-b", bind] + server = subprocess.Popen(server_cmd, cwd=tmp_path, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + + # Wait for port to accept connections + ready = False + t0 = time.time() + while time.time() - t0 < 60: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.settimeout(0.5) + try: + sock.connect(("127.0.0.1", port)) + ready = True + break + except OSError: + time.sleep(0.1) + continue + assert ready, "Server did not start in time" + + try: + # Run client + client_cmd = [sys.executable, "-m", "maceexttool.client", "-b", bind, str(ext)] + cp = subprocess.run(client_cmd, cwd=tmp_path, env=env, capture_output=True, text=True, timeout=600) + assert cp.returncode == 0, cp.stderr + + engrad = Path(str(ext).replace(".extinp.tmp", ".engrad")).with_suffix(".engrad") + assert engrad.exists() + out = engrad.read_text() + assert "Total energy" in out + finally: + server.terminate() + try: + server.wait(timeout=5) + except subprocess.TimeoutExpired: + server.kill() diff --git a/maceclient.sh b/maceclient.sh new file mode 100755 index 0000000..ad3789d --- /dev/null +++ b/maceclient.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +SCRIPT_PATH="$(dirname -- "${BASH_SOURCE[0]}")" +src="${SCRIPT_PATH}/mace" +venv="$src/.venv" + +need_setup=0 +if [ ! -d "$venv" ]; then + need_setup=1 +elif [ ! -x "$venv/bin/maceclient" ]; then + need_setup=1 +fi + +if [ $need_setup -eq 1 ]; then + "$src"/install.sh +fi + +source "$venv"/bin/activate +maceclient "$@" diff --git a/maceserver.sh b/maceserver.sh new file mode 100755 index 0000000..a124cc9 --- /dev/null +++ b/maceserver.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +SCRIPT_PATH="$(dirname -- "${BASH_SOURCE[0]}")" +src="${SCRIPT_PATH}/mace" +venv="$src/.venv" + +need_setup=0 +if [ ! -d "$venv" ]; then + need_setup=1 +elif [ ! -x "$venv/bin/maceserver" ]; then + need_setup=1 +fi + +if [ $need_setup -eq 1 ]; then + "$src"/install.sh +fi + +source "$venv"/bin/activate +maceserver "$@" & +PID=$! +echo "MACESERVER_PID: $PID" From 5b57022da888ee231e4024c0ed2736fa52ca70cc Mon Sep 17 00:00:00 2001 From: Ilyes Batatia <48651863+ilyes319@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:50:07 +0000 Subject: [PATCH 2/6] add tests --- .gitignore | 6 ++ mace/test/.gitignore | 16 +++++ mace/test/H2O_goat_extclient.property.txt | 3 + mace/test/HF.engrad | 17 ----- mace/test/HF_orca_ext.bibtex | 84 ++++++++++++++++++++++ mace/test/HF_orca_ext.gbw | Bin 0 -> 981072 bytes mace/test/HF_orca_ext.opt | Bin 0 -> 564 bytes mace/test/HF_orca_ext.property.txt | 83 +++++++++++++++++++++ mace/test/HF_orca_extclient.bibtex | 84 ++++++++++++++++++++++ mace/test/HF_orca_extclient.gbw | Bin 0 -> 981072 bytes mace/test/HF_orca_extclient.opt | Bin 0 -> 564 bytes mace/test/HF_orca_extclient.property.txt | 83 +++++++++++++++++++++ mace/test/run_tests.sh | 46 +++++++----- 13 files changed, 388 insertions(+), 34 deletions(-) create mode 100644 mace/test/.gitignore create mode 100644 mace/test/H2O_goat_extclient.property.txt delete mode 100644 mace/test/HF.engrad create mode 100644 mace/test/HF_orca_ext.bibtex create mode 100644 mace/test/HF_orca_ext.gbw create mode 100644 mace/test/HF_orca_ext.opt create mode 100644 mace/test/HF_orca_ext.property.txt create mode 100644 mace/test/HF_orca_extclient.bibtex create mode 100644 mace/test/HF_orca_extclient.gbw create mode 100644 mace/test/HF_orca_extclient.opt create mode 100644 mace/test/HF_orca_extclient.property.txt diff --git a/.gitignore b/.gitignore index 82f9275..80307c0 100644 --- a/.gitignore +++ b/.gitignore @@ -160,3 +160,9 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +*.bas4 +*.bas2 +*.bas3 +*.bas5 +*.bas0 +*.bas1 diff --git a/mace/test/.gitignore b/mace/test/.gitignore new file mode 100644 index 0000000..4bf3485 --- /dev/null +++ b/mace/test/.gitignore @@ -0,0 +1,16 @@ +# Cache folder for test artifacts +.cache/ + +# ORCA + wrapper outputs +*.out +*.serverout +*.lastext +*.engrad +*.extinp.tmp +*.tmp +pid.txt +s.out + +# Generated XYZ files from ORCA (tracked inputs remain tracked) +*.xyz + diff --git a/mace/test/H2O_goat_extclient.property.txt b/mace/test/H2O_goat_extclient.property.txt new file mode 100644 index 0000000..59459a9 --- /dev/null +++ b/mace/test/H2O_goat_extclient.property.txt @@ -0,0 +1,3 @@ +************************************************* +******************* ORCA 6.1.0 ****************** +************************************************* diff --git a/mace/test/HF.engrad b/mace/test/HF.engrad deleted file mode 100644 index daeb669..0000000 --- a/mace/test/HF.engrad +++ /dev/null @@ -1,17 +0,0 @@ -# -# Number of atoms -# -2 -# -# Total energy [Eh] -# --1.004614677992e+02 -# -# Gradient [Eh/Bohr] A1X, A1Y, A1Z, A2X, ... -# --0.000000000000e+00 --0.000000000000e+00 - 2.938162452069e-02 --0.000000000000e+00 --0.000000000000e+00 --2.938162452069e-02 diff --git a/mace/test/HF_orca_ext.bibtex b/mace/test/HF_orca_ext.bibtex new file mode 100644 index 0000000..1f5378c --- /dev/null +++ b/mace/test/HF_orca_ext.bibtex @@ -0,0 +1,84 @@ +@article{RN269, +author = {Neese,F.}, +title = {Software update: the ORCA program system, version 6.0}, +journal = {WIRES Comput. Molec. Sci.}, +volume = {15}, +number = {1}, +pages = {e70019}, +DOI = {10.1002/wcms.7019}, +year = {2025}, +type = {journal Article} +} + +@article{RN232, +author = {Neese,F.}, +title = {The SHARK Integral Generation and Digestion System}, +journal = {J. Comp. Chem.}, +volume = {44}, +number = {3}, +pages = {381}, +DOI = {10.1002/jcc.26942}, +year = {2022}, +type = {journal Article} +} + +@article{RN95, +author = {Neese,F.}, +title = {The ORCA program system}, +journal = {WIRES Comput. Molec. Sci.}, +volume = {2}, +number = {1}, +pages = {73-78}, +DOI = {10.1002/wcms.81}, +year = {2012}, +type = {journal Article} +} + +@article{RN157, +author = {Neese,F.}, +title = {Software update: the ORCA program system, version 4.0}, +journal = {WIRES Comput. Molec. Sci.}, +volume = {8}, +number = {1}, +pages = {1-6}, +DOI = {10.1002/wcms.1327}, +year = {2018}, +type = {journal Article} +} + +@article{RN204, +author = {Neese,F. and Wennmohs,F. and Becker,U. and Riplinger,C.}, +title = {The ORCA quantum chemistry program package}, +journal = {J. Chem. Phys.}, +volume = {152}, +number = {22}, +pages = {224108}, +DOI = {10.1063/5.0004608}, +year = {2020}, +type = {journal Article} +} + +@article{RN231, +author = {Neese,F.}, +title = {Software update: The ORCA program system—Version 5.0}, +journal = {WIRES Comput. Molec. Sci.}, +volume = {12}, +number = {1}, +pages = {e1606}, +DOI = {10.1002/wcms.1606}, +year = {2022}, +type = {journal Article} +} + +@article{RN24, +author = {Neese,F.}, +title = {Approximate second-order SCF convergence for spin unrestricted wavefunctions}, +journal = {Chem. Phys. Lett.}, +volume = {325}, +number = {1-3}, +pages = {93-98}, +DOI = {10.1016/s0009-2614(00)00662-x}, +year = {2000}, +type = {journal Article} +} + diff --git a/mace/test/HF_orca_ext.gbw b/mace/test/HF_orca_ext.gbw new file mode 100644 index 0000000000000000000000000000000000000000..8c86e74806b4bf0711e388eba059217854d6cd91 GIT binary patch literal 981072 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 K_CvkLzyJWbcx-n7 literal 0 HcmV?d00001 diff --git a/mace/test/HF_orca_ext.property.txt b/mace/test/HF_orca_ext.property.txt new file mode 100644 index 0000000..70b1294 --- /dev/null +++ b/mace/test/HF_orca_ext.property.txt @@ -0,0 +1,83 @@ +************************************************* +******************* ORCA 6.1.0 ****************** +************************************************* +$Calculation_Status + &GeometryIndex 0 + &version [&Type "String"] "6.1.0" + &progName [&Type "String"] "orca" + &Status [&Type "String"] "NORMAL TERMINATION" +$End +$Geometry + &GeometryIndex 1 + &NAtoms [&Type "Integer"] 2 + &NCorelessECP [&Type "Integer"] 0 + &NGhostAtoms [&Type "Integer"] 0 + &CartesianCoordinates [&Type "Coordinates", &Dim(2,4), &Units "Bohr"] + H 0.000000000000 0.000000000000 0.000000000000 + F 0.000000000000 0.000000000000 1.700753520529 +$End +$Single_Point_Data + &GeometryIndex 1 + &FinalEnergy [&Type "Double"] -1.0046146779919999e+02 "Final single point energy" + &Converged [&Type "Boolean"] true +$End +$Geometry + &GeometryIndex 2 + &NAtoms [&Type "Integer"] 2 + &NCorelessECP [&Type "Integer"] 0 + &NGhostAtoms [&Type "Integer"] 0 + &CartesianCoordinates [&Type "Coordinates", &Dim(2,4), &Units "Bohr"] + H 0.000000000000 0.000000000000 -0.024335017042 + F 0.000000000000 0.000000000000 1.725088537571 +$End +$Single_Point_Data + &GeometryIndex 2 + &FinalEnergy [&Type "Double"] -1.0046209305660000e+02 "Final single point energy" + &Converged [&Type "Boolean"] true +$End +$Geometry + &GeometryIndex 3 + &NAtoms [&Type "Integer"] 2 + &NCorelessECP [&Type "Integer"] 0 + &NGhostAtoms [&Type "Integer"] 0 + &CartesianCoordinates [&Type "Coordinates", &Dim(2,4), &Units "Bohr"] + H 0.000000000000 0.000000000000 -0.022329656513 + F 0.000000000000 0.000000000000 1.723083177042 +$End +$Single_Point_Data + &GeometryIndex 3 + &FinalEnergy [&Type "Double"] -1.0046209882930000e+02 "Final single point energy" + &Converged [&Type "Boolean"] true +$End +$Geometry + &GeometryIndex 4 + &NAtoms [&Type "Integer"] 2 + &NCorelessECP [&Type "Integer"] 0 + &NGhostAtoms [&Type "Integer"] 0 + &CartesianCoordinates [&Type "Coordinates", &Dim(2,4), &Units "Bohr"] + H 0.000000000000 0.000000000000 -0.022135144768 + F 0.000000000000 0.000000000000 1.722888665297 +$End +$Single_Point_Data + &GeometryIndex 4 + &FinalEnergy [&Type "Double"] -1.0046209887430000e+02 "Final single point energy" + &Converged [&Type "Boolean"] true +$End +$Calculation_Info + &GeometryIndex 4 + &Mult [&Type "Integer"] 1 + &Charge [&Type "Integer"] 0 + &NumOfAtoms [&Type "Integer"] 2 + &NumOfElectrons [&Type "Integer"] 0 + &NumOfBasisFuncts [&Type "Integer"] 0 + &NumOfAuxCBasisFuncts [&Type "Integer"] 0 + &NumOfAuxJBasisFuncts [&Type "Integer"] 0 + &NumOfAuxJKBasisFuncts [&Type "Integer"] 0 + &NumOfCABSBasisFuncts [&Type "Integer"] 0 +$End +$Calculation_Timings + &GeometryIndex 4 + &GSTEP [&Type "Double"] 3.9419999999994321e-03 + &EXT [&Type "Double"] 9.4839280000000006e+00 + &SUM [&Type "Double"] 9.4878699999999991e+00 +$End diff --git a/mace/test/HF_orca_extclient.bibtex b/mace/test/HF_orca_extclient.bibtex new file mode 100644 index 0000000..1f5378c --- /dev/null +++ b/mace/test/HF_orca_extclient.bibtex @@ -0,0 +1,84 @@ +@article{RN269, +author = {Neese,F.}, +title = {Software update: the ORCA program system, version 6.0}, +journal = {WIRES Comput. Molec. Sci.}, +volume = {15}, +number = {1}, +pages = {e70019}, +DOI = {10.1002/wcms.7019}, +year = {2025}, +type = {journal Article} +} + +@article{RN232, +author = {Neese,F.}, +title = {The SHARK Integral Generation and Digestion System}, +journal = {J. Comp. Chem.}, +volume = {44}, +number = {3}, +pages = {381}, +DOI = {10.1002/jcc.26942}, +year = {2022}, +type = {journal Article} +} + +@article{RN95, +author = {Neese,F.}, +title = {The ORCA program system}, +journal = {WIRES Comput. Molec. Sci.}, +volume = {2}, +number = {1}, +pages = {73-78}, +DOI = {10.1002/wcms.81}, +year = {2012}, +type = {journal Article} +} + +@article{RN157, +author = {Neese,F.}, +title = {Software update: the ORCA program system, version 4.0}, +journal = {WIRES Comput. Molec. Sci.}, +volume = {8}, +number = {1}, +pages = {1-6}, +DOI = {10.1002/wcms.1327}, +year = {2018}, +type = {journal Article} +} + +@article{RN204, +author = {Neese,F. and Wennmohs,F. and Becker,U. and Riplinger,C.}, +title = {The ORCA quantum chemistry program package}, +journal = {J. Chem. Phys.}, +volume = {152}, +number = {22}, +pages = {224108}, +DOI = {10.1063/5.0004608}, +year = {2020}, +type = {journal Article} +} + +@article{RN231, +author = {Neese,F.}, +title = {Software update: The ORCA program system—Version 5.0}, +journal = {WIRES Comput. Molec. Sci.}, +volume = {12}, +number = {1}, +pages = {e1606}, +DOI = {10.1002/wcms.1606}, +year = {2022}, +type = {journal Article} +} + +@article{RN24, +author = {Neese,F.}, +title = {Approximate second-order SCF convergence for spin unrestricted wavefunctions}, +journal = {Chem. Phys. Lett.}, +volume = {325}, +number = {1-3}, +pages = {93-98}, +DOI = {10.1016/s0009-2614(00)00662-x}, +year = {2000}, +type = {journal Article} +} + diff --git a/mace/test/HF_orca_extclient.gbw b/mace/test/HF_orca_extclient.gbw new file mode 100644 index 0000000000000000000000000000000000000000..6a96a2e3939ca1e2effa914a2b8a16916d1a1868 GIT binary patch literal 981072 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 z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5(bnLs5Q zwrmM=6Cgl<009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0*xS0+03C4EVPjT0RjXF5Ew2nc$hFL9lY-z0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAW)wI1Ih4D4j3aqfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U 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 K_CvkLzyJWbcx-n7 literal 0 HcmV?d00001 diff --git a/mace/test/HF_orca_extclient.property.txt b/mace/test/HF_orca_extclient.property.txt new file mode 100644 index 0000000..f6aaf56 --- /dev/null +++ b/mace/test/HF_orca_extclient.property.txt @@ -0,0 +1,83 @@ +************************************************* +******************* ORCA 6.1.0 ****************** +************************************************* +$Calculation_Status + &GeometryIndex 0 + &version [&Type "String"] "6.1.0" + &progName [&Type "String"] "orca" + &Status [&Type "String"] "NORMAL TERMINATION" +$End +$Geometry + &GeometryIndex 1 + &NAtoms [&Type "Integer"] 2 + &NCorelessECP [&Type "Integer"] 0 + &NGhostAtoms [&Type "Integer"] 0 + &CartesianCoordinates [&Type "Coordinates", &Dim(2,4), &Units "Bohr"] + H 0.000000000000 0.000000000000 0.000000000000 + F 0.000000000000 0.000000000000 1.700753520529 +$End +$Single_Point_Data + &GeometryIndex 1 + &FinalEnergy [&Type "Double"] -1.0046146779919999e+02 "Final single point energy" + &Converged [&Type "Boolean"] true +$End +$Geometry + &GeometryIndex 2 + &NAtoms [&Type "Integer"] 2 + &NCorelessECP [&Type "Integer"] 0 + &NGhostAtoms [&Type "Integer"] 0 + &CartesianCoordinates [&Type "Coordinates", &Dim(2,4), &Units "Bohr"] + H 0.000000000000 0.000000000000 -0.024335017042 + F 0.000000000000 0.000000000000 1.725088537571 +$End +$Single_Point_Data + &GeometryIndex 2 + &FinalEnergy [&Type "Double"] -1.0046209305660000e+02 "Final single point energy" + &Converged [&Type "Boolean"] true +$End +$Geometry + &GeometryIndex 3 + &NAtoms [&Type "Integer"] 2 + &NCorelessECP [&Type "Integer"] 0 + &NGhostAtoms [&Type "Integer"] 0 + &CartesianCoordinates [&Type "Coordinates", &Dim(2,4), &Units "Bohr"] + H 0.000000000000 0.000000000000 -0.022329656513 + F 0.000000000000 0.000000000000 1.723083177042 +$End +$Single_Point_Data + &GeometryIndex 3 + &FinalEnergy [&Type "Double"] -1.0046209882930000e+02 "Final single point energy" + &Converged [&Type "Boolean"] true +$End +$Geometry + &GeometryIndex 4 + &NAtoms [&Type "Integer"] 2 + &NCorelessECP [&Type "Integer"] 0 + &NGhostAtoms [&Type "Integer"] 0 + &CartesianCoordinates [&Type "Coordinates", &Dim(2,4), &Units "Bohr"] + H 0.000000000000 0.000000000000 -0.022135144768 + F 0.000000000000 0.000000000000 1.722888665297 +$End +$Single_Point_Data + &GeometryIndex 4 + &FinalEnergy [&Type "Double"] -1.0046209887430000e+02 "Final single point energy" + &Converged [&Type "Boolean"] true +$End +$Calculation_Info + &GeometryIndex 4 + &Mult [&Type "Integer"] 1 + &Charge [&Type "Integer"] 0 + &NumOfAtoms [&Type "Integer"] 2 + &NumOfElectrons [&Type "Integer"] 0 + &NumOfBasisFuncts [&Type "Integer"] 0 + &NumOfAuxCBasisFuncts [&Type "Integer"] 0 + &NumOfAuxJBasisFuncts [&Type "Integer"] 0 + &NumOfAuxJKBasisFuncts [&Type "Integer"] 0 + &NumOfCABSBasisFuncts [&Type "Integer"] 0 +$End +$Calculation_Timings + &GeometryIndex 4 + &GSTEP [&Type "Double"] 2.4739999999999901e-03 + &EXT [&Type "Double"] 1.1125980000000000e+00 + &SUM [&Type "Double"] 1.1150720000000001e+00 +$End diff --git a/mace/test/run_tests.sh b/mace/test/run_tests.sh index 609ee37..6d10fca 100755 --- a/mace/test/run_tests.sh +++ b/mace/test/run_tests.sh @@ -1,20 +1,33 @@ #!/bin/bash +# Output directory for artifacts (can override with OUTDIR=/path) +OUTDIR="${OUTDIR:-.cache}" +mkdir -p "$OUTDIR" + # Direct execution of standalone wrapper script echo "These tests might take a few minutes." echo "To check the progress, please see the respective outputs." -cmd="../../mace.sh HF_exttool.inp > HF_exttool.out" +cmd="../../mace.sh HF_exttool.inp > \"$OUTDIR\"/HF_exttool.out" echo "Test command: ${cmd}" eval $cmd +mv -f ./*.engrad "$OUTDIR" 2>/dev/null || true # Check if ORCA is available; if not, skip ORCA-based tests -if ! command -v orca >/dev/null 2>&1 ; then - echo "ORCA not found in PATH. Skipping ORCA-based tests (orca_ext, client, GOAT)." +ORCA_CMD="" +if [ -n "${ORCA:-}" ] && [ -x "${ORCA}" ]; then + ORCA_CMD="${ORCA}" +elif command -v orca >/dev/null 2>&1 ; then + ORCA_CMD="$(command -v orca)" +fi + +# Check if ORCA is available; if not, skip ORCA-based tests +if [ -z "$ORCA_CMD" ]; then + echo "ORCA not found. Set ORCA env var or add to PATH. Skipping ORCA-based tests (orca_ext, client, GOAT)." exit 0 fi # Execution of standalone wrapper script via ORCA optimization -cmd="$(which orca) HF_orca_ext.inp > HF_orca_ext.out" +cmd="\"$ORCA_CMD\" HF_orca_ext.inp > \"$OUTDIR\"/HF_orca_ext.out" echo "Test command: ${cmd}" eval $cmd @@ -27,18 +40,18 @@ killserver(){ } trap "killserver; exit" INT TERM EXIT # - start the server -sf=HF_orca_extclient.serverout -cmd="../../maceserver.sh > $sf 2>&1 &" +sf="$OUTDIR/HF_orca_extclient.serverout" +cmd="../../maceserver.sh > \"$sf\" 2>&1 &" echo "Starting server: ${cmd}" eval $cmd # - initialize the output file -of=HF_orca_extclient.out -> $of +of="$OUTDIR/HF_orca_extclient.out" +> "$of" # - wait for the server to start WAITED=0 -while [ -z "$(grep -E "Serving|Running on" $sf)" ]; do echo "Waiting for server" >> $of; sleep 1s; WAITED=$((WAITED+1)); if [ $WAITED -gt 30 ]; then echo "Timeout waiting for server" >> $of; break; fi; done +while [ -z "$(grep -E "Serving|Running on" "$sf")" ]; do echo "Waiting for server" >> "$of"; sleep 1s; WAITED=$((WAITED+1)); if [ $WAITED -gt 30 ]; then echo "Timeout waiting for server" >> "$of"; break; fi; done # - start the ORCA job -cmd="$(which orca) HF_orca_extclient.inp >> $of" +cmd="\"$ORCA_CMD\" HF_orca_extclient.inp >> \"$of\"" echo "Test command: ${cmd}" eval $cmd @@ -47,18 +60,17 @@ killall maceserver || true # Parallel server/client GOAT test # - start the server (4 threads) -sf=H2O_goat_extclient.serverout -cmd="../../maceserver.sh -n 4 > $sf 2>&1 &" +sf="$OUTDIR/H2O_goat_extclient.serverout" +cmd="../../maceserver.sh -n 4 > \"$sf\" 2>&1 &" echo "Starting server: ${cmd}" eval $cmd # - initialize the output file -of=H2O_goat_extclient.out -> $of +of="$OUTDIR/H2O_goat_extclient.out" +> "$of" # - wait for the server to start WAITED=0 -while [ -z "$(grep -E "Serving|Running on" $sf)" ]; do echo "Waiting for server" >> $of; sleep 1s; WAITED=$((WAITED+1)); if [ $WAITED -gt 30 ]; then echo "Timeout waiting for server" >> $of; break; fi; done +while [ -z "$(grep -E "Serving|Running on" "$sf")" ]; do echo "Waiting for server" >> "$of"; sleep 1s; WAITED=$((WAITED+1)); if [ $WAITED -gt 30 ]; then echo "Timeout waiting for server" >> "$of"; break; fi; done # - start the ORCA job -cmd="$(which orca) H2O_goat_extclient.inp >> $of" +cmd="\"$ORCA_CMD\" H2O_goat_extclient.inp >> \"$of\"" echo "Test command: ${cmd}" eval $cmd - From d0f9c4e85aac8a00999093b804630a2794f33846 Mon Sep 17 00:00:00 2001 From: Ilyes Batatia <48651863+ilyes319@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:54:13 +0000 Subject: [PATCH 3/6] add examples --- examples/README.md | 49 ++++++++++++++++++++ examples/goat_water_mace_omol_server.inp | 11 +++++ examples/goat_water_mace_omol_standalone.inp | 11 +++++ 3 files changed, 71 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/goat_water_mace_omol_server.inp create mode 100644 examples/goat_water_mace_omol_standalone.inp diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..b707b1c --- /dev/null +++ b/examples/README.md @@ -0,0 +1,49 @@ +# MACE GOAT Examples (Custom Model) + +This folder shows how to run ORCA's global optimization tool (GOAT) using the MACE wrapper with a custom model file. + +Two variants are provided: +- Server–client (recommended for many external calls): `goat_water_mace_omol_server.inp` +- Standalone (simpler, slower for many calls): `goat_water_mace_omol_standalone.inp` + +Replace `/abs/path/to/your/MACE-omol-custom.model` with your actual model file path before running. + +## Server–Client (recommended) + +1) Start the MACE server in another terminal with your custom model: + +``` +../maceserver.sh \ + -s omol \ + -m /abs/path/to/your/MACE-omol-custom.model \ + --default-dtype float64 \ + --device cpu \ + -n 4 \ + -b 127.0.0.1:8888 +``` + +2) Run ORCA on the provided input: + +``` +orca goat_water_mace_omol_server.inp > goat_water_mace_omol_server.out +``` + +- The input references `../maceclient.sh` and points it to the server via `Ext_Params "-b 127.0.0.1:8888"`. +- This example uses `pal1` to avoid requiring MPI. If you have MPI, you can change to `pal4`. + +## Standalone (simple, no server) + +Run ORCA on the standalone input: + +``` +orca goat_water_mace_omol_standalone.inp > goat_water_mace_omol_standalone.out +``` + +- The input references `../mace.sh` and passes the suite and custom model via `Ext_Params`. +- Recommended flags for optimization: `--default-dtype float64` and `--device cpu`. + +Notes +- Ensure ORCA is available on your PATH (or call it via absolute path). +- Use absolute paths for the custom model so ORCA can find it regardless of working directory. +- For Materials Project models (mp), change `-s omol` to `-s mp` and add flags like `--dispersion` or `--head mh0` as needed. + diff --git a/examples/goat_water_mace_omol_server.inp b/examples/goat_water_mace_omol_server.inp new file mode 100644 index 0000000..e728ce8 --- /dev/null +++ b/examples/goat_water_mace_omol_server.inp @@ -0,0 +1,11 @@ +! extopt goat pal1 +%method + progext "../maceclient.sh" + ext_params "-b 127.0.0.1:8888" +end +*xyz 0 1 + O 0.000000 0.000000 0.000000 + H 0.000000 0.000000 0.960000 + H 0.000000 0.750000 -0.240000 +* + diff --git a/examples/goat_water_mace_omol_standalone.inp b/examples/goat_water_mace_omol_standalone.inp new file mode 100644 index 0000000..4f1f8cb --- /dev/null +++ b/examples/goat_water_mace_omol_standalone.inp @@ -0,0 +1,11 @@ +! extopt goat pal1 +%method + progext "../mace.sh" + ext_params "-s omol -m /abs/path/to/your/MACE-omol-custom.model --default-dtype float64 --device cpu" +end +*xyz 0 1 + O 0.000000 0.000000 0.000000 + H 0.000000 0.000000 0.960000 + H 0.000000 0.750000 -0.240000 +* + From 380a2d8db1747d34773218cc415c999ea3ea0ca5 Mon Sep 17 00:00:00 2001 From: Ilyes Batatia <48651863+ilyes319@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:58:55 +0000 Subject: [PATCH 4/6] move example --- {examples => mace/examples}/README.md | 6 +++--- {examples => mace/examples}/goat_water_mace_omol_server.inp | 2 +- .../examples}/goat_water_mace_omol_standalone.inp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename {examples => mace/examples}/README.md (86%) rename {examples => mace/examples}/goat_water_mace_omol_server.inp (84%) rename {examples => mace/examples}/goat_water_mace_omol_standalone.inp (90%) diff --git a/examples/README.md b/mace/examples/README.md similarity index 86% rename from examples/README.md rename to mace/examples/README.md index b707b1c..3dac847 100644 --- a/examples/README.md +++ b/mace/examples/README.md @@ -13,7 +13,7 @@ Replace `/abs/path/to/your/MACE-omol-custom.model` with your actual model file p 1) Start the MACE server in another terminal with your custom model: ``` -../maceserver.sh \ +../../maceserver.sh \ -s omol \ -m /abs/path/to/your/MACE-omol-custom.model \ --default-dtype float64 \ @@ -28,7 +28,7 @@ Replace `/abs/path/to/your/MACE-omol-custom.model` with your actual model file p orca goat_water_mace_omol_server.inp > goat_water_mace_omol_server.out ``` -- The input references `../maceclient.sh` and points it to the server via `Ext_Params "-b 127.0.0.1:8888"`. +- The input references `../../maceclient.sh` and points it to the server via `Ext_Params "-b 127.0.0.1:8888"`. - This example uses `pal1` to avoid requiring MPI. If you have MPI, you can change to `pal4`. ## Standalone (simple, no server) @@ -39,7 +39,7 @@ Run ORCA on the standalone input: orca goat_water_mace_omol_standalone.inp > goat_water_mace_omol_standalone.out ``` -- The input references `../mace.sh` and passes the suite and custom model via `Ext_Params`. +- The input references `../../mace.sh` and passes the suite and custom model via `Ext_Params`. - Recommended flags for optimization: `--default-dtype float64` and `--device cpu`. Notes diff --git a/examples/goat_water_mace_omol_server.inp b/mace/examples/goat_water_mace_omol_server.inp similarity index 84% rename from examples/goat_water_mace_omol_server.inp rename to mace/examples/goat_water_mace_omol_server.inp index e728ce8..546f032 100644 --- a/examples/goat_water_mace_omol_server.inp +++ b/mace/examples/goat_water_mace_omol_server.inp @@ -1,6 +1,6 @@ ! extopt goat pal1 %method - progext "../maceclient.sh" + progext "../../maceclient.sh" ext_params "-b 127.0.0.1:8888" end *xyz 0 1 diff --git a/examples/goat_water_mace_omol_standalone.inp b/mace/examples/goat_water_mace_omol_standalone.inp similarity index 90% rename from examples/goat_water_mace_omol_standalone.inp rename to mace/examples/goat_water_mace_omol_standalone.inp index 4f1f8cb..febc6fa 100644 --- a/examples/goat_water_mace_omol_standalone.inp +++ b/mace/examples/goat_water_mace_omol_standalone.inp @@ -1,6 +1,6 @@ ! extopt goat pal1 %method - progext "../mace.sh" + progext "../../mace.sh" ext_params "-s omol -m /abs/path/to/your/MACE-omol-custom.model --default-dtype float64 --device cpu" end *xyz 0 1 From a34fe4b5e26ea403ddb012d3b96bbd60ba1ee68c Mon Sep 17 00:00:00 2001 From: Ilyes Batatia <48651863+ilyes319@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:35:06 +0000 Subject: [PATCH 5/6] Delete mace/test/s.out --- mace/test/s.out | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 mace/test/s.out diff --git a/mace/test/s.out b/mace/test/s.out deleted file mode 100644 index 92a9125..0000000 --- a/mace/test/s.out +++ /dev/null @@ -1,5 +0,0 @@ -MACESERVER_PID: 25381 -INFO:waitress:Serving on http://127.0.0.1:64207/Users/Lilyes/Documents/GitHub/orca-external-tools/mace/.venv/lib/python3.12/site-packages/e3nn/o3/_wigner.py:10: UserWarning: Environment variable TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD detected, since the`weights_only` argument was not explicitly passed to `torch.load`, forcing weights_only=False. - _Jd, _W3j_flat, _W3j_indices = torch.load(os.path.join(os.path.dirname(__file__), 'constants.pt')) -/Users/Lilyes/Documents/GitHub/orca-external-tools/mace/.venv/lib/python3.12/site-packages/mace/calculators/mace.py:197: UserWarning: Environment variable TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD detected, since the`weights_only` argument was not explicitly passed to `torch.load`, forcing weights_only=False. - torch.load(f=model_path, map_location=device) From d7fae7f3dcb91d22148dbe23d3798a1271aeb09c Mon Sep 17 00:00:00 2001 From: Ilyes Batatia <48651863+ilyes319@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:46:12 +0000 Subject: [PATCH 6/6] remoe cache test files --- mace/test/HF_orca_ext.gbw | Bin 981072 -> 0 bytes mace/test/pid.txt | 1 - 2 files changed, 1 deletion(-) delete mode 100644 mace/test/HF_orca_ext.gbw delete mode 100644 mace/test/pid.txt diff --git a/mace/test/HF_orca_ext.gbw b/mace/test/HF_orca_ext.gbw deleted file mode 100644 index 8c86e74806b4bf0711e388eba059217854d6cd91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 981072 zcmeI*3zS`DVK4AANuUwAG{uO(RYZstZ+OY`R!H^+l!q9339Xb%7?KGYNM^>Fgc6`Q zEf1mPQJ0qjp;{igZ1i40kuHMRnG3yCT!`F+Riv?t0FntJFaeQg$lc%G-*;vvWFSx| zB>VTRocZ?q?C-bt`Ja7u&e?m-nyMUe*qBBryT@{9+1F^?^!ygF>-DQc51!uHJ)?Dc z+Zpp-e?nsk5ZLGhc00DGt-EKp&$rK6&=#tbyIZ>#>{dp?KGQ!nefN#7!5TC|;0v7{ zZ6gNgsg*$aN^kAxZ13>?U#krt8(VJwo_Vd^^X7NevHf*#bKL%3|25qa%I)7V)Aq0P z_V1Z7tESDbeY4Bg|0(m^dU|S4QA>bw`_E}>?T`WW-TrgC_NZm!HM&v0{^xejXlGGN zfpYuLXz%K=1Jrl>cg^W^_OF(kzsmpO*zc6?)|vGbsCTQ&{6D>Go&&&o?g!=e_u{YT z#xGi3ZU3HmZT|PCmis`t{pWVht!3jix_bT3=yLY2ru#zq`k&J|qo&QTeY4B$Kcn6M z{?v3&D7Sx?mw!z+e|-OM@9Opepq9Hp`T9S-{p4CUUZbnme^;kZ|25qg%GZB)+nky< zzxK^8w||e%e|6mv%I)9Q)!i|-yRHPaZg;u;yKMcs?gZubpV8V=oB-Z_mtERA+PY6! zP*;mOwtM9E@9OUCYU`e752($qP;URZGrGH4Ytv*69VSq1|Czr3JItg`o#pm#ojtg{KLB_ApL%TZk*zGZ|NMFF{sVZV zOI`ZP?cX+kPMiP!tIH-I(av)F&-K&K5hyk4DYyR~t?hmQRHKbPtf}SppVdCEtIY?1 zVJx-jtls~7yaCi^i;rw*x&3F(>FVg5;YT1NX=>3|ZvVO7{cEwY*U`x8|9{Sy=LaC` zz^TEYYWw%Jcbqb(ZF*}*drxN#8m);o-wq)RF%Gzq`H1K|qbRdur;g zyY5!}o7+00ZP%XJX`s3`0_E|4_}aG{z|>}&4{vC>{fqB^bhq{R3fOR%nsk@jzxw@; zU7#l0e0Vdf?cd&G;}2JR>&NSVR=@wS3)E@D$F`QQ z|8V?Y!?tU3di}T0ndJrmwcH(w-+Ol)bi^^!jyU?5TDHILjjoRWimN`f&FXIT6TsT- z4(0Z5o9Wm8wcGMlgUju|$EiJ?9W@20d9$nhZ#|{B1YqkKHMh`w0t5)uNuZ@@YZg;Uq6K{JwTXisv-o^zxs&4a4)#^|i+eB&_mK?u zmKzuA)PQtf$*U({d%@M`Z8{~@)qIQ7aZXVx; zDbhWehj*u8!w)^fF?A}%CxT&z!b8X1>3BQU>A3synF}8N%Ia@7kGJ3Iad%jUbdUIO zd_TNxY26x)<=G}fJw?~L*Dp+x*6;25ToAm5fA$F7!J;ZDkJqA5f6JdmAu-I=GI^u2+sv$*U1Pp=)Q?stF1K=zO4oqgZUUmD2TI|t9d z`-=nFq@B*Wc=2fi+5i0Arqi$d{6Mx%&jVXNbHG6M!HYll>v4MxWYeC#^q6^*2C}U$ zzyE!+cNoa-`O2YvfBunyY+2tAmV9gCKsNvDTaP<#i-By(*8ltZ@4jy!Tl2Bizni~& zMYi(P$-lee-&bVClRdZi-&SPzAGFtP=l)_v_KT%kd}Z3duE;)k^f^aAxpYOAoYd9+ z!r|RL}nXnvxPy-_88Uf*V@H+EI$4Mt8pQgX*-FP?Mi z?eEXiG=s&bf_*O!7P0q->ew1dGQGIrulX*p7f38OY^+h`NH$PmGw;LV=K*KAw>z@q5YwK zVSPjS^QwC7-Wtm?G%O(AW$=bDhGcwrTYJy^QTnJANv1` zwZ1piSB}pw%NvaeO{w9T)EuazUIJ_F1gmC0df_cE{A%hei&ia}cJh(U_w#z8Xe|K( z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkL{y)6)~RS>S_5Pm9}elD7>OIp=?Pd)U;+ZR6bz?oC4>p@&IHT{k>)g#B> zG__b#d5MYYI)y8mzcW4IvOAvJ>4c}Vk-uNQFH65iO~b3}9S+9n?vC?M%ER>?!Zl{o zdKR5!Ir!uA$1EIwS{AM?5lXe3yPNZmxD0D!==!YH)Yac$5K36~FMqY@#(VF(uKZnf zxQ0Vozc6LEu0y)+|I9F;SeudS^Gp`*;SsJWG2s(oe4}yuMZdV>2TT4gTnh4a%Fudk zT}+i1kI>ezeMVk~V{w+Af0ydQkFlK(Hl2%EiV~Ms{w};468?sD4Uf>SP`^|>QUlU* z!=T}%+8)2mdgH)K%3lHm2oNAZfB*pk1PBlyKwx7Q2&Vzw?)HUq5aFD}cEux{R|)4P z()o>4r}I+9sG=;--e?`F=_dBaIuCWaN6{P2TbJ>bP4PFstiRcvI?{z=%l@J34_2e~ za|ipE4{=PUo)mck1U|o-Dy7BpN-%YB4#ovkf!mC1A9GBaloi*`>Lmoc!ysR32 zRUAG(uVxwSYiTK-rTSMsUs~?iVuG2)BdkMNnjN8Uc^1YGmBF;$>Jo)VwQq3P*d0d1 zLSwLR`=Y@qL(~Ka5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5)GS<{H6rKp2P>JRm-vaHBL_uAtcjlptw zM`uS{7_Zb&_Qm70Ti)>`S3|eGwo)fB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+0D<)pXpAUZ z=X;jr*{soMX{^uurXfIp009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAW#>9Mo~5?9(xpzmeA8Iuiel-CC1PIhj;FR6> ztXYdSmjHov3B-fRs%j~ZHDB*IF&%rZYf*;PQQ)w=YW2$Ub4J)dj(=|Pzx~zfSNrCF zed}?@UE0!LjZc4nTTVBo=_uViod`1j|&YF0`ArGH9FKdYlYWBzJXN7WIcV6`l_Njva2m8T@ z{h)lk+Aq8=ye>w(F8b2@Q*?>1lP~3|Ka3BLt0!N3!PVz&I%VNip)AT$T}_w%c64^M zrEYco1Z@BMZS&zRw*7}|)Ui9fSBxziUVRR)3>p%|30XZNCLAcq&r$~qZ6rW|009C7MhXlaCQQnQx^)pCK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk8>hfvHu9ST`UnspK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oTti1sW}lVP&wd>RG!^lceE|#+W!L%syffadd+lZ z=uTz$GG>)!MfRf~Kk(o~XD^%b@{K)9|L|k2`G#z3-T&hPX($>ShmI-!)~|yd2%Sd;aBOw(D>DRQ|Ja_4mwgIHVZUk;moa zRsRiH9u|N7;MDxe(EM$t<~xj{?~Qcb_>q1E#4IW&KwT;Id;@@Y5w zT(#VP_*`7S!z-s1hv!8riZ%RvY{#^`T-+Vg;(C4|w)cqqqhZ2t#(KLW;70bj*a7E-5cxvST~M~<73?$>;70b{y2`0 zb#JWuW8L_ZI6l_BvF?v`<4bXTtb1eKAM3{PaeSktU%gWe1K#rU`$ z>HJh!)wEurez`cUab!{F2mXHJnhRRWapAmH7?&PJXIVmjSWbEjKmS!tG5qgfR-La{ z`kK0$YHB`w>fznx`Hkzk<9Uo@@-JTe?7r7dxHdcTGe3Ipr$7H?JU_Bi{>q|NOQxNC zWbBXUQBHj!E_XqHd0yhEbbck(@tH2XvzqS0oAXt(AHDFF7k)MMtm3T3!)KnCmFFw& z`At@xXL%~C&dV&0&y&`4d6wp@hD@rK*Vrr8_hlp3FU_YWK%kBS;S6$U_os{CJfWyV zo+<3wQaJkR~x`2P7s^ZloKfBJ1U`)g00o$dWnHsMeH=!ZW!{dd{sVil~nkFxQ6`0A`W5B_qq?)%L*_uu*cSF-YLZ{jP>Yc%1h__C(jb%b&X8 z+}OW79tV!iE2qKiBgfC&@1dW6CiYLtuk6_J-2R{bLH~81{l{ll-+y0q{-QXKQca)! zHjgv=I*LIjEqHZJb)M$g_&n9|=V1E_{b7z!r{m4gUkvH1>gCy2Ub=C@_^rPe=c|T> zb3>a{oz1`H_bRq~t~xG#_@(UNuzF{-XIGT%+$8_`^T#~;?W-I4Xa0K5z{`tT;`d6% zHIGx%abdOojXc}?#M=(rd`vuk-7Nq5*5i)bVwX+h_iDzz>H7QMc=|hc9loa7u3}}1 zReUM?KR>;;*a9tiF|ugjxctuW^_4qXs_&6Z8Pn|l)cF7P;p1nH&5Ly^4&47sUV)8& z)9X=fzp;5*pCb#+EzfP_x6cbLd^HP=p8VA@d04B}_m9hW`9WCmvCX<-8Jq98?8S3V zy?tC>eV&+*KUI9XIPtdkz2)^u>-C>ir*?m@P~GpLHCgpNpVe8|%O1OLOrE@}@niFH zH++0(c(tEyl7FE*ke`s3d&}hakJ|p~b=xxP`2VbSzunK%eBqWI>GiIzTsUwD0vpDD z5Z;duz47*i&pdGE)M~q}eco|mUJXxm+2h4Dj&4}*PazG|A+ z9?z!nVZQJrJI9&VUe0Lq8y*B+>&k_T{le=)4#)vHAP3}t9FPNYKn}L!l2R4As9}e4gwc`AiN4fnb==ejfr_xkN$VhvMkMJm1Wa6oPP8r8AE^o0RjXF z5FkK+009CUlfWyBRxO!!a-J=`DwM@pIybX1wNYCL5O{L}ckZ;)?|uCvU+RBzbD5d| z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oQMh z2yB%%CREpF30G_h*KaAW;WEk!zLnn;rR%`FQ9b-A*PeFmajAc_>4pza)2>gQ<{Lh3 z7`8rTUGt{-(lXwtPSdCQ^_H2YY1S<*&Fk?rhv^_dpk@L^5ENyr;|9KY1BrQd~D^`h*OCvUmt`MYkK8pelvysl4qQ^zCgvp~}jAV7cs0RkJKKucqn zgFgJkBl~QdRV%+dyL$4q7hHYbrc-`)Pbk01K7H}-k6gOov*mHd2G~HW{B{Y%lV4+s z<;{A#miZo7PCDvNs}h