-
Notifications
You must be signed in to change notification settings - Fork 10
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
Add NEBCalculator
with M3GNet and CHGNet example notebook
#13
Merged
Merged
Changes from 14 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
7af4470
Add NEB calculator
949aa09
update M3GNet loading to make M3GNet-DIRECT the default
3246622
minor bug fix
6adba18
add test
f7a4823
add documentation
92c95a1
add NEB example
62351c9
update doc
a9654fa
ruff
c0e41c7
ruff
634435d
ruff
045406c
black
c6cb569
add visualization
02675c1
remove example traj files from NEB
ecfbe15
remove NEB path cif files to save space
c86e58a
improve test coverage
910806d
bump actions/setup-python to v5, checkout to v4
janosh b4ccc73
compress to ~15% orig size
janosh 7ad8550
format and rename examples/LiFePO4-NEB.ipynb
janosh c1b724e
fix relaxor spelling and add doc str
janosh e3375e4
use enumerate in NEBCalc.calc() and document return value unit
janosh ebf2565
from_end_images rename nimages kwarg to n_images
janosh f783fe9
sort notebook imports
janosh 7b45c91
drop black for ruff format in CI
janosh f8c5652
fix TypeError: IStructure.interpolate() got an unexpected keyword arg…
janosh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
# generated using pymatgen | ||
data_LiFePO4 | ||
_symmetry_space_group_name_H-M 'P 1' | ||
_cell_length_a 10.23619605 | ||
_cell_length_b 11.94151020 | ||
_cell_length_c 9.30983438 | ||
_cell_angle_alpha 90.00002115 | ||
_cell_angle_beta 90.00000000 | ||
_cell_angle_gamma 90.00000000 | ||
_symmetry_Int_Tables_number 1 | ||
_chemical_formula_structural LiFePO4 | ||
_chemical_formula_sum 'Li16 Fe16 P16 O64' | ||
_cell_volume 1137.99355945 | ||
_cell_formula_units_Z 16 | ||
loop_ | ||
_symmetry_equiv_pos_site_id | ||
_symmetry_equiv_pos_as_xyz | ||
1 'x, y, z' | ||
loop_ | ||
_atom_site_type_symbol | ||
_atom_site_label | ||
_atom_site_symmetry_multiplicity | ||
_atom_site_fract_x | ||
_atom_site_fract_y | ||
_atom_site_fract_z | ||
_atom_site_occupancy | ||
Li Li0 1 0.00000000 0.00000000 0.00000000 1 | ||
Li Li1 1 0.00000000 0.00000000 0.50000000 1 | ||
Li Li2 1 0.00000000 0.50000000 0.00000000 1 | ||
Li Li3 1 0.00000000 0.50000000 0.50000000 1 | ||
Li Li4 1 0.50000000 0.25000000 0.25000000 1 | ||
Li Li5 1 0.50000000 0.25000000 0.75000000 1 | ||
Li Li6 1 0.50000000 0.75000000 0.25000000 1 | ||
Li Li7 1 0.50000000 0.75000000 0.75000000 1 | ||
Li Li8 1 0.50000000 0.00000000 0.25000000 1 | ||
Li Li9 1 0.50000000 0.00000000 0.75000000 1 | ||
Li Li10 1 0.50000000 0.50000000 0.25000000 1 | ||
Li Li11 1 0.50000000 0.50000000 0.75000000 1 | ||
Li Li12 1 0.00000000 0.25000000 0.00000000 1 | ||
Li Li13 1 0.00000000 0.25000000 0.50000000 1 | ||
Li Li14 1 0.00000000 0.75000000 0.00000000 1 | ||
Li Li15 1 0.00000000 0.75000000 0.50000000 1 | ||
Fe Fe16 1 0.78115127 0.12500000 0.26493287 1 | ||
Fe Fe17 1 0.78115127 0.12500000 0.76493287 1 | ||
Fe Fe18 1 0.78115127 0.62500000 0.26493287 1 | ||
Fe Fe19 1 0.78115127 0.62500000 0.76493287 1 | ||
Fe Fe20 1 0.71884873 0.37500000 0.01493386 1 | ||
Fe Fe21 1 0.71884873 0.37500000 0.51493387 1 | ||
Fe Fe22 1 0.71884873 0.87500000 0.01493387 1 | ||
Fe Fe23 1 0.71884873 0.87500000 0.51493386 1 | ||
Fe Fe24 1 0.28115127 0.12500000 0.48506664 1 | ||
Fe Fe25 1 0.28115127 0.12500000 0.98506664 1 | ||
Fe Fe26 1 0.28115127 0.62500000 0.48506663 1 | ||
Fe Fe27 1 0.28115127 0.62500000 0.98506664 1 | ||
Fe Fe28 1 0.21884873 0.37500000 0.23506663 1 | ||
Fe Fe29 1 0.21884873 0.37500000 0.73506664 1 | ||
Fe Fe30 1 0.21884873 0.87500000 0.23506663 1 | ||
Fe Fe31 1 0.21884873 0.87500000 0.73506664 1 | ||
P P32 1 0.09386630 0.12500000 0.20931129 1 | ||
P P33 1 0.09386630 0.12500000 0.70931129 1 | ||
P P34 1 0.09386630 0.62500000 0.20931129 1 | ||
P P35 1 0.09386630 0.62500000 0.70931128 1 | ||
P P36 1 0.40613370 0.37500000 0.45931179 1 | ||
P P37 1 0.40613370 0.37500000 0.95931179 1 | ||
P P38 1 0.40613370 0.87500000 0.45931178 1 | ||
P P39 1 0.40613370 0.87500000 0.95931179 1 | ||
P P40 1 0.59386630 0.12500000 0.04068771 1 | ||
P P41 1 0.59386630 0.12500000 0.54068772 1 | ||
P P42 1 0.59386630 0.62500000 0.04068771 1 | ||
P P43 1 0.59386630 0.62500000 0.54068772 1 | ||
P P44 1 0.90613370 0.37500000 0.29068822 1 | ||
P P45 1 0.90613370 0.37500000 0.79068822 1 | ||
P P46 1 0.90613370 0.87500000 0.29068822 1 | ||
P P47 1 0.90613370 0.87500000 0.79068821 1 | ||
O O48 1 0.09423067 0.12500000 0.37239328 1 | ||
O O49 1 0.09423067 0.12500000 0.87239328 1 | ||
O O50 1 0.09423067 0.62500000 0.37239328 1 | ||
O O51 1 0.09423067 0.62500000 0.87239328 1 | ||
O O52 1 0.83415452 0.27277902 0.35686767 1 | ||
O O53 1 0.83415452 0.27277902 0.85686767 1 | ||
O O54 1 0.83415452 0.77277902 0.35686767 1 | ||
O O55 1 0.83415452 0.77277902 0.85686767 1 | ||
O O56 1 0.83415452 0.47722149 0.35686767 1 | ||
O O57 1 0.83415452 0.47722149 0.85686767 1 | ||
O O58 1 0.83415452 0.97722149 0.35686767 1 | ||
O O59 1 0.83415452 0.97722149 0.85686767 1 | ||
O O60 1 0.90577033 0.37500000 0.12760722 1 | ||
O O61 1 0.90577033 0.37500000 0.62760722 1 | ||
O O62 1 0.90577033 0.87500000 0.12760722 1 | ||
O O63 1 0.90577033 0.87500000 0.62760722 1 | ||
O O64 1 0.04430856 0.37500000 0.35493223 1 | ||
O O65 1 0.04430856 0.37500000 0.85493222 1 | ||
O O66 1 0.04430856 0.87500000 0.35493223 1 | ||
O O67 1 0.04430856 0.87500000 0.85493223 1 | ||
O O68 1 0.40576933 0.37500000 0.12239278 1 | ||
O O69 1 0.40576933 0.37500000 0.62239278 1 | ||
O O70 1 0.40576933 0.87500000 0.12239278 1 | ||
O O71 1 0.40576933 0.87500000 0.62239278 1 | ||
O O72 1 0.16584548 0.02277901 0.14313283 1 | ||
O O73 1 0.16584548 0.02277902 0.64313283 1 | ||
O O74 1 0.16584548 0.52277902 0.14313283 1 | ||
O O75 1 0.16584548 0.52277902 0.64313283 1 | ||
O O76 1 0.66584548 0.22722098 0.10686717 1 | ||
O O77 1 0.66584548 0.22722098 0.60686717 1 | ||
O O78 1 0.66584548 0.72722098 0.10686717 1 | ||
O O79 1 0.66584548 0.72722098 0.60686717 1 | ||
O O80 1 0.33415452 0.27277902 0.39313283 1 | ||
O O81 1 0.33415452 0.27277902 0.89313283 1 | ||
O O82 1 0.33415452 0.77277902 0.39313283 1 | ||
O O83 1 0.33415452 0.77277902 0.89313283 1 | ||
O O84 1 0.33415452 0.47722149 0.39313283 1 | ||
O O85 1 0.33415452 0.47722149 0.89313283 1 | ||
O O86 1 0.33415452 0.97722149 0.39313283 1 | ||
O O87 1 0.33415452 0.97722149 0.89313283 1 | ||
O O88 1 0.95569144 0.12500000 0.14506827 1 | ||
O O89 1 0.95569144 0.12500000 0.64506827 1 | ||
O O90 1 0.95569144 0.62500000 0.14506827 1 | ||
O O91 1 0.95569144 0.62500000 0.64506828 1 | ||
O O92 1 0.45569144 0.12500000 0.10493173 1 | ||
O O93 1 0.45569144 0.12500000 0.60493173 1 | ||
O O94 1 0.45569144 0.62500000 0.10493173 1 | ||
O O95 1 0.45569144 0.62500000 0.60493173 1 | ||
O O96 1 0.54430856 0.37500000 0.39506877 1 | ||
O O97 1 0.54430856 0.37500000 0.89506878 1 | ||
O O98 1 0.54430856 0.87500000 0.39506877 1 | ||
O O99 1 0.54430856 0.87500000 0.89506878 1 | ||
O O100 1 0.59423067 0.12500000 0.37760772 1 | ||
O O101 1 0.59423067 0.12500000 0.87760772 1 | ||
O O102 1 0.59423067 0.62500000 0.37760772 1 | ||
O O103 1 0.59423067 0.62500000 0.87760772 1 | ||
O O104 1 0.66584548 0.02277901 0.10686717 1 | ||
O O105 1 0.66584548 0.02277901 0.60686717 1 | ||
O O106 1 0.66584548 0.52277902 0.10686717 1 | ||
O O107 1 0.66584548 0.52277902 0.60686717 1 | ||
O O108 1 0.16584548 0.22722098 0.14313283 1 | ||
O O109 1 0.16584548 0.22722099 0.64313283 1 | ||
O O110 1 0.16584548 0.72722098 0.14313283 1 | ||
O O111 1 0.16584548 0.72722098 0.64313283 1 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
"""NEB calculations.""" | ||
from __future__ import annotations | ||
|
||
import os | ||
from inspect import isclass | ||
from typing import TYPE_CHECKING | ||
|
||
from ase import optimize | ||
from ase.io import Trajectory | ||
from ase.neb import NEB, NEBTools | ||
from ase.optimize.optimize import Optimizer | ||
from pymatgen.core import Structure | ||
from pymatgen.io.ase import AseAtomsAdaptor | ||
|
||
from matcalc.base import PropCalc | ||
from matcalc.util import get_universal_calculator | ||
|
||
if TYPE_CHECKING: | ||
from ase.calculators.calculator import Calculator | ||
|
||
|
||
class NEBCalc(PropCalc): | ||
"""Nudged Elastic Band calculator.""" | ||
|
||
def __init__( | ||
self, | ||
images: list[Structure], | ||
calculator: str | Calculator = "M3GNet-MP-2021.2.8-DIRECT-PES", | ||
optimizer: str | Optimizer = "BFGS", | ||
traj_folder: str | None = None, | ||
interval: int = 1, | ||
climb: bool = True, | ||
**kwargs, | ||
): | ||
""" | ||
Args: | ||
images(list): A list of pymatgen structures as NEB image structures. | ||
calculator(str|Calculator): ASE Calculator to use. Default to M3GNet-MP-2021.2.8-DIRECT-PES. | ||
optimizer(str|Optimizer): The optimization algorithm. Defaults to "BEGS". | ||
traj_folder(str|None): The folder address to store NEB trajectories. Default to None. | ||
interval(int): The step interval for saving the trajectories. Defaults to 1. | ||
climb(bool): Whether to enable climb image NEB. Default to True. | ||
kwargs: Other arguments passed to ASE NEB object. | ||
""" | ||
self.images = images | ||
self.calculator = calculator | ||
|
||
# check str is valid optimizer key | ||
def is_ase_optimizer(key): | ||
return isclass(obj := getattr(optimize, key)) and issubclass(obj, Optimizer) | ||
|
||
valid_keys = [key for key in dir(optimize) if is_ase_optimizer(key)] | ||
if isinstance(optimizer, str) and optimizer not in valid_keys: | ||
raise ValueError(f"Unknown {optimizer=}, must be one of {valid_keys}") | ||
|
||
self.optimizer: Optimizer = getattr(optimize, optimizer) if isinstance(optimizer, str) else optimizer | ||
self.traj_folder = traj_folder | ||
self.interval = interval | ||
self.climb = climb | ||
|
||
self.images = [] | ||
for image in images: | ||
if isinstance(image, Structure): | ||
atoms = AseAtomsAdaptor().get_atoms(image) | ||
atoms.calc = get_universal_calculator(self.calculator) | ||
self.images.append(atoms) | ||
|
||
self.neb = NEB( | ||
self.images, | ||
climb=self.climb, | ||
allow_shared_calculator=True, | ||
**kwargs, | ||
) | ||
self.optimizer = self.optimizer(self.neb) | ||
|
||
@classmethod | ||
def from_end_images( | ||
cls, | ||
start_struct: Structure, | ||
end_struct: Structure, | ||
calculator: str | Calculator = "M3GNet-MP-2021.2.8-DIRECT-PES", | ||
nimages: int = 7, | ||
interpolate_lattices: bool = False, | ||
autosort_tol: float = 0.5, | ||
**kwargs, | ||
): | ||
""" | ||
Initialize a NEBCalc from end images. | ||
|
||
Args: | ||
start_struct(Structure): The starting image as a pymatgen Structure. | ||
end_struct(Structure): The ending image as a pymatgen Structure. | ||
calculator(str|Calculator): ASE Calculator to use. Default to M3GNet-MP-2021.2.8-DIRECT-PES. | ||
nimages(int): The number of intermediate image structures to create. | ||
interpolate_lattices(bool): Whether to interpolate the lattices when creating NEB | ||
path with Structure.interpolate() in pymatgen. Default to False. | ||
autosort_tol(float): A distance tolerance in angstrom in which to automatically | ||
sort end_struct to match to the closest points in start_struct. This | ||
argument is required for Structure.interpolate() in pymatgen. | ||
Default to 0.5. | ||
kwargs: Other arguments passed to construct NEBCalc. | ||
""" | ||
images = start_struct.interpolate( | ||
end_struct, | ||
nimages=nimages + 1, | ||
interpolate_lattices=interpolate_lattices, | ||
pbc=False, | ||
autosort_tol=autosort_tol, | ||
) | ||
return cls(images=images, calculator=calculator, **kwargs) | ||
|
||
def calc( | ||
self, | ||
fmax: float = 0.1, | ||
max_steps: int = 1000, | ||
): | ||
""" | ||
Perform NEB calculation. | ||
|
||
Args: | ||
fmax (float): Convergence criteria for NEB calculations defined by Max forces. | ||
Default to 0.1 eV/Angstrom. | ||
max_steps (int): Maximum number of steps in NEB calculations. Default to 1000. | ||
|
||
Returns: | ||
NEB barrier. | ||
""" | ||
if self.traj_folder is not None: | ||
os.makedirs(self.traj_folder, exist_ok=True) | ||
for i in range(len(self.images)): | ||
self.optimizer.attach( | ||
Trajectory(f"{self.traj_folder}/image_{i}.traj", "w", self.images[i]), | ||
interval=self.interval, | ||
) | ||
self.optimizer.run(fmax=fmax, steps=max_steps) | ||
neb_tool = NEBTools(self.neb.images) | ||
return neb_tool.get_barrier() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from __future__ import annotations | ||
|
||
import pytest | ||
|
||
from matcalc.neb import NEBCalc | ||
|
||
|
||
def test_neb_calc(LiFePO4, M3GNetCalc): | ||
"""Tests for NEBCalc class""" | ||
image_start = LiFePO4.copy() | ||
image_start.remove_sites([2]) | ||
image_end = LiFePO4.copy() | ||
image_end.remove_sites([3]) | ||
NEBcalc = NEBCalc.from_end_images(image_start, image_end, M3GNetCalc, nimages=5) | ||
barriers = NEBcalc.calc(fmax=0.5) | ||
print(barriers) | ||
assert len(NEBcalc.neb.images) == 7 | ||
assert barriers[0] == pytest.approx(0.0184783935546875, rel=0.002) | ||
assert barriers[1] == pytest.approx(0.0018920898, rel=0.002) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
We need some more tests for uncovered lines (see the codecov comments).