-
Notifications
You must be signed in to change notification settings - Fork 6
CHGNet and Dpa3 Integration #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from ._chgnet_calculator import CHGNetCalc | ||
|
|
||
| __all__ = ["CHGNetCalc"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| """ | ||
| CHGNet calculator wrapper. Uses pretrained CHGNet via chgnet package; converts eV -> Hartree. | ||
| """ | ||
| from typing import Literal | ||
|
|
||
| from ase.calculators.calculator import all_changes | ||
| from ..calculator_base import CalcABC | ||
|
|
||
| EV2HARTREE = 1.0 / 27.211386245988 | ||
|
|
||
|
|
||
| def _get_chgnet_calculator(device): | ||
| """Build CHGNet ASE calculator (pretrained model, no local file).""" | ||
| try: | ||
| from chgnet.model import CHGNet | ||
| from chgnet.model import CHGNetCalculator | ||
| except ImportError: | ||
| raise ImportError( | ||
| "CHGNet is required. Install with: pip install chgnet" | ||
| ) from None | ||
| chgnet = CHGNet.load() | ||
| return CHGNetCalculator(potential=chgnet) | ||
|
|
||
|
|
||
| class CHGNetCalc(CalcABC): | ||
| """ASE calculator wrapping CHGNet (charge-informed graph neural network potential).""" | ||
|
|
||
| implemented_properties = ["energy", "forces", "free_energy", "stress"] | ||
|
|
||
| def __init__( | ||
| self, | ||
| device, | ||
|
||
| model: str = "chgnet", | ||
| overwrite: bool = False, | ||
| implicit: Literal["gbsa", "none"] = "gbsa", | ||
| solvent: str = "none", | ||
| ): | ||
| super().__init__() | ||
| self._chg = _get_chgnet_calculator(device) | ||
| self.device = device | ||
| self.overwrite = overwrite | ||
| self.implicit_solv_init(implicit=implicit, solvent=solvent) | ||
|
|
||
| def calculate( | ||
| self, atoms=None, properties=("energy", "forces"), system_changes=all_changes | ||
| ): | ||
| super().calculate(atoms, properties, system_changes) | ||
|
|
||
| self._chg.calculate(atoms, properties=properties, system_changes=system_changes) | ||
|
|
||
| energy_eV = self._chg.results.get("energy", 0.0) | ||
| energy = energy_eV * EV2HARTREE | ||
|
|
||
| if self.solvent_correction: | ||
| energy = energy + self.implicit_solv_energy(atoms).item() | ||
|
|
||
| self.results["energy"] = energy if isinstance(energy, float) else energy.item() | ||
| self.results["free_energy"] = self.results["energy"] | ||
|
|
||
| if "forces" in properties: | ||
| forces = self._chg.results.get("forces") | ||
| if forces is not None: | ||
| forces = forces * EV2HARTREE # eV/Å -> Hartree/Å | ||
| if self.solvent_correction: | ||
| _, solvent_force = self.implicit_solv_energy_and_force(atoms) | ||
| forces = forces + solvent_force.cpu().numpy() | ||
| self.results["forces"] = forces | ||
|
|
||
| if "stress" in properties: | ||
| stress = self._chg.results.get("stress") | ||
| if stress is not None: | ||
| self.results["stress"] = stress * EV2HARTREE # eV/ų -> Hartree/ų | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from ._dpa3_calculator import DPA3Calculator | ||
|
|
||
| __all__ = ["DPA3Calculator"] |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,86 @@ | ||||||||||||||
| """ | ||||||||||||||
| DPA3 / DeepMD-Kit calculator (PyTorch backend). | ||||||||||||||
| Uses the unified DP ASE calculator from deepmd.calculator; converts eV -> Hartree. | ||||||||||||||
| """ | ||||||||||||||
| import os | ||||||||||||||
| from typing import Literal | ||||||||||||||
|
|
||||||||||||||
| from ase.calculators.calculator import all_changes | ||||||||||||||
| from ..calculator_base import CalcABC | ||||||||||||||
|
|
||||||||||||||
| EV2HARTREE = 1.0 / 27.211386245988 | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def _get_dp_calculator(): | ||||||||||||||
| """Import DeepMD-Kit DP calculator (works with .pt/.pth DPA3 models).""" | ||||||||||||||
| try: | ||||||||||||||
| from deepmd.calculator import DP | ||||||||||||||
| return DP | ||||||||||||||
| except ImportError: | ||||||||||||||
| try: | ||||||||||||||
| from deepmd.pt.utils.ase_calc import DPCalculator as DP | ||||||||||||||
| return DP | ||||||||||||||
| except ImportError: | ||||||||||||||
| raise ImportError( | ||||||||||||||
| "DeepMD-Kit is required for DPA3. Install with: pip install deepmd-kit" | ||||||||||||||
| ) from None | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class DPA3Calculator(CalcABC): | ||||||||||||||
| """ASE calculator wrapping DeepMD-Kit DP (DPA3 and other Deep Potential models).""" | ||||||||||||||
|
|
||||||||||||||
| implemented_properties = ["energy", "forces", "free_energy"] | ||||||||||||||
|
|
||||||||||||||
| def __init__( | ||||||||||||||
| self, | ||||||||||||||
| device, | ||||||||||||||
|
||||||||||||||
| model: str = "dpa3", | ||||||||||||||
| overwrite: bool = False, | ||||||||||||||
| implicit: Literal["gbsa", "none"] = "gbsa", | ||||||||||||||
| solvent: str = "none", | ||||||||||||||
| ): | ||||||||||||||
| super().__init__() | ||||||||||||||
| DP = _get_dp_calculator() | ||||||||||||||
|
|
||||||||||||||
| model_dir = os.path.dirname(os.path.realpath(__file__)) | ||||||||||||||
| model_dir = os.path.dirname(model_dir) | ||||||||||||||
| model_dir = os.path.dirname(model_dir) | ||||||||||||||
| default_path = os.path.join(model_dir, "model", f"{model}.pt") | ||||||||||||||
| if os.path.isfile(default_path): | ||||||||||||||
| model_path = default_path | ||||||||||||||
| elif os.path.isdir(model): | ||||||||||||||
| model_path = model | ||||||||||||||
| elif os.path.isfile(model): | ||||||||||||||
| model_path = model | ||||||||||||||
| else: | ||||||||||||||
| model_path = default_path | ||||||||||||||
|
||||||||||||||
| model_path = default_path | |
| raise FileNotFoundError( | |
| f"Could not find DeepMD/DPA3 model. " | |
| f"Neither default model file '{default_path}' nor provided " | |
| f"model path '{model}' exists." | |
| ) |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The device parameter is stored but never passed to the DP calculator during instantiation. DeepMD-Kit calculators typically need to be configured with the target device to ensure the model runs on the correct device (CPU vs GPU). Check if the DP calculator accepts a device parameter and pass it during instantiation, or verify that the model file already contains device information.
| self._dp = DP(model=model_path) | |
| self._dp = DP(model=model_path, device=device) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The device parameter is accepted but never used when creating the CHGNet calculator. CHGNet.load() and CHGNetCalculator() should be configured with the device parameter to ensure the model runs on the correct device (CPU vs GPU). The device should be passed to CHGNet.load() using the use_device parameter.