# MIT License
#
# Copyright (c) 2025 Isacks-Co contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import os
from asap3 import EMT
from asap3 import LennardJones as asap_LJ
[docs]
class Potential:
"""Base class for interatomic potentials.
This abstract interface defines the structure for all potentials
that can produce ASE-compatible calculator objects.
"""
def __init__(self):
"""Initialize the Potential base class."""
self.pot_str = None
[docs]
def getASEPotentialCalculator(self):
"""Return an ASE calculator object.
Returns:
ase.Calculator | None: ASE-compatible calculator, or None if
not implemented by the subclass.
"""
return None
def __str__(self):
"""Return the name of the potential.
Returns:
str: Human-readable potential name.
"""
return self.pot_str
[docs]
class LennardJonesPotential(Potential):
"""Lennard-Jones pair potential."""
def __init__(self, atomic_numbers, sigmas, epsilons, rc=None):
"""Initialize Lennard-Jones parameters.
Args:
atomic_numbers (list[int]): List of atomic numbers.
sigmas (list[float]): LJ sigma parameters (Å).
epsilons (list[float]): LJ epsilon parameters (eV).
rc (float, optional): Cutoff radius (Å). Defaults to `2.5 * max(sigmas)`.
"""
super().__init__()
self.pot_str = "Lennard Jones"
self.atomic_nums = atomic_numbers
self.sigmas = sigmas
self.epsilons = epsilons
self.rc = rc if rc is not None else 2.5 * max(self.sigmas)
[docs]
def getASEPotentialCalculator(self):
"""Create an ASE Lennard-Jones calculator.
Returns:
ase.Calculator: ASAP LJ calculator instance.
"""
return asap_LJ(
self.atomic_nums,
sigma=self.sigmas,
epsilon=self.epsilons,
rCut=self.rc
)
[docs]
class EMTPotential(Potential):
"""Effective Medium Theory (EMT) potential."""
def __init__(self):
"""Initialize EMT potential."""
super().__init__()
self.pot_str = "EMT"
[docs]
def getASEPotentialCalculator(self):
"""Return the EMT ASE calculator.
Returns:
ase.calculators.emt.EMT: EMT calculator instance.
"""
return EMT()
[docs]
class MACEPotential(Potential):
"""MACE machine-learning interatomic potential."""
def __init__(
self,
model_path=os.path.dirname(
os.path.dirname(os.path.abspath(__file__))
) + "/MACEModels/mace-mpa-0-medium.model"
):
"""Initialize the MACE potential.
Args:
model_path (str): Path to the .model file for MACE.
Defaults to a packaged model in `MACEModels/`.
"""
super().__init__()
self.pot_str = "MACE"
self.model_path = model_path
[docs]
def getASEPotentialCalculator(self):
"""Load and return the MACE ASE calculator.
Includes internal environment variable settings and warning suppression.
Returns:
mace.calculators.MACECalculator: MACE calculator instance.
"""
import os, warnings, torch
os.environ["TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD"] = "1"
warnings.filterwarnings(
"ignore",
message=(
"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."
),
)
warnings.filterwarnings(
"ignore",
message="cuequivariance or cuequivariance_torch is not available. "
"Cuequivariance acceleration will be disabled."
)
from mace.calculators import MACECalculator
device = "cuda" if torch.cuda.is_available() else "cpu"
return MACECalculator(
model_paths=self.model_path,
device=device,
default_dtype="float64",
head="default"
)