Skip to content

Commit

Permalink
Restructering/modularizyation of the node library
Browse files Browse the repository at this point in the history
Optimized import statements in nodes (pyiron_atomistics -> ase atom object) reduces import time from 5s to < 50 ms!
Implementation of universal force field M3GNet
  • Loading branch information
JNmpi committed Jan 7, 2024
1 parent d5eb20c commit c401b8b
Show file tree
Hide file tree
Showing 8 changed files with 617 additions and 345 deletions.
591 changes: 419 additions & 172 deletions notebooks/phonopy_wf.ipynb

Large diffs are not rendered by default.

214 changes: 106 additions & 108 deletions notebooks/pyiron_like_workflows.ipynb

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions pyiron_workflow/node_library/calculator/ase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from pyiron_workflow.function import single_value_node, function_node


@single_value_node()
def static(atoms=None, engine=None, _internal=None):
if engine is None:
from ase.calculators.emt import EMT

engine = EMT()

atoms.calc = engine

out = {}
# out['structure'] = atoms # not needed since identical to input
out["forces"] = atoms.get_forces()
out["energy"] = atoms.get_potential_energy()
if _internal is not None:
out["iter_index"] = _internal[
"iter_index"
] # TODO: move _internal argument to decorator class
return out


@function_node("structure", "out")
def minimize(atoms=None, engine=None, fmax=0.005, log_file="tmp.log"):
from ase.optimize import BFGS
import numpy as np

if engine is None:
from ase.calculators.emt import EMT

engine = EMT()

atoms.calc = engine

if log_file is None: # write to standard io
log_file = "-"

dyn = BFGS(atoms, logfile=log_file)
dyn.run(fmax=fmax)

# it appears that r0 is the structure of the second to last step (check)
atoms_relaxed = atoms.copy()
atoms_relaxed.calc = atoms.calc
if dyn.r0 is not None:
atoms_relaxed.positions = dyn.r0.reshape(-1, 3)

out = {}
out["relaxed_structure"] = atoms_relaxed
# out["forces"] = dyn.f0.reshape(-1, 3)
out["forces"] = atoms_relaxed.get_forces()
out["energy"] = atoms_relaxed.get_potential_energy()
out["energy_initial"] = atoms.get_potential_energy()
print("energy: ", out["energy"], "max_force: ", np.min(np.abs(out["forces"])))

return atoms_relaxed, out


nodes = [
static,
minimize,
]
22 changes: 22 additions & 0 deletions pyiron_workflow/node_library/engine/ase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from pyiron_workflow.function import single_value_node


@single_value_node("engine")
def EMT():
from ase.calculators.emt import EMT

return EMT()


@single_value_node("engine")
def M3GNet():
import matgl
from matgl.ext.ase import M3GNetCalculator

return M3GNetCalculator(matgl.load_model("M3GNet-MP-2021.2.8-PES"))


nodes = [
EMT,
M3GNet,
]
66 changes: 3 additions & 63 deletions pyiron_workflow/node_library/phonopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,18 @@ def generate_supercells(phonopy, parameters: InputPhonopyGenerateSupercells):
@function_node()
def create_phonopy(
structure,
calculator=None,
engine=None,
executor=None,
parameters=InputPhonopyGenerateSupercells(),
):
from phonopy import Phonopy
from structuretoolkit.common import atoms_to_phonopy
from pyiron_workflow.node_library import calculator

phonopy = Phonopy(unitcell=atoms_to_phonopy(structure))

cells = generate_supercells(phonopy, parameters=parameters) # .run()
gs = calc_static()
gs = calculator.ase.static(engine=engine)
df = gs.iter(atoms=cells, executor=executor)
phonopy.forces = df.forces

Expand Down Expand Up @@ -126,7 +127,6 @@ def check_consistency(phonopy, tolerance: float = 1e-10):
has_imaginary_modes = True
else:
has_imaginary_modes = False
print("alles ok")
return has_imaginary_modes


Expand All @@ -141,71 +141,11 @@ def get_total_dos(phonopy, mesh=3 * [10]):
return total_dos


@single_value_node()
def calc_static(atoms=None, engine=None, _internal=None):
# move later to other package
# print("atoms: ", atoms)
if engine is None:
from ase.calculators.emt import EMT

engine = EMT()

atoms.calc = engine

out = {}
# out['structure'] = atoms # not needed since identical to input
out["forces"] = atoms.get_forces()
out["energy"] = atoms.get_potential_energy()
if _internal is not None:
out["iter_index"] = _internal[
"iter_index"
] # TODO: move _internal argument to decorator class
return out


@function_node("structure", "out")
def calc_minimize(atoms=None, engine=None, fmax=0.005, log_file="tmp.log"):
# move later to other package
from ase.optimize import BFGS
import numpy as np

if engine is None:
from ase.calculators.emt import EMT

engine = EMT()

atoms.calc = engine

if log_file is None: # write to standard io
log_file = "-"

dyn = BFGS(atoms, logfile=log_file)
dyn.run(fmax=fmax)

# it appears that the is the structure of the second to last step (check)
atoms_relaxed = atoms.copy()
atoms_relaxed.calc = atoms.calc
if dyn.r0 is not None:
atoms_relaxed.positions = dyn.r0.reshape(-1, 3)

out = {}
out["relaxed_structure"] = atoms_relaxed
# out["forces"] = dyn.f0.reshape(-1, 3)
out["forces"] = atoms_relaxed.get_forces()
out["energy"] = atoms_relaxed.get_potential_energy()
out["energy_initial"] = atoms.get_potential_energy()
print("energy: ", out["energy"], "max_force: ", np.min(np.abs(out["forces"])))

return atoms_relaxed, out


nodes = [
# generate_supercells,
create_phonopy,
get_dynamical_matrix,
get_eigenvalues,
check_consistency,
get_total_dos,
calc_static,
calc_minimize,
]
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def bulk(
def cubic_bulk_cell(
wf, element: str, cell_size: int = 1, vacancy_index: int | None = None
):
from pyiron_workflow.node_library.structure_transform import create_vacancy, repeat
from pyiron_workflow.node_library.structure.transform import create_vacancy, repeat

wf.structure = bulk(name=element, cubic=True)
wf.cell = repeat(structure=wf.structure, repeat_scalar=cell_size)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from pyiron_workflow.function import single_value_node
from typing import Optional, Union
from pyiron_atomistics.atomistics.structure.atoms import Atoms

# Huge savings when replacing pyiron_atomistics atoms class with ase one!! (> 5s vs 40 ms)
# from pyiron_atomistics.atomistics.structure.atoms import Atoms
from ase import Atoms


@single_value_node("structure")
Expand Down

0 comments on commit c401b8b

Please sign in to comment.