Source code for abipy.scripts.abiml

#!/usr/bin/env python
"""
Script to perform several types of calculations with ASE and ML potentials.
"""
from __future__ import annotations

import sys
import os
import json
import click
import numpy as np
import abipy.ml.aseml as aseml
import abipy.tools.cli_parsers as cli

from functools import wraps
from time import time
from abipy.core.structure import Structure
#from abipy.tools.printing import print_dataframe


ASE_OPTIMIZERS = aseml.ase_optimizer_cls("__all__")

DEFAULT_NN = "chgnet"


def _get_atoms_from_filepath(filepath: str):
    if filepath.startswith("__mp-"):
        print(f"Fetching structure for mp-id {filepath[2:]} from the materials project database.")
        return aseml.get_atoms(Structure.from_mpid(filepath[2:]))

    return aseml.get_atoms(filepath)


def _get_structure_from_filepath(filepath: str) -> Structure:
    if filepath.startswith("__mp-"):
        print(f"Fetching structure for mp-id {filepath[2:]} from the materials project database.")
        structure = Structure.from_mpid(filepath[2:])
    else:
        structure = Structure.from_file(filepath)

    return structure


[docs] def set_default(ctx, param, filepath): """ To have config file for a single command: Based on https://stackoverflow.com/questions/46358797/python-click-supply-arguments-and-options-from-a-configuration-file """ from abipy.tools.iotools import yaml_safe_load_path if os.path.exists(filepath): print("Reading CLI options from:", filepath) config = yaml_safe_load_path(filepath) print("Config options:\n", config) ctx.default_map = config return filepath
[docs] def herald(f): @wraps(f) def wrapper(*args, **kw): verbose = kw.get("verbose", 0) #print('func:%r args:[%r, %r]' % (f.__name__, args, kw)) #raise ValueError() if verbose > 3: print('func:%r args:[%r, %r]' % (f.__name__, args, kw)) print(f.__doc__, end=2*"\n") print("Command line options:") print(json.dumps(kw, indent=4), end="\n") # IMPORTANT: Set OMP_NUM_THREADS to 1 if env variable is not defined. num_threads = cli.fix_omp_num_threads() t_start = time() exit_code = f(*args, **kw) t_end = time() print('\n%s command completed in %2.4f sec\n' % (f.__name__, t_end - t_start)) return exit_code return wrapper
[docs] def add_constraint_opts(f): """Add CLI options to constrain atoms.""" def mk_cbk(type): def callback(ctx, param, value): if value is None: return None return [type(s) for s in value.split()] return callback f = click.option("--fix-inds", "-fi", type=str, default=None, show_default=True, callback=mk_cbk(int), help='Fix atoms by indices e.g. `--fix-inds "0 1"` to fix the first two atoms.')(f) f = click.option("--fix-symbols", "-fs", type=str, default=None, show_default=True, callback=mk_cbk(str), help='Fix atoms by chemical symbols e.g. `--fix-symbols "C O"`')(f) return f
[docs] def add_relax_opts(f): """Add CLI options for structural relaxations with ASE.""" # fmax (float): total force tolerance for relaxation convergence. # Here fmax is a sum of force and stress forces. Defaults to 0.1. f = click.option("--relax-mode", "-r", default="ions", show_default=True, type=click.Choice(["no", "ions", "cell"]), help='Relaxation mode.')(f) f = click.option("--fmax", default=0.01, type=float, show_default=True, help='Stopping criterion in eV/A')(f) f = click.option("--pressure", default=0.0, type=float, show_default=True, help='Scalar pressure')(f) f = click.option("--steps", default=500, type=int, show_default=True, help="Max number of steps for ASE relaxation.")(f) f = click.option("--optimizer", "-o", default="BFGS", show_default=True, type=click.Choice(ASE_OPTIMIZERS), help="ASE optimizer class.")(f) return f
[docs] def add_phonopy_opts(f, supercell=(2, 2, 2)): """Add CLI options for phonopy_calculations.""" f = click.option("--supercell", "-s", nargs=3, type=int, default=supercell, show_default=True, help="Supercell dimensions.")(f) f = click.option("--distance", "-d", type=float, show_default=True, default=0.01, help="Displacement distance in Ang.")(f) f = click.option('--line-density', "-ld", default=20, type=float, show_default=True, help="Line density to generate the q-path for PH bands.")(f) f = click.option('--qppa', "-qppa", default=None, type=float, show_default=True, help="q-points per atom to generate the q-mesh for PH DOS.")(f) return f
[docs] def add_neb_opts(f): """Add CLI options for NEB calculations with ASE.""" f = click.option("--nimages", "-n", default=14, type=click.IntRange(3, None), show_default=True, help='Number of NEB images including initial/final points. Must be >= 3')(f) f = click.option("--relax-mode", "-r", default="ions", show_default=True, type=click.Choice(["no", "ions", "cell"]), help="Relax initial and final structure. Use `cell` to relax ions and cell, " + "`ions` to relax atomic positions only, `no` to disable relaxation")(f) f = click.option("--fmax", default=0.03, type=float, show_default=True, help='Stopping criterion.')(f) f = click.option("--pressure", default=0.0, type=float, show_default=True, help='Scalar pressure')(f) f = click.option("--optimizer", "-o", default="BFGS", show_default=True, type=click.Choice(ASE_OPTIMIZERS), help="ASE optimizer class.")(f) f = click.option("--neb-method", "-m", default="aseneb", type=click.Choice(aseml.ASENEB_METHODS), show_default=True, help="ASE NEB method")(f) f = click.option("--climb", "-c", is_flag=True, help="Use a climbing image (default is no climbing image).")(f) return f
[docs] def add_nprocs_opt(f): """Add CLI options for multiprocessing.""" f = click.option("-np", "--nprocs", default=-1, type=int, show_default=True, help='Number of processes in multiprocessing pool. -1 to let Abipy select it automatically.')(f) return f
[docs] def add_workdir_verbose_opts(f): """Add workdir and verbose options to CLI subcommand.""" f = click.option("--workdir", "-w", default=None, type=str, help="Working directory. If not specified, a temporary directory is created.")(f) f = click.option('-v', '--verbose', count=True, help="Verbosity level")(f) return f
[docs] def add_nn_name_opt(f): """Add CLI options to select the NN potential.""" f = click.option("--nn-name", "-nn", default=DEFAULT_NN, show_default=True, help=f"ML potential to be used. Supported values are: {aseml.CalcBuilder.ALL_NN_TYPES}")(f) #f = click.option("--nn-name", "-nn", default=DEFAULT_NN, show_default=True, # help=f"ML potential to be used.\n{aseml.CalcBuilder.DOC_NAME}")(f) #f = click.option("--dftd3", , default="no", show_default=True, help=f"Activate DFD3.")(f) return f
[docs] def add_nn_names_opt(f): """Add CLI options to select multiple NN potentials.""" f = click.option("-nns", '--nn-names', type=str, multiple=True, show_default=True, help='ML potentials to use.', default=[DEFAULT_NN])(f) return f
def _get_nn_names(nn_names: list[str]) -> list[str]: """ Pre-processing of nn-names option. --nn-names all --> return all NN names installed in this env. --nn-names all-alignn-m3gnet --> return all NN names except alignn and m3gnet. --nn-names chgnet- --> return all NN names except chgnet. """ if "all" in nn_names: # Return all possibilities. nn_installed, nn_versions = aseml.get_installed_nn_names(verbose=0, printout=False) return nn_installed if any(n.startswith("all-") for n in nn_names): # --nn-names all-alignn-m3gnet --> return all NN names except alignn and m3gnet assert len(nn_names) == 1 skip_names = nn_names[0].replace("all-", "").split("-") return [s for s in aseml.CalcBuilder.ALL_NN_TYPES if s not in skip_names] if any(n.endswith("-") for n in nn_names): # --nn-names chgnet- --> return all NN names except chgnet. skip_names = [n[:-2] for n in nn_names if n.endswith("-")] return [s for s in aseml.CalcBuilder.ALL_NN_TYPES if s not in skip_names] return nn_names @click.group() @click.pass_context @click.option("--seaborn", "-sns", default=None, show_default=True, help='Use seaborn settings. Accept value defining context in ("paper", "notebook", "talk", "poster").') def main(ctx, seaborn): """Script to perform calculations with ML potentials.""" ctx.ensure_object(dict) if seaborn: # Activate seaborn settings for plots import seaborn as sns sns.set(context=seaborn, style='darkgrid', palette='deep', font='sans-serif', font_scale=1, color_codes=False, rc=None) # Suppress all DeprecationWarnings #import warnings #warnings.filterwarnings("ignore", category=DeprecationWarning) @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @add_nn_name_opt @add_relax_opts @add_constraint_opts @add_workdir_verbose_opts @click.option('--config', default='abiml_relax.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def relax(ctx, filepath, nn_name, relax_mode, fmax, pressure, steps, optimizer, fix_inds, fix_symbols, workdir, verbose): """ Structural relaxation with ASE and ML potential. Usage example: \b abiml.py relax FILE --fmax 0.01 -r cell --optimizer FIRE -w OUT_DIR abiml.py relax FILE --fix-inds "0 3" --fix-symbols "Si O" where `FILE` is any file supported by abipy/pymatgen e.g. netcdf files, Abinit input, POSCAR, xsf, etc. or a string such as __mp-134 to fetch the structure from the MP database. To change the ML potential, use e.g.: abiml.py relax -nn m3gnet [...] """ atoms = _get_atoms_from_filepath(filepath) aseml.fix_atoms(atoms, fix_inds=fix_inds, fix_symbols=fix_symbols) ml_relaxer = aseml.MlRelaxer(atoms, relax_mode, fmax, pressure, steps, optimizer, nn_name, verbose, workdir, prefix="_abiml_relax_") print(ml_relaxer.to_string(verbose=verbose)) ml_relaxer.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @add_workdir_verbose_opts @click.option('--config', default='abiml_abinit_relax.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def abinit_relax(ctx, filepath, workdir, verbose): """ Interact with ABINIT in hybrid relaxation mode. """ ml_relaxer = aseml.MlRelaxer.from_abinit_yaml_file(filepath) print(ml_relaxer.to_string(verbose=verbose)) ml_relaxer.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @add_nn_name_opt @add_relax_opts @add_workdir_verbose_opts @click.option('--config', default='abiml_eos.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def eos(ctx, filepath, nn_name, relax_mode, fmax, pressure, steps, optimizer, workdir, verbose): """ EOS computation with ASE and ML potential. Usage example: \b abiml.py eos FILE --fmax 0.01 -r cell --optimizer FIRE -w OUT_DIR where `FILE` is any file supported by abipy/pymatgen e.g. netcdf files, Abinit input, POSCAR, xsf, etc. or a string such as __mp-134 to fetch the structure from the MP database. To change the ML potential, use e.g.: abiml.py eos -nn m3gnet [...] """ atoms = _get_atoms_from_filepath(filepath) vol_scales = np.arange(0.95, 1.06, 0.01) ml_eos = aseml.MlEos(atoms, vol_scales, relax_mode, fmax, pressure, steps, optimizer, nn_name, verbose, workdir, prefix="_abiml_eos_") print(ml_eos.to_string(verbose=verbose)) ml_eos.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @add_nn_name_opt @click.option('--temperature', "-t", default=600, type=float, show_default=True, help='Temperature in Kelvin') @click.option('--pressure', "-p", default=1, type=float, show_default=True, help='Pressure in ???.') @click.option('--timestep', "-ts", default=1, type=float, show_default=True, help='Timestep in fs.') @click.option('--steps', "-s", default=1000, type=int, show_default=True, help='Number of timesteps.') @click.option('--loginterval', "-l", default=100, type=int, show_default=True, help='Interval for record the log.') @click.option('--ensemble', "-e", default="nvt", show_default=True, type=click.Choice(["nvt", "npt", "npt_berendsen"]), help='Ensemble e.g. nvt, npt.') @add_constraint_opts @add_workdir_verbose_opts @click.option('--config', default='abiml_md.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def md(ctx, filepath, nn_name, temperature, pressure, timestep, steps, loginterval, ensemble, fix_inds, fix_symbols, workdir, verbose): """ MD simulation with ASE and ML potential. Usage example: \b abiml.py md FILE --temperature 1200 --timestep 2 --steps 5000 --workdir OUT_DIR abiml.py md FILE --fix-inds "0 3" --fix-symbols "Si O" where `FILE` is any file supported by abipy/pymatgen e.g. netcdf files, Abinit input, POSCAR, xsf, etc. To change the ML potential, use e.g.: abiml.py md -nn m3gnet [...] To restart a MD run, use --workdir to specify a pre-existent directory. """ # See https://github.com/materialsvirtuallab/m3gnet#molecular-dynamics atoms = aseml.get_atoms(filepath) aseml.fix_atoms(atoms, fix_inds=fix_inds, fix_symbols=fix_symbols) ml_md = aseml.MlMd(atoms, temperature, pressure, timestep, steps, loginterval, ensemble, nn_name, verbose, workdir, prefix="_abiml_md_") print(ml_md.to_string(verbose=verbose)) ml_md.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepaths", nargs=2, type=str) @add_nn_name_opt @add_neb_opts @add_constraint_opts @add_workdir_verbose_opts @click.option('--config', default='abiml_neb.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def neb(ctx, filepaths, nn_name, nimages, relax_mode, fmax, pressure, optimizer, neb_method, climb, fix_inds, fix_symbols, workdir, verbose): """ NEB calculation with ASE and ML potential. Usage example: \b abiml.py neb START_FILE END_FILE --nimages 6 --fmax=0.05 --optimizer FIRE -w OUT_DIR abiml.py neb START_FILE END_FILE --neb-method improvedtangent --climb abiml.py neb START_FILE END_FILE --fix-inds "0 3" --fix-symbols "Si O" where `FILE` is any file supported by abipy/pymatgen e.g. netcdf files, Abinit input, POSCAR, xsf, etc. To change the ML potential, use e.g.: abiml.py neb -nn m3gnet [...] """ initial_atoms = aseml.get_atoms(filepaths[0]) aseml.fix_atoms(initial_atoms, fix_inds=fix_inds, fix_symbols=fix_symbols) final_atoms = aseml.get_atoms(filepaths[1]) aseml.fix_atoms(final_atoms, fix_inds=fix_inds, fix_symbols=fix_symbols) ml_neb = aseml.MlNeb(initial_atoms, final_atoms, nimages, neb_method, climb, optimizer, relax_mode, fmax, pressure, nn_name, verbose, workdir, prefix="_abiml_neb_") print(ml_neb.to_string(verbose=verbose)) ml_neb.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepaths", nargs=-1, type=str) # , help="Files with structures") @add_nn_name_opt @add_neb_opts @add_constraint_opts @add_workdir_verbose_opts @click.option('--config', default='abiml_mneb.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def mneb(ctx, filepaths, nn_name, nimages, relax_mode, fmax, pressure, optimizer, neb_method, climb, fix_inds, fix_symbols, workdir, verbose): """ Multi-NEB calculation with ASE and ML potential. Usage example: \b abiml.py mneb FILE1 FILE2 FILE2 ... --nimages 6 --fmax=0.05 -w OUT_DIR abiml.py mneb FILE1 FILE2 FILE2 ... --neb-method improvedtangent --climb abiml.py mneb FILE1 FILE2 FILE2 ... --fix-inds "0 3" --fix-symbols "Si O" where `FILE` is any file supported by abipy/pymatgen e.g. netcdf files, Abinit input, POSCAR, xsf, etc. To change the ML potential, use e.g.: abiml.py mneb -nn m3gnet [...] """ # Fix atoms atoms_list = [aseml.get_atoms(p) for p in filepaths] for atoms in atoms_list: aseml.fix_atoms(atoms, fix_inds=fix_inds, fix_symbols=fix_symbols) mneb = aseml.MultiMlNeb(atoms_list, nimages, neb_method, climb, optimizer, relax_mode, fmax, pressure, nn_name, verbose, workdir, prefix="_abiml_mneb_") print(mneb.to_string(verbose=verbose)) mneb.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @add_nn_names_opt @add_phonopy_opts @add_relax_opts @add_workdir_verbose_opts @click.option('--config', default='abiml_ph.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def ph(ctx, filepath, nn_names, supercell, distance, line_density, qppa, relax_mode, fmax, pressure, steps, optimizer, workdir, verbose): """ Use phonopy and ML potential to compute phonons. Usage example: \b abiml.py ph FILE --distance 0.03 --supercell 2 2 2 where `FILE` provides the crystalline structure or a string such as __mp-134 to fetch the structure from the MP database. To specify the list of ML potential, use e.g.: abiml.py ph -nn-names m3gnet --nn-names chgnet [...] To use all NN potentials supported, use: -nn-names all [...] """ structure = _get_structure_from_filepath(filepath) from abipy.ml.ml_phonopy import MlPhonopy supercell = np.eye(3) * np.array(supercell) nn_names = _get_nn_names(nn_names) ml_ph = MlPhonopy(structure, supercell, distance, line_density, qppa, relax_mode, fmax, pressure, steps, optimizer, nn_names, verbose, workdir, prefix="_abiml_ph_", ) print(ml_ph.to_string(verbose=verbose)) ml_ph.run() return 0
[docs] def phddb_add_phonopy_opts(f): """Change default value of supercell.""" return add_phonopy_opts(f, supercell=(-1, -1, -1))
@main.command() @herald @click.pass_context @click.argument("ddb_filepath", type=str) @add_nn_names_opt @phddb_add_phonopy_opts @click.option('--asr', type=int, default=2, show_default=True, help="Restore the acoustic sum rule on the interatomic force constants.") @click.option('--dipdip', type=int, default=1, show_default=True, help="Treatment of dipole-dipole interaction.") @add_relax_opts @add_workdir_verbose_opts @click.option('--config', default='abiml_phddb.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def phddb(ctx, ddb_filepath, nn_names, supercell, distance, line_density, qppa, asr, dipdip, relax_mode, fmax, pressure, steps, optimizer, workdir, verbose): """ Use phonopy and ML potential to compute phonons and compare with DDB. Usage example: \b abiml.py phddb DDB_FILE --distance 0.03 --dipdip 0 --supercell 2 2 2 where `DDB_FILE` is an Abinit DDB file or a string such as __mp-134 to fetch the DDB from the MP database. To specify the list of ML potential, use e.g.: abiml.py phddb -nn-names m3gnet --nn-names chgnet [...] To use all NN potentials supported, use: -nn-names all [...] """ if ddb_filepath.startswith("__mp-"): print(f"Fetching DDB for mp-id {ddb_filepath[2:]} from the materials project database.") from abipy.dfpt.ddb import DdbFile with DdbFile.from_mpid(ddb_filepath[2:]) as ddb: ddb_filepath = ddb.filepath from abipy.ml.ml_phonopy import MlPhonopyWithDDB if any(s <= 0 for s in supercell): supercell = None else: supercell = np.eye(3) * np.array(supercell) nn_names = _get_nn_names(nn_names) ml_phddb = MlPhonopyWithDDB(ddb_filepath, distance, asr, dipdip, line_density, qppa, relax_mode, fmax, pressure, steps, optimizer, nn_names, verbose, workdir, prefix="_abiml_phddb_", supercell=supercell, ) print(ml_phddb.to_string(verbose=verbose)) ml_phddb.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @add_nn_name_opt @add_phonopy_opts @add_relax_opts @add_workdir_verbose_opts @click.option('--config', default='abiml_vqha.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def vqha(ctx, filepath, nn_name, supercell, distance, line_density, qppa, relax_mode, fmax, pressure, steps, optimizer, workdir, verbose): """ Use phonopy and ML potential to perform VZSISA-QHA calculations. Usage example: \b abiml.py vqha FILE --distance 0.03 --supercell 2 2 2 where `FILE` provides the crystalline structure or a string such as __mp-134 to fetch the structure from the MP database. To specify the list of ML potential, use e.g.: abiml.py vqha -nn-name mace [...] """ structure = _get_structure_from_filepath(filepath) from abipy.ml.ml_phonopy import MlVZSISAQHAPhonopy supercell = np.eye(3) * np.array(supercell) #bo_vol_scales = [0.96, 0.98, 1.0, 1.02, 1.04, 1.06] #ph_vol_scales = [0.98, 1.0, 1.02, 1.04, 1.06] # EinfVib4(D) bo_vol_scales = [0.96, 0.98, 1, 1.02, 1.04] # EinfVib4(S) ph_vol_scales = [1, 1.02, 1.04] # EinfVib2(D) ph_vol_scales = bo_vol_scales vqha = MlVZSISAQHAPhonopy(structure, bo_vol_scales, supercell, distance, line_density, qppa, relax_mode, fmax, pressure, steps, optimizer, nn_name, verbose, workdir, prefix="_abiml_vqha_", ) print(vqha.to_string(verbose=verbose)) vqha.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @add_nn_name_opt @click.option("--max-ns", "-m", default=100, type=int, show_default=True, help='Max number of structures') @add_relax_opts @add_workdir_verbose_opts @click.option('--config', default='abiml_order.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def order(ctx, filepath, nn_name, max_ns, relax_mode, fmax, pressure, steps, optimizer, workdir, verbose): """ Generate ordered structures from CIF with partial occupancies. Usage example: \b abiml.py order FILE --max-ns 10 --relax cell where `FILE` is any file supported by abipy/pymatgen e.g. netcdf files, Abinit input, POSCAR, xsf, etc. Based on: https://matgenb.materialsvirtuallab.org/2013/01/01/Ordering-Disordered-Structures.html """ ml_orderer = aseml.MlOrderer(filepath, max_ns, optimizer, relax_mode, fmax, pressure, steps, nn_name, verbose, workdir, prefix="_abiml_order_") print(ml_orderer.to_string(verbose=verbose)) ml_orderer.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @add_nn_name_opt @click.option("-isite", "--isite", required=True, help='Index of atom to displace or string with the chemical element to be added to input structure.') @click.option("--mesh", type=int, default=4, show_default=True, help='Mesh size along the smallest cell size.') @add_relax_opts @add_nprocs_opt @add_workdir_verbose_opts @click.option('--config', default='abiml_scan_relax.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def scan_relax(ctx, filepath, nn_name, isite, mesh, relax_mode, fmax, pressure, steps, optimizer, nprocs, workdir, verbose ): """ Generate 3D mesh of (nx,ny,nz) initial positions and perform multiple relaxations in which all atoms are fixed except the one initially placed at the mesh point. Usage example: \b abiml.py scan-relax FILE -isite 0 --mesh 4 # Move first atom in the structure abiml.py scan-relax FILE -isite H # Add H to the structure read from FILE. where `FILE` is any file supported by abipy/pymatgen e.g. netcdf files, Abinit input, POSCAR, xsf, etc. To change the ML potential, use e.g.: abiml.py scan-relax -nn m3gnet [...] """ structure = Structure.from_file(filepath) from abipy.ml.relax_scanner import RelaxScanner scanner = RelaxScanner(structure, isite, mesh, nn_name, relax_mode=relax_mode, fmax=fmax, steps=steps, verbose=verbose, optimizer_name=optimizer, pressure=pressure, workdir=workdir, prefix="_abiml_scan_relax_") print(scanner) scanner.run(nprocs=nprocs) return 0 @main.command() @herald @click.pass_context @click.argument('filepaths', type=str, nargs=-1) @add_nn_names_opt @click.option("--traj_range", type=str, show_default=True, help="Trajectory range e.g. `5` to select the first 5 iterations, `1:4` to select steps 1,2,3. `1:4:2 for 1,3", default=None) @click.option("--symbol", type=str, show_default=True, help="Chemical symbol. If None all atoms are considered.", default=None) @click.option('--stress/--no-stress', default=True, show_default=True, help="Show parity-plot for stress tensor") @click.option('--delta/--no-delta', default=False, show_default=True, help="Show parity-plot for delta mode") @click.option('--traj/--no-traj', default=False, show_default=True, help="Show results along trajectory") @click.option("-e", '--exposer', default="mpl", show_default=True, type=click.Choice(["mpl", "panel"]), help='Plotting backend: mpl for matplotlib, panel for web-based, None to disable plotting.') @add_nprocs_opt @add_workdir_verbose_opts @click.option('--config', default='abiml_validate.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def validate(ctx, filepaths, nn_names, traj_range, symbol, stress, delta, traj, exposer, nprocs, workdir, verbose ): """ Compare ab-initio energies, forces, and stresses with ML-computed ones. usage example: \b abiml.py validate FILE --nn-names matgl --nn-names chgnet where `FILE` can be a HIST.nc, a vasprun.xml or an ASE extended XYZ file. """ traj_range = cli.range_from_str(traj_range) nn_names = _get_nn_names(nn_names) ml_comp = aseml.MlValidateWithAbinitio(filepaths, nn_names, traj_range, verbose, workdir, prefix="_abiml_validate_") #print(ml_comp) c = ml_comp.run(nprocs=nprocs) show = False if exposer != "None": from abipy.tools.plotting import Exposer with Exposer.as_exposer(exposer, title=" ".join(os.path.basename(p) for p in filepaths)) as e: e(c.plot_energies(show=show, savefig="energies.png")) if traj: e(c.plot_energies_traj(delta_mode=True, show=show, savefig="energies_traj.png")) if delta: e(c.plot_energies_traj(delta_mode=False, show=show, savefig="energies_traj_delta_mode.png")) e(c.plot_forces(delta_mode=False, symbol=symbol, show=show, savefig="forces.png")) if delta: e(c.plot_forces(delta_mode=True, symbol=symbol, show=show, savefig="forces_delta.png")) if traj: e(c.plot_forces_traj(delta_mode=False, show=show, savefig="forces_traj.png")) if delta: e(c.plot_forces_traj(delta_mode=True, show=show, savefig="forces_traj_delta_mode.png")) if stress: e(c.plot_stresses(delta_mode=False, show=show, savefig="stresses.png")) if delta: e(c.plot_stresses(delta_mode=True, show=show, savefig="stresses_delta_mode.png")) if traj: e(c.plot_stress_traj(delta_mode=False, show=show, savefig="stress_traj.png")) if delta: e(c.plot_stress_traj(delta_mode=True, show=show, savefig="stress_traj_delta_mode.png")) return 0 @main.command() @herald @click.pass_context @click.option('-v', '--verbose', count=True, help="Verbosity level") def show(ctx, verbose): """ Show the NN potentials installed in the environment. """ installed, versions = aseml.get_installed_nn_names(verbose=verbose, printout=True) return 0 if installed else 1 @main.command() @herald @click.pass_context @click.option("-nns", '--nn-names', type=str, multiple=True, show_default=True, help='ML potentials to install.', default=["all"]) @click.option('-U', '--update', is_flag=True, default=False, show_default=True, help="Update packages.") @click.option('-v', '--verbose', count=True, help="Verbosity level") def install(ctx, nn_names, update, verbose): """ Install NN potentials in the environment using pip. """ aseml.install_nn_names(nn_names=nn_names, update=update, verbose=verbose) installed, versions = aseml.get_installed_nn_names(verbose=verbose, printout=True) return 0 if installed else 1 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @click.option("-nns", '--nn-names', type=str, multiple=True, show_default=True, help='ML potentials to compare.', default=["all"]) @click.option('--num-tests', "-n", default=20, type=int, show_default=True, help='Number of configurations to generate.') @click.option("--rattle", default=0.2, type=float, show_default=True, help="Displace atoms randomly with this stdev.") @click.option("-srv", "--stdev-rvol", default=0.1, type=float, show_default=True, help="Scale volumes randomly around input v0 with stdev: v0 * value") @add_workdir_verbose_opts @click.option('--config', default='abiml_compare.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def compare(ctx, filepath, nn_names, num_tests, rattle, stdev_rvol, workdir, verbose ): """ Compare different neural networks. """ atoms = _get_atoms_from_filepath(filepath) nn_names = _get_nn_names(nn_names) ml_comp = aseml.MlCompareNNs(atoms, nn_names, num_tests, rattle, stdev_rvol, verbose, workdir, prefix="_abiml_compare_") print(ml_comp.to_string(verbose=verbose)) ase_comp = ml_comp.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @add_nn_name_opt @add_workdir_verbose_opts @click.option('--config', default='abiml_gs.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def gs(ctx, filepath, nn_name, workdir, verbose, ): """ Compute ground-state properties and magnetic moments with ML potential(s). """ atoms = _get_atoms_from_filepath(filepath) gs = aseml.GsMl(atoms, nn_name, verbose, workdir, prefix="_abiml_gs_") gs.run() return 0 @main.command() @herald @click.pass_context @click.argument("filepath", type=str) @click.option("--qpoint", "-q", nargs=3, type=float, help="q-point in reduced coordinates.") @add_nn_name_opt @add_workdir_verbose_opts @click.option('--config', default='abiml_phfrozen.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def phddb_frozen(ctx, filepath, qpoint, nn_name, workdir, verbose, ): """ Frozen-phonon calculation with ML potential. """ qpoint = [0, 0, 0] eta_list = [1, 2] frozen_ph = aseml.FrozenPhononMl.from_ddb_file(filepath, qpoint, eta_list, nn_name, verbose, workdir, prefix="_abiml_phfrozen") frozen_ph.run() return 0 @main.command() @herald @click.pass_context @click.argument("elements", nargs=-1, type=str) @add_nn_names_opt @add_workdir_verbose_opts @click.option('--config', default='abiml_cwf_eos.yml', type=click.Path(), callback=set_default, is_eager=True, expose_value=False) def cwf_eos(ctx, elements, nn_names, workdir, verbose ): """ Compute CWF EOS with ML potentials. """ nn_names = _get_nn_names(nn_names) if "all" in elements: if len(elements) != 1: raise ValueError(f"When all is used for elements len(elements) should be 1 while {elements=}") from ase.data import chemical_symbols elements = [chemical_symbols[Z] for Z in range(1, 96+1)] ml_cwf_eos = aseml.MlCwfEos(elements, nn_names, verbose, workdir, prefix="_abiml_cwf_eos_") print(ml_cwf_eos.to_string(verbose=verbose)) ml_cwf_eos.run() return 0 if __name__ == "__main__": sys.exit(main())