diff --git a/abics/applications/latgas_abinitio_interface/__init__.py b/abics/applications/latgas_abinitio_interface/__init__.py index 572df28c..d6fc44cc 100644 --- a/abics/applications/latgas_abinitio_interface/__init__.py +++ b/abics/applications/latgas_abinitio_interface/__init__.py @@ -25,8 +25,14 @@ register_solver("aenet", "AenetSolver", "abics.applications.latgas_abinitio_interface.aenet") register_solver("nequip", "NequipSolver", "abics.applications.latgas_abinitio_interface.nequip") register_solver("mlip_3", "MLIP3Solver", "abics.applications.latgas_abinitio_interface.mlip_3") +register_solver("sevennet", "SevennetSolver", "abics.applications.latgas_abinitio_interface.sevennet") +register_solver("mace", "MaceSolver", "abics.applications.latgas_abinitio_interface.mace") +register_solver("chgnet", "CHGNetSolver", "abics.applications.latgas_abinitio_interface.chgnet") register_solver("User", "UserFunctionSolver", "abics.applications.latgas_abinitio_interface.user_function_solver") register_trainer("aenet", "AenetTrainer", "abics.applications.latgas_abinitio_interface.aenet_trainer") register_trainer("nequip", "NequipTrainer", "abics.applications.latgas_abinitio_interface.nequip_trainer") -register_trainer("mlip_3", "MLIP3Trainer", "abics.applications.latgas_abinitio_interface.mlip_3_trainer") \ No newline at end of file +register_trainer("mlip_3", "MLIP3Trainer", "abics.applications.latgas_abinitio_interface.mlip_3_trainer") +register_trainer("sevennet", "SevennetTrainer", "abics.applications.latgas_abinitio_interface.sevennet_trainer") +register_trainer("mace", "MaceTrainer", "abics.applications.latgas_abinitio_interface.mace_trainer") +register_trainer("chgnet", "CHGNetTrainer", "abics.applications.latgas_abinitio_interface.chgnet_trainer") \ No newline at end of file diff --git a/abics/applications/latgas_abinitio_interface/chgnet.py b/abics/applications/latgas_abinitio_interface/chgnet.py new file mode 100644 index 00000000..7e8a07ed --- /dev/null +++ b/abics/applications/latgas_abinitio_interface/chgnet.py @@ -0,0 +1,217 @@ +# ab-Initio Configuration Sampling tool kit (abICS) +# Copyright (C) 2019- The University of Tokyo +# +# abICS wrapper of CHGNet +# Yusuke Konishi (Academeia Co., Ltd.) 2025 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +""" +energy calculator using sevennet python interface +""" + +from __future__ import annotations + +import os.path +from collections import namedtuple +from pymatgen.core import Structure +import torch +from ase import Atoms +from ase.calculators.calculator import Calculator +from nequip.utils import Config +from chgnet.model.model import CHGNet +from chgnet.model import StructOptimizer + +from .base_solver import SolverBase, register_solver +from .params import ALParams, DFTParams + +class CHGNetSolver(SolverBase): + """ + Nequip solver + + Attributes + ---------- + path_to_solver : str + Path to the solver + input : NequipSolver.Input + Input manager + output : NequipSolver.Output + Output manager + """ + + def __init__(self, ignore_species, use_pretrained, relax, fmax, device): + """ + Initialize the solver. + + """ + + super(CHGNetSolver, self).__init__("") + self.path_to_solver = self.calculate_energy + self.input = CHGNetSolver.Input(ignore_species, use_pretrained, relax, fmax, device) + self.output = CHGNetSolver.Output() + + def name(self): + return "chgnet" + + def calculate_energy(self, fi, output_dir): + st = self.input.st + + if self.input.relax: + result = self.input.calculator.relax(atoms=st, fmax=self.input.fmax) + # Get predicted energy + ene = result["trajectory"].energies[-1] + else: + comp = st.composition.as_dict() + num_atoms = sum([comp[key] for key in comp.keys()]) + ene = self.input.calculator.predict_structure(st)["e"]*num_atoms + + self.output.st = st + self.output.ene = ene + + class Input(object): + """ + Input manager for Mock + + Attributes + ---------- + st : pymatgen.Structure + structure + """ + + st: Structure + + def __init__(self, ignore_species=None, use_pretrained=True, relax=True, fmax=0.1, device="cpu"): + self.ignore_species = ignore_species + self.use_pretrained = use_pretrained + self.relax = relax + self.fmax = fmax + self.device = device + # self.st = Structure() + + def from_directory(self, base_input_dir): + """ + + Parameters + ---------- + base_input_dir : str + Path to the directory including base input files. + """ + self.base_input_dir = base_input_dir + model_file = os.path.join(base_input_dir, "deployed.pth.tar") + if not self.use_pretrained: + if self.relax: + model = CHGNet.from_file(model_file) + self.calculator = StructOptimizer(model=model, optimizer_class="BFGS") + else: + self.calculator = CHGNet.from_file(model_file) + else: + if self.relax: + model = CHGNet.load(use_device=self.device) + self.calculator = StructOptimizer(model=model, optimizer_class="BFGS") + else: + self.calculator = CHGNet.load(use_device=self.device) + + def update_info_by_structure(self, structure): + """ + Update information by structure file + + Parameters + ---------- + structure : pymatgen.Structure + Atomic structure + """ + self.st = structure.copy() + if self.ignore_species is not None: + self.st.remove_species(self.ignore_species) + + def update_info_from_files(self, workdir, rerun): + """ + Do nothing + """ + pass + + def write_input(self, output_dir): + """ + Generate input files of the solver program. + + Parameters + ---------- + workdir : str + Path to working directory. + """ + if not os.path.exists(output_dir): + import shutil + + shutil.copytree(self.base_input_dir, output_dir) + + # self.st.to("POSCAR", os.path.join(output_dir, "structure.vasp")) + + def cl_args(self, nprocs, nthreads, workdir): + """ + Generate command line argument of the solver program. + + Parameters + ---------- + nprocs : int + The number of processes. + nthreads : int + The number of threads. + workdir : str + Path to the working directory. + + Returns + ------- + args : list[str] + Arguments of command + """ + return [workdir] + + class Output(object): + """ + Output manager. + """ + + def __init__(self): + pass + + def get_results(self, workdir): + """ + Get energy and structure obtained by the solver program. + + Parameters + ---------- + workdir : str + Path to the working directory. + + Returns + ------- + phys : named_tuple("energy", "structure") + Total energy and atomic structure. + The energy is measured in the units of eV + and coodinates is measured in the units of Angstrom. + """ + Phys = namedtuple("PhysVaules", ("energy", "structure")) + return Phys(self.ene, self.st) + + def solver_run_schemes(self): + return ("function",) + + @classmethod + def create(cls, params: ALParams | DFTParams): + ignore_species = params.ignore_species + use_pretrained = params.use_pretrained + relax = params.relax + fmax = params.fmax + device = params.device + return cls(ignore_species, use_pretrained, relax, fmax, device) diff --git a/abics/applications/latgas_abinitio_interface/chgnet_trainer.py b/abics/applications/latgas_abinitio_interface/chgnet_trainer.py new file mode 100644 index 00000000..6196af3d --- /dev/null +++ b/abics/applications/latgas_abinitio_interface/chgnet_trainer.py @@ -0,0 +1,237 @@ +# ab-Initio Configuration Sampling tool kit (abICS) +# Copyright (C) 2019- The University of Tokyo +# +# abICS wrapper of CHGNet solver +# Masashi Noda, Yusuke Konishi (Academeia Co., Ltd.) 2025 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +from __future__ import annotations +from typing import Sequence, Dict + +import numpy as np +import os, pathlib, shutil, subprocess, shlex +import time + +from pymatgen.core import Structure + +from abics.util import expand_cmd_path +from abics.applications.latgas_abinitio_interface.base_trainer import TrainerBase +from abics.applications.latgas_abinitio_interface.util import structure_to_XSF + +import ase +from ase import io +from ase.calculators.singlepoint import SinglePointCalculator +from ase.calculators.morse import MorsePotential + +from chgnet.model.model import CHGNet +from chgnet.data.dataset import StructureData, get_train_val_test_loader +from chgnet.trainer import Trainer + +import yaml + +def xsf_to_ase(xsf): + ase_xsf = ase.io.read(xsf) + with open(xsf) as f: + lines = f.readlines() + + tot_energy = float(lines[0].split()[4]) + ase_xsf.calc = SinglePointCalculator(energy=tot_energy, atoms=ase_xsf) + ase_xsf.calc = MorsePotential() + forces = ase_xsf.get_forces() + ase_xsf.calc = SinglePointCalculator(energy=tot_energy, forces=forces, atoms=ase_xsf) + return ase_xsf + +class CHGNetTrainer(TrainerBase): + def __init__( + self, + structures: Sequence[Structure], + energies: Sequence[float], + generate_inputdir: os.PathLike, + train_inputdir: os.PathLike, + predict_inputdir: os.PathLike, + execute_commands: Dict, + # trainer_type: str, + ): + self.structures = structures + self.energies = energies + self.generate_inputdir = generate_inputdir + self.train_inputdir = train_inputdir + self.predict_inputdir = predict_inputdir + #train_exe = execute_commands["train"] + #self.train_exe = [expand_cmd_path(e) for e in shlex.split(train_exe)] + assert len(self.structures) == len(self.energies) + self.numdata = len(self.structures) + self.is_prepared = False + self.is_trained = False + self.generate_outputdir = None + self.chgnet_params = { + "finetuning" : True, + "batch_size" : 4, + "train_ratio" : 0.9, + "val_ratio" : 0.05, + "learning_rate" : 1e-2, + "epochs" : 5, + "model_params" : { + "atom_fea_dim" : 64 + } + } + yaml_file = os.path.join(train_inputdir, "input.yaml") + with open(yaml_file, "r") as f: + self.chgnet_params.update(yaml.safe_load(f)) + if "mlp_hidden_dims" in self.chgnet_params["model_params"].keys(): + self.chgnet_params["model_params"]["mlp_hidden_dims"] = tuple(self.chgnet_params["model_params"]["mlp_hidden_dims"]) + # self.trainer_type = trainer_type + + def prepare(self, latgas_mode = True, st_dir = "chgnetXSF"): + rootdir = os.getcwd() + xsfdir = os.path.join(rootdir, st_dir) + + # prepare XSF files for nequip + os.makedirs(xsfdir, exist_ok=True) + os.chdir(xsfdir) + xsfdir = os.getcwd() + if latgas_mode: + for i, st in enumerate(self.structures): + xsf_string = structure_to_XSF(st, write_force_zero=False) + xsf_string = ( + "# total energy = {} eV\n\n".format(self.energies[i]) + xsf_string + ) + with open("structure.{}.xsf".format(i), "w") as fi: + fi.write(xsf_string) + else: + for i, st in enumerate(self.structures): + xsf_string = structure_to_XSF(st, write_force_zero=False) + xsf_string = ( + "# total energy = {} eV\n\n".format(self.energies[i]) + xsf_string + ) + with open("structure.{}.xsf".format(i), "w") as fi: + fi.write(xsf_string) + + os.chdir(rootdir) + + def generate_run(self, xsfdir="chgnetXSF", generate_dir="generate"): + # prepare generate + xsfdir = str(pathlib.Path(xsfdir).resolve()) + if os.path.exists(generate_dir): + shutil.rmtree(generate_dir) + shutil.copytree(self.generate_inputdir, generate_dir) + os.makedirs(generate_dir, exist_ok=True) + self.generate_dir = generate_dir + os.chdir(generate_dir) + xsf_paths = [ + os.path.join(xsfdir, "structure.{}.xsf".format(i)) + for i in range(self.numdata) + ] + ases = [xsf_to_ase(xsf) for xsf in xsf_paths] + #generate structure.xyz + ase.io.write("structure.xyz", ases) + self.generate_outputdir = os.getcwd() + os.chdir(pathlib.Path(os.getcwd()).parent) + + def generate_wait(self): + interval = 0.1 # sec + self.is_prepared = False + if os.path.exists(os.path.join(self.generate_outputdir, "structure.xyz")): + self.is_prepared = True + time.sleep(interval) + if not self.is_prepared: + raise RuntimeError(f"{self.generate_outputdir}") + + def train(self, train_dir = "train"): + if not self.is_prepared: + raise RuntimeError("you have to prepare the trainer before training!") + if os.path.exists(train_dir): + shutil.rmtree(train_dir) + shutil.copytree(self.train_inputdir, train_dir) + os.chdir(train_dir) + + os.rename( + os.path.join(self.generate_outputdir, "structure.xyz"), + os.path.join(os.getcwd(), "structure.xyz"), + ) + + # read structure.xyz as ase + atoms_list = io.read("structure.xyz", index=":") + # make Structure list and energy list + structures = [] + energies = [] + forces = [] + + for atoms in atoms_list: + # Convert ASE Atoms -> Pymatgen Structure + structure = Structure( + lattice=atoms.get_cell(), + species=atoms.get_chemical_symbols(), + coords=atoms.get_positions(), + coords_are_cartesian=True + ) + structures.append(structure) + + comp = structure.composition.as_dict() + num_atoms = sum([comp[key] for key in comp.keys()]) + + # Get energy and force + energy = atoms.get_potential_energy() + force = atoms.get_forces() + energies.append(energy/num_atoms) + forces.append(force) + + dataset = StructureData( + structures=structures, + energies=energies, + forces = forces, + ) + train_loader, val_loader, test_loader = get_train_val_test_loader( + dataset, + batch_size=self.chgnet_params["batch_size"], + train_ratio=self.chgnet_params["train_ratio"], + val_ratio=self.chgnet_params["val_ratio"] + ) + if self.chgnet_params["finetuning"]: + chgnet = CHGNet(atom_graph_cutoff=7.5, bond_graph_cutoff=6.0) + chgnet = chgnet.load() + else: + chgnet = CHGNet( + **self.chgnet_params["model_params"] + ) + + trainer = Trainer( + model=chgnet, + targets="ef", + force_loss_ratio=0.0, + optimizer="Adam", + criterion="MSE", + learning_rate=self.chgnet_params["learning_rate"], + epochs=self.chgnet_params["epochs"], + ) + trainer.train(train_loader, val_loader, test_loader, save_dir="chgnet_out", train_composition_model=True) + + os.chdir(pathlib.Path(os.getcwd()).parent) + self.is_trained = True + + def new_baseinput(self, baseinput_dir, train_dir = "train"): + try: + assert self.is_trained + except AssertionError as e: + e.args += "you have to train before getting results!" + + baseinput = str(pathlib.Path(baseinput_dir).resolve()) + os.makedirs(baseinput, exist_ok=True) + shutil.copy(os.path.join(train_dir,"input.yaml"),baseinput) + os.chdir(train_dir) + # Search for bestE_*.pth.tar in the chgnet_out directory and copy the 0th as deployed.pth.tar + bestE_files = [f for f in os.listdir("chgnet_out") if f.startswith("bestE")] + shutil.copy(os.path.join("chgnet_out",bestE_files[0]),os.path.join(baseinput,"deployed.pth.tar")) + os.chdir(pathlib.Path(os.getcwd()).parent) diff --git a/abics/applications/latgas_abinitio_interface/mace.py b/abics/applications/latgas_abinitio_interface/mace.py new file mode 100644 index 00000000..a5b3cb7d --- /dev/null +++ b/abics/applications/latgas_abinitio_interface/mace.py @@ -0,0 +1,216 @@ +# ab-Initio Configuration Sampling tool kit (abICS) +# Copyright (C) 2019- The University of Tokyo +# +# abICS wrapper of Mace +# Masashi Noda, Yusuke Konishi (Academeia Co., Ltd.) 2025 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +""" +energy calculator using mace python interface +""" + +from __future__ import annotations + +import os.path +from collections import namedtuple +import numpy as np +from pymatgen.core import Structure +import torch +from ase import Atoms +from ase.optimize import BFGS +from nequip.data import AtomicDataDict, AtomicData +from nequip.utils import Config +from mace.calculators import mace_mp, MACECalculator + +from .base_solver import SolverBase, register_solver +from .params import ALParams, DFTParams + + +class MaceSolver(SolverBase): + """ + Nequip solver + + Attributes + ---------- + path_to_solver : str + Path to the solver + input : NequipSolver.Input + Input manager + output : NequipSolver.Output + Output manager + """ + + def __init__(self, ignore_species, use_pretrained, relax, fmax, device): + """ + Initialize the solver. + + """ + + super(MaceSolver, self).__init__("") + self.path_to_solver = self.calculate_energy + self.input = MaceSolver.Input(ignore_species, use_pretrained, relax, fmax, device) + self.output = MaceSolver.Output() + + def name(self): + return "mace" + + def calculate_energy(self, fi, output_dir): + st = self.input.st + symbols = [site.specie.symbol for site in st] + positions = [site.coords for site in st] + pbc = (True, True, True) + cell = st.lattice.matrix + atoms = Atoms(symbols=symbols, positions=positions, pbc=pbc, cell=cell) + + atoms.calc = self.input.calculator + + if self.input.relax: + opt = BFGS(atoms) + opt.run(fmax=self.input.fmax) + + # Get predicted energy + ene = atoms.get_total_energy() + + self.output.st = st + self.output.ene = ene + + class Input(object): + """ + Input manager for Mock + + Attributes + ---------- + st : pymatgen.Structure + structure + """ + + st: Structure + + def __init__(self, ignore_species=None, use_pretrained=False, relax=False, fmax=0.1, device="cpu"): + self.ignore_species = ignore_species + self.use_pretrained = use_pretrained + self.relax = relax + self.fmax = fmax + self.device = device + # self.st = Structure() + + def from_directory(self, base_input_dir): + """ + + Parameters + ---------- + base_input_dir : str + Path to the directory including base input files. + """ + self.base_input_dir = base_input_dir + if not self.use_pretrained: + self.model = os.path.join(base_input_dir, "deployed.model") + self.calculator = MACECalculator(model_paths=self.model, device=self.device, default_dtype="float64") + else: + self.calculator = mace_mp(device=self.device, default_dtype="float64") + + def update_info_by_structure(self, structure): + """ + Update information by structure file + + Parameters + ---------- + structure : pymatgen.Structure + Atomic structure + """ + self.st = structure.copy() + if self.ignore_species is not None: + self.st.remove_species(self.ignore_species) + + def update_info_from_files(self, workdir, rerun): + """ + Do nothing + """ + pass + + def write_input(self, output_dir): + """ + Generate input files of the solver program. + + Parameters + ---------- + workdir : str + Path to working directory. + """ + if not os.path.exists(output_dir): + import shutil + + shutil.copytree(self.base_input_dir, output_dir) + + # self.st.to("POSCAR", os.path.join(output_dir, "structure.vasp")) + + def cl_args(self, nprocs, nthreads, workdir): + """ + Generate command line argument of the solver program. + + Parameters + ---------- + nprocs : int + The number of processes. + nthreads : int + The number of threads. + workdir : str + Path to the working directory. + + Returns + ------- + args : list[str] + Arguments of command + """ + return [workdir] + + class Output(object): + """ + Output manager. + """ + + def __init__(self): + pass + + def get_results(self, workdir): + """ + Get energy and structure obtained by the solver program. + + Parameters + ---------- + workdir : str + Path to the working directory. + + Returns + ------- + phys : named_tuple("energy", "structure") + Total energy and atomic structure. + The energy is measured in the units of eV + and coodinates is measured in the units of Angstrom. + """ + Phys = namedtuple("PhysVaules", ("energy", "structure")) + return Phys(self.ene, self.st) + + def solver_run_schemes(self): + return ("function",) + + @classmethod + def create(cls, params: ALParams | DFTParams): + ignore_species = params.ignore_species + use_pretrained = params.use_pretrained + relax = params.relax + fmax = params.fmax + device = params.device + return cls(ignore_species, use_pretrained, relax, fmax, device) diff --git a/abics/applications/latgas_abinitio_interface/mace_trainer.py b/abics/applications/latgas_abinitio_interface/mace_trainer.py new file mode 100644 index 00000000..58ce7ba9 --- /dev/null +++ b/abics/applications/latgas_abinitio_interface/mace_trainer.py @@ -0,0 +1,165 @@ +# ab-Initio Configuration Sampling tool kit (abICS) +# Copyright (C) 2019- The University of Tokyo +# +# abICS wrapper of Mace solver +# Masashi Noda, Yusuke Konishi (Academeia Co., Ltd.) 2025 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +from __future__ import annotations +from typing import Sequence, Dict + +import numpy as np +import os, pathlib, shutil, subprocess, shlex +import time + +from pymatgen.core import Structure + +from abics.util import expand_cmd_path +from abics.applications.latgas_abinitio_interface.base_trainer import TrainerBase +from abics.applications.latgas_abinitio_interface.util import structure_to_XSF + +import ase +from ase import io +from ase.calculators.singlepoint import SinglePointCalculator + +import yaml + +def xsf_to_ase(xsf): + ase_xsf = ase.io.read(xsf) + with open(xsf) as f: + lines = f.readlines() + + tot_energy = float(lines[0].split()[4]) + ase_xsf.calc = SinglePointCalculator(energy=tot_energy, atoms=ase_xsf) + return ase_xsf + +class MaceTrainer(TrainerBase): + def __init__( + self, + structures: Sequence[Structure], + energies: Sequence[float], + generate_inputdir: os.PathLike, + train_inputdir: os.PathLike, + predict_inputdir: os.PathLike, + execute_commands: Dict, + # trainer_type: str, + ): + self.structures = structures + self.energies = energies + self.generate_inputdir = generate_inputdir + self.train_inputdir = train_inputdir + self.predict_inputdir = predict_inputdir + train_exe = execute_commands["train"] + self.train_exe = [expand_cmd_path(e) for e in shlex.split(train_exe)] + self.train_exe.append("--config=input.yaml") + assert len(self.structures) == len(self.energies) + self.numdata = len(self.structures) + self.is_prepared = False + self.is_trained = False + self.generate_outputdir = None + # self.trainer_type = trainer_type + + def prepare(self, latgas_mode = True, st_dir = "maceXSF"): + rootdir = os.getcwd() + xsfdir = os.path.join(rootdir, st_dir) + + # prepare XSF files for nequip + os.makedirs(xsfdir, exist_ok=True) + os.chdir(xsfdir) + xsfdir = os.getcwd() + if latgas_mode: + for i, st in enumerate(self.structures): + xsf_string = structure_to_XSF(st, write_force_zero=False) + xsf_string = ( + "# total energy = {} eV\n\n".format(self.energies[i]) + xsf_string + ) + with open("structure.{}.xsf".format(i), "w") as fi: + fi.write(xsf_string) + else: + for i, st in enumerate(self.structures): + xsf_string = structure_to_XSF(st, write_force_zero=False) + xsf_string = ( + "# total energy = {} eV\n\n".format(self.energies[i]) + xsf_string + ) + with open("structure.{}.xsf".format(i), "w") as fi: + fi.write(xsf_string) + + os.chdir(rootdir) + + def generate_run(self, xsfdir="maceXSF", generate_dir="generate"): + # prepare generate + xsfdir = str(pathlib.Path(xsfdir).resolve()) + if os.path.exists(generate_dir): + shutil.rmtree(generate_dir) + shutil.copytree(self.generate_inputdir, generate_dir) + os.makedirs(generate_dir, exist_ok=True) + self.generate_dir = generate_dir + os.chdir(generate_dir) + xsf_paths = [ + os.path.join(xsfdir, "structure.{}.xsf".format(i)) + for i in range(self.numdata) + ] + ases = [xsf_to_ase(xsf) for xsf in xsf_paths] + for ase_ in ases: + ase_.info["energy_mace"] = ase_.get_potential_energy() + #generate structure.xyz + ase.io.write("structure.xyz", ases) + self.generate_outputdir = os.getcwd() + os.chdir(pathlib.Path(os.getcwd()).parent) + + def generate_wait(self): + interval = 0.1 # sec + self.is_prepared = False + if os.path.exists(os.path.join(self.generate_outputdir, "structure.xyz")): + self.is_prepared = True + time.sleep(interval) + if not self.is_prepared: + raise RuntimeError(f"{self.generate_outputdir}") + + def train(self, train_dir = "train"): + if not self.is_prepared: + raise RuntimeError("you have to prepare the trainer before training!") + if os.path.exists(train_dir): + shutil.rmtree(train_dir) + shutil.copytree(self.train_inputdir, train_dir) + os.chdir(train_dir) + + os.rename( + os.path.join(self.generate_outputdir, "structure.xyz"), + os.path.join(os.getcwd(), "structure.xyz"), + ) + + with open(os.path.join(os.getcwd(), "stdout"), "w") as fi: + subprocess.run( + self.train_exe, stdout=fi, stderr=subprocess.STDOUT, check=True + ) + os.chdir(pathlib.Path(os.getcwd()).parent) + self.is_trained = True + + def new_baseinput(self, baseinput_dir, train_dir = "train"): + try: + assert self.is_trained + except AssertionError as e: + e.args += "you have to train before getting results!" + + baseinput = str(pathlib.Path(baseinput_dir).resolve()) + os.makedirs(baseinput, exist_ok=True) + shutil.copy(os.path.join(train_dir,"input.yaml"),baseinput) + os.chdir(train_dir) + with open("input.yaml", "r") as yml: + yaml_dic = yaml.safe_load(yml) + runname = yaml_dic["name"] + shutil.copy("{}.model".format(runname),os.path.join(baseinput,"deployed.model")) + os.chdir(pathlib.Path(os.getcwd()).parent) diff --git a/abics/applications/latgas_abinitio_interface/params.py b/abics/applications/latgas_abinitio_interface/params.py index 90165aa0..bb1a6022 100644 --- a/abics/applications/latgas_abinitio_interface/params.py +++ b/abics/applications/latgas_abinitio_interface/params.py @@ -33,6 +33,11 @@ def __init__(self): self.ensemble = False self.par_ensemble = False self.use_tmpdir = False + self.use_pretrained = True + self.relax = True + self.fmax = 0.1 + self.dvice = "cpu" + self.pretrained = None @classmethod def from_dict(cls, d): @@ -64,6 +69,9 @@ def from_dict(cls, d): else: params.path = "" + if params.solver == "sevennet": + params.pretrained = d.get("pretrained", "7net-0") + params.perturb = d.get("perturb", 0.1) params.ignore_species = d.get("ignore_species", None) params.constraint_module = d.get("constraint_module", None) @@ -71,6 +79,10 @@ def from_dict(cls, d): params.ensemble = d.get("ensemble", False) params.par_ensemble = d.get("par_ensemble", False) params.use_tmpdir = d.get("use_tmpdir", True) + params.use_pretrained = d.get("use_pretrained", True) + params.relax = d.get("relax", True) + params.fmax = d.get("fmax", 0.1) + params.device = d.get("device", "cpu") params.properties = d return params diff --git a/abics/applications/latgas_abinitio_interface/sevennet.py b/abics/applications/latgas_abinitio_interface/sevennet.py new file mode 100644 index 00000000..c2248c58 --- /dev/null +++ b/abics/applications/latgas_abinitio_interface/sevennet.py @@ -0,0 +1,218 @@ +# ab-Initio Configuration Sampling tool kit (abICS) +# Copyright (C) 2019- The University of Tokyo +# +# abICS wrapper of Sevennet +# Masashi Noda, Yusuke Konishi (Academeia Co., Ltd.) 2025 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +""" +energy calculator using sevennet python interface +""" + +from __future__ import annotations + +import os.path +from collections import namedtuple +import numpy as np +from pymatgen.core import Structure +import torch +from ase import Atoms +from ase.optimize import BFGS +from nequip.data import AtomicDataDict, AtomicData +from nequip.utils import Config +from sevenn.sevennet_calculator import SevenNetCalculator + +from .base_solver import SolverBase, register_solver +from .params import ALParams, DFTParams + +import tomli + +class SevennetSolver(SolverBase): + """ + Nequip solver + + Attributes + ---------- + path_to_solver : str + Path to the solver + input : NequipSolver.Input + Input manager + output : NequipSolver.Output + Output manager + """ + + def __init__(self, ignore_species, use_pretrained, relax, fmax, device, pretrained): + """ + Initialize the solver. + + """ + + super(SevennetSolver, self).__init__("") + self.path_to_solver = self.calculate_energy + self.input = SevennetSolver.Input(ignore_species, use_pretrained, relax, fmax, device, pretrained) + self.output = SevennetSolver.Output() + + def name(self): + return "sevennet" + + def calculate_energy(self, fi, output_dir): + st = self.input.st + symbols = [site.specie.symbol for site in st] + positions = [site.coords for site in st] + pbc = (True, True, True) + cell = st.lattice.matrix + atoms = Atoms(symbols=symbols, positions=positions, pbc=pbc, cell=cell) + + atoms.calc = self.input.calculator + + if self.input.relax: + opt = BFGS(atoms) + opt.run(fmax=self.input.fmax) + + # Get predicted energy + ene = atoms.get_total_energy() + + self.output.st = st + self.output.ene = ene + + class Input(object): + """ + Input manager for Mock + + Attributes + ---------- + st : pymatgen.Structure + structure + """ + + st: Structure + + def __init__(self, ignore_species=None, use_pretrained=False, relax=False, fmax=0.1, device="cpu", pretrained="7net-0"): + self.ignore_species = ignore_species + self.use_pretrained = use_pretrained + self.relax = relax + self.fmax = fmax + self.device = device + self.pretrained = pretrained + # self.st = Structure() + + def from_directory(self, base_input_dir): + """ + + Parameters + ---------- + base_input_dir : str + Path to the directory including base input files. + """ + self.base_input_dir = base_input_dir + if not self.use_pretrained: + self.calculator = SevenNetCalculator(os.path.join(base_input_dir, "deployed.pth"), device=self.device) + else: + self.calculator = SevenNetCalculator(model=self.pretrained, device=self.device) + + def update_info_by_structure(self, structure): + """ + Update information by structure file + + Parameters + ---------- + structure : pymatgen.Structure + Atomic structure + """ + self.st = structure.copy() + if self.ignore_species is not None: + self.st.remove_species(self.ignore_species) + + def update_info_from_files(self, workdir, rerun): + """ + Do nothing + """ + pass + + def write_input(self, output_dir): + """ + Generate input files of the solver program. + + Parameters + ---------- + workdir : str + Path to working directory. + """ + if not os.path.exists(output_dir): + import shutil + + shutil.copytree(self.base_input_dir, output_dir) + + # self.st.to("POSCAR", os.path.join(output_dir, "structure.vasp")) + + def cl_args(self, nprocs, nthreads, workdir): + """ + Generate command line argument of the solver program. + + Parameters + ---------- + nprocs : int + The number of processes. + nthreads : int + The number of threads. + workdir : str + Path to the working directory. + + Returns + ------- + args : list[str] + Arguments of command + """ + return [workdir] + + class Output(object): + """ + Output manager. + """ + + def __init__(self): + pass + + def get_results(self, workdir): + """ + Get energy and structure obtained by the solver program. + + Parameters + ---------- + workdir : str + Path to the working directory. + + Returns + ------- + phys : named_tuple("energy", "structure") + Total energy and atomic structure. + The energy is measured in the units of eV + and coodinates is measured in the units of Angstrom. + """ + Phys = namedtuple("PhysVaules", ("energy", "structure")) + return Phys(self.ene, self.st) + + def solver_run_schemes(self): + return ("function",) + + @classmethod + def create(cls, params: ALParams | DFTParams): + ignore_species = params.ignore_species + use_pretrained = params.use_pretrained + relax = params.relax + fmax = params.fmax + device = params.device + pretrained = params.pretrained + return cls(ignore_species, use_pretrained, relax, fmax, device, pretrained) diff --git a/abics/applications/latgas_abinitio_interface/sevennet_trainer.py b/abics/applications/latgas_abinitio_interface/sevennet_trainer.py new file mode 100644 index 00000000..4d39b783 --- /dev/null +++ b/abics/applications/latgas_abinitio_interface/sevennet_trainer.py @@ -0,0 +1,168 @@ +# ab-Initio Configuration Sampling tool kit (abICS) +# Copyright (C) 2019- The University of Tokyo +# +# abICS wrapper of SevenNet solver +# Masashi Noda, Yusuke Konishi (Academeia Co., Ltd.) 2025 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +from __future__ import annotations +from typing import Sequence, Dict + +import numpy as np +import os, pathlib, shutil, subprocess, shlex +import time + +from pymatgen.core import Structure + +from abics.util import expand_cmd_path +from abics.applications.latgas_abinitio_interface.base_trainer import TrainerBase +from abics.applications.latgas_abinitio_interface.util import structure_to_XSF + +import ase +from ase import io +from ase.calculators.singlepoint import SinglePointCalculator +from ase.calculators.morse import MorsePotential + +def xsf_to_ase(xsf): + ase_xsf = ase.io.read(xsf) + with open(xsf) as f: + lines = f.readlines() + + tot_energy = float(lines[0].split()[4]) + ase_xsf.calc = SinglePointCalculator(energy=tot_energy, atoms=ase_xsf) + ase_xsf.calc = MorsePotential() + forces = ase_xsf.get_forces() + ase_xsf.calc = SinglePointCalculator(energy=tot_energy, forces=forces, atoms=ase_xsf) + return ase_xsf + +class SevennetTrainer(TrainerBase): + def __init__( + self, + structures: Sequence[Structure], + energies: Sequence[float], + generate_inputdir: os.PathLike, + train_inputdir: os.PathLike, + predict_inputdir: os.PathLike, + execute_commands: Dict, + # trainer_type: str, + ): + self.structures = structures + self.energies = energies + self.generate_inputdir = generate_inputdir + self.train_inputdir = train_inputdir + self.predict_inputdir = predict_inputdir + train_exe = execute_commands["train"] + self.train_exe = [expand_cmd_path(e) for e in shlex.split(train_exe)] + self.train_exe.append("input.yaml") + assert len(self.structures) == len(self.energies) + self.numdata = len(self.structures) + self.is_prepared = False + self.is_trained = False + self.generate_outputdir = None + # self.trainer_type = trainer_type + + def prepare(self, latgas_mode = True, st_dir = "sevennetXSF"): + rootdir = os.getcwd() + xsfdir = os.path.join(rootdir, st_dir) + + # prepare XSF files for nequip + os.makedirs(xsfdir, exist_ok=True) + os.chdir(xsfdir) + xsfdir = os.getcwd() + if latgas_mode: + for i, st in enumerate(self.structures): + xsf_string = structure_to_XSF(st, write_force_zero=False) + xsf_string = ( + "# total energy = {} eV\n\n".format(self.energies[i]) + xsf_string + ) + with open("structure.{}.xsf".format(i), "w") as fi: + fi.write(xsf_string) + else: + for i, st in enumerate(self.structures): + xsf_string = structure_to_XSF(st, write_force_zero=False) + xsf_string = ( + "# total energy = {} eV\n\n".format(self.energies[i]) + xsf_string + ) + with open("structure.{}.xsf".format(i), "w") as fi: + fi.write(xsf_string) + + os.chdir(rootdir) + + def generate_run(self, xsfdir="sevennetXSF", generate_dir="generate"): + # prepare generate + xsfdir = str(pathlib.Path(xsfdir).resolve()) + if os.path.exists(generate_dir): + shutil.rmtree(generate_dir) + # shutil.copytree(self.generate_inputdir, generate_dir) + os.makedirs(generate_dir, exist_ok=True) + self.generate_dir = generate_dir + os.chdir(generate_dir) + xsf_paths = [ + os.path.join(xsfdir, "structure.{}.xsf".format(i)) + for i in range(self.numdata) + ] + ases = [xsf_to_ase(xsf) for xsf in xsf_paths] + #generate structure.xyz + ase.io.write("structure.xyz", ases) + self.generate_outputdir = os.getcwd() + os.chdir(pathlib.Path(os.getcwd()).parent) + + def generate_wait(self): + interval = 0.1 # sec + self.is_prepared = False + if os.path.exists(os.path.join(self.generate_outputdir, "structure.xyz")): + self.is_prepared = True + time.sleep(interval) + if not self.is_prepared: + raise RuntimeError(f"{self.generate_outputdir}") + + def train(self, train_dir = "train"): + if not self.is_prepared: + raise RuntimeError("you have to prepare the trainer before training!") + if os.path.exists(train_dir): + shutil.rmtree(train_dir) + shutil.copytree(self.train_inputdir, train_dir) + os.chdir(train_dir) + + os.rename( + os.path.join(self.generate_outputdir, "structure.xyz"), + os.path.join(os.getcwd(), "structure.xyz"), + ) + + with open(os.path.join(os.getcwd(), "stdout"), "w") as fi: + subprocess.run( + self.train_exe, stdout=fi, stderr=subprocess.STDOUT, check=True + ) + os.chdir(pathlib.Path(os.getcwd()).parent) + self.is_trained = True + + def new_baseinput(self, baseinput_dir, train_dir = "train"): + try: + assert self.is_trained + except AssertionError as e: + e.args += "you have to train before getting results!" + + baseinput = str(pathlib.Path(baseinput_dir).resolve()) + os.makedirs(baseinput, exist_ok=True) + shutil.copy(os.path.join(train_dir,"input.yaml"),baseinput) + os.chdir(train_dir) + checkpoint_files = [f for f in os.listdir() if f.startswith("checkpoint_") and f.endswith(".pth")] + checkpoint_nums = [int(f.replace("checkpoint_", "").replace(".pth", "")) for f in checkpoint_files] + max_checkpoint_num = max(checkpoint_nums) + checkpoint_file = f"checkpoint_{max_checkpoint_num}.pth" + #subprocess.run(["sevenn_get_model", checkpoint_file], check=True) + #shutil.copy("deployed_serial.pt", os.path.join(baseinput, "deployed.pt")) + shutil.copy(checkpoint_file, os.path.join(baseinput, "deployed.pth")) + os.chdir(pathlib.Path(os.getcwd()).parent) diff --git a/abics/scripts/main_dft_latgas.py b/abics/scripts/main_dft_latgas.py index 64713f3a..23f2cc04 100644 --- a/abics/scripts/main_dft_latgas.py +++ b/abics/scripts/main_dft_latgas.py @@ -295,6 +295,13 @@ def main_dft_latgas(params_root: MutableMapping): ALrun = exists_on_all_nodes(commAll, "ALloop.progress") + # if use_pretrained is True, make directory dftparams.base_input_dir + if dftparams.use_pretrained: + if comm.Get_rank() == 0: + for dir in dftparams.base_input_dir: + if not os.path.exists(dir): + os.mkdir(dir) + # Active learning mode if ALrun: logger.info(f"-Running in active learning mode.") diff --git a/abics/scripts/train.py b/abics/scripts/train.py index eb9c0081..5fe9b7c3 100644 --- a/abics/scripts/train.py +++ b/abics/scripts/train.py @@ -83,7 +83,7 @@ def main_impl(params_root: MutableMapping): - if trainer_type not in ["aenet", "allegro", "nequip", "mlip_3"]: + if trainer_type not in ["aenet", "allegro", "nequip", "mlip_3", "sevennet", "mace", "chgnet"]: logger.error("Unknown trainer: ", trainer_type) sys.exit(1) diff --git a/docs/sphinx/ja/source/how_to_use/index.rst b/docs/sphinx/ja/source/how_to_use/index.rst index 707fc1a3..06514582 100644 --- a/docs/sphinx/ja/source/how_to_use/index.rst +++ b/docs/sphinx/ja/source/how_to_use/index.rst @@ -180,6 +180,47 @@ MLIP-3 - MLIP-3用の入力ファイル ``input.almtp`` を ``[train]`` セクションの ``base_input_dir`` で設定したディレクトリ内の ``train`` ディレクトリに設置してください。 +SevenNet +*********** + +- URL : https://github.com/MDIL-SNU/SevenNet + +- SevenNet 0.10.1 で動作確認済。 + +- 参照ファイル(参照ファイルの具体例についてはチュートリアル参照) + + - 学習済みモデルを使用する場合: abicsのinput.tomlだけで問題ありません + + - 学習を行う場合: SevenNetのinputファイル ``input.yaml`` を ``[train]`` セクションの ``base_input_dir`` で設定したディレクトリ内の ``train`` ディレクトリに設置してください。 + +Mace +*********** + +- URL : https://github.com/ACEsuit/mace + +- Mace 0.3.8 で動作確認済。 + +- 参照ファイル(参照ファイルの具体例についてはチュートリアル参照) + + - 学習済みモデルを使用する場合: abicsのinput.tomlだけで問題ありません + + - 学習を行う場合: Maceのinputファイル ``input.yaml`` を ``[train]`` セクションの ``base_input_dir`` で設定したディレクトリ内の ``train`` ディレクトリに設置してください。 + +CHGNet +*********** + +- URL : https://chgnet.lbl.gov/ + +- CHGNet 0.3.8 で動作確認済。 + +- 参照ファイル(参照ファイルの具体例についてはチュートリアル参照) + + - 学習済みモデルを使用する場合: abicsのinput.tomlだけで問題ありません + + - 学習を行う場合: inputファイル ``input.yaml`` を ``[train]`` セクションの ``base_input_dir`` で設定したディレクトリ内の ``train`` ディレクトリに設置してください。 + + - こちらの ``input.yaml`` はCHGNet側のinputファイルとは異なり、abICS側で定義された形式となっております。詳しくはチュートリアルを参照してください。 + 学習データの作成 ------------------- diff --git a/docs/sphinx/ja/source/tutorial/other_models.rst b/docs/sphinx/ja/source/tutorial/other_models.rst index bb9183d6..5d8f67bd 100644 --- a/docs/sphinx/ja/source/tutorial/other_models.rst +++ b/docs/sphinx/ja/source/tutorial/other_models.rst @@ -290,4 +290,305 @@ MLIP-3の実行ファイル ``mlp`` のパスを指定します。お使いの alpha_scalar_moments = 5 alpha_moment_mapping = {0, 4, 5, 6, 7} -モデル学習、サンプリングの方法に関してはaenetと同様です。 \ No newline at end of file +モデル学習、サンプリングの方法に関してはaenetと同様です。 + +SevenNetを利用したサンプリング +---------------------------------------------- + +SevenNetのインストール +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``sevennet`` の利用には、 SevenNetのインストールが必要です。 + +下記コマンドにてインストールします。 + +.. code-block:: bash + + $ python3 -m pip install sevenn + +学習済みモデルの利用 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SevenNetでは、モデルを学習してからサンプリングを行う事以外に、 +学習済みモデルを利用してサンプリングを行う事も可能です。 + +学習済みモデルを用いる場合は、 ``[sanmping.solver]`` セクションを下記のように設定します。 + +.. code-block:: toml + + [sampling.solver] + type = 'sevennet' + perturb = 0.0 + +サンプリングの方法については、aenetと同様です。 + +モデル学習から実行する場合 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +モデルの学習から実行する場合は、 ``[train]`` セクションを適切に設定の上で、 +``[sanmping.solver]`` セクションに、 +``use_pretrained = false`` を追加する必要があります。 +``relax = false`` とする事で、構造の最適化を行わずにサンプリングを行う事も可能です。 + +.. code-block:: toml + + [sampling.solver] + type = 'sevennet' + perturb = 0.0 + base_input_dir = './baseinput_sevennet' + use_pretrained = false + + [train] + type = 'sevennet' + base_input_dir = './sevennet_train_input' + exe_command = ['', 'sevenn'] + vac_map = [] + restart = false + +また、SevenNetのインプットファイル ``input.yaml`` を ``sevennet_train_input/train`` ディレクトリに作成します。 +ここではコマンドsevennのインプットファイルを作成しています。 +各パラメータの詳しい説明はSevenNetのドキュメントを参照してください。 + +.. code-block:: yaml + + model: # model keys should be consistent except for train_* keys + chemical_species: 'Auto' + cutoff: 5.0 + channel: 128 + is_parity: False + lmax: 2 + num_convolution_layer: 5 + irreps_manual: + - "128x0e" + - "128x0e+64x1e+32x2e" + - "128x0e+64x1e+32x2e" + - "128x0e+64x1e+32x2e" + - "128x0e+64x1e+32x2e" + - "128x0e" + + weight_nn_hidden_neurons: [64, 64] + radial_basis: + radial_basis_name: 'bessel' + bessel_basis_num: 8 + cutoff_function: + cutoff_function_name: 'XPLOR' + cutoff_on: 4.5 + self_connection_type: 'linear' + + train_shift_scale: False # customizable (True | False) + train_denominator: False # customizable (True | False) + + train: # Customizable + random_seed: 1 + is_train_stress: False + epoch: 5 + + optimizer: 'adam' + optim_param: + lr: 0.004 + scheduler: 'exponentiallr' + scheduler_param: + gamma: 0.99 + + force_loss_weight: 0.1 + stress_loss_weight: 1e-06 + + per_epoch: 1 # Generate checkpoints every this epoch + + # ['target y', 'metric'] + # Target y: TotalEnergy, Energy, Force, Stress, Stress_GPa, TotalLoss + # Metric : RMSE, MAE, or Loss + error_record: + - ['Energy', 'RMSE'] + - ['TotalLoss', 'None'] + + continue: + reset_optimizer: True + reset_scheduler: True + reset_epoch: True + checkpoint: 'SevenNet-0_11July2024' + + data: # Customizable + batch_size: 4 + data_divide_ratio: 0.1 + + # SevenNet automatically matches data format from its filename. + # For those not `structure_list` or `.pt` files, assumes it is ASE readable + # In this case, below arguments are directly passed to `ase.io.read` + data_format_args: + index: ':' # see `https://wiki.fysik.dtu.dk/ase/ase/io/io.html` for more valid arguments + + # validset is needed if you want '_best.pth' during training. If not, both validset and testset is optional. + load_trainset_path: ['./structure.xyz'] # Example of using ase as data_format, support multiple files and expansion(*) + +モデル学習、サンプリングの方法に関してはaenetと同様です。 + +Maceを利用したサンプリング +---------------------------------------------- + +Maceのインストール +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``mace`` の利用には、 Maceのインストールが必要です。 + +下記コマンドにてインストールします。 + +.. code-block:: bash + + $ python3 -m pip install mace + +モデル学習から実行する場合 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Maceでは、モデルを学習してからサンプリングを行う事以外に、 +学習済みモデルを利用してサンプリングを行う事も可能です。 + +学習済みモデルを用いる場合は、 ``[sanmping.solver]`` セクションを下記のように設定します。 + +.. code-block:: toml + + [sampling.solver] + type = 'mace' + perturb = 0.0 + +サンプリングの方法については、aenetと同様です。 + +モデル学習から実行する場合 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +モデルの学習から実行する場合は、 ``[train]`` セクションを適切に設定の上で、 +``[sanmping.solver]`` セクションに、 +``use_pretrained = false`` を追加する必要があります。 +``relax = false`` とする事で、構造の最適化を行わずにサンプリングを行う事も可能です。 + +.. code-block:: toml + + [sampling.solver] + type = 'mace' + perturb = 0.0 + base_input_dir = './baseinput_mace' + use_pretrained = false + + [train] + type = 'mace' + base_input_dir = './mace_train_input' + exe_command = ['', 'mace_run_train'] + vac_map = [] + restart = false + +また、Maceのインプットファイル ``input.yaml`` を ``mace_train_input/train`` ディレクトリに作成します。 +ここではコマンドmace_run_trainのインプットファイルを作成しています。 +各パラメータの詳しい説明はMaceのドキュメントを参照してください。 + +.. code-block:: yaml + + name: spinel + foundation_model: "small" + seed: 2024 + train_file: structure.xyz + swa: yes + start_swa: 1200 + max_num_epochs: 5 + device: cpu + E0s: + 8: -2042.0 + 12: -1750.0 + 13: -1750.0 + energy_weight: 1.0 + forces_weight: 0.0 + stress_weight: 0.0 + +モデル学習、サンプリングの方法に関してはaenetと同様です。 + +CHGNetを利用したサンプリング +---------------------------------------------- + +CHGNetのインストール +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``chgnet`` の利用には、 CHGNetのインストールが必要です。 + +下記コマンドにてインストールします。 + +.. code-block:: bash + + $ python3 -m pip install chgnet + +学習済みモデルの利用 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +CHGNetでは、モデルを学習してからサンプリングを行う事以外に、 +学習済みモデルを利用してサンプリングを行う事も可能です。 + +学習済みモデルを用いる場合は、 ``[sanmping.solver]`` セクションを下記のように設定します。 + +.. code-block:: toml + + [sampling.solver] + type = 'chgnet' + perturb = 0.0 + +サンプリングの方法については、aenetと同様です。 + +モデル学習から実行する場合 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +モデルの学習から実行する場合は、 ``[train]`` セクションを適切に設定の上で、 +``[sanmping.solver]`` セクションに、 +``use_pretrained = false`` を追加する必要があります。 +``relax = false`` とする事で、構造の最適化を行わずにサンプリングを行う事も可能です。 + +.. code-block:: toml + + [sampling.solver] + type = 'chgnet' + perturb = 0.0 + base_input_dir = './baseinput_chgnet' + use_pretrained = false + + [train] + type = 'chgnet' + base_input_dir = './chgnet_train_input' + exe_command = ['', 'chgnet'] + vac_map = [] + restart = false + +また、CHGNetのインプットファイル ``input.yaml`` を ``chgnet_train_input/train`` ディレクトリに作成します。 + +.. code-block:: yaml + + finetuning : False + batch_size : 4 + train_ratio : 0.9 + val_ratio : 0.05 + learning_rate : 0.004 + epochs : 100 + model_params: + atom_fea_dim : 8 + bond_fea_dim : 8 + angle_fea_dim : 8 + num_radial : 9 + num_angular : 9 + num_conv : 2 + atom_conv_hidden_dim : 4 + bond_conv_hidden_dim : 4 + mlp_hidden_dims : + - 16 + - 16 + atom_graph_cutoff : 7.5 + bond_graph_cutoff : 6.0 + +このインプットファイルは、CHGNetの学習に必要なパラメータを設定するものであり、 +abICS側で定義されているものとなります。 +各ファイルのパラメータは下記の通りとなります。 + +- finetuning : ファインチューニングを行うかどうか。デフォルト値: True +- batch_size : バッチサイズ。デフォルト値: 4 +- train_ratio : 学習データの割合。デフォルト値: 0.9 +- val_ratio : 検証データの割合。デフォルト値: 0.05 +- learning_rate : 学習率。デフォルト値: 0.01 +- epochs : エポック数。デフォルト値: 5 +- model_params: finetuningがFalseの時に、CHGNetのパラメータとして使用されます。パラメータに関しては https://chgnet.lbl.gov/api#class-chgnet を参照してください。 + +モデル学習、サンプリングの方法に関してはaenetと同様です。 +