Source code for dpgen2.conf.alloy_conf

import random
import tempfile
from pathlib import (
    Path,
)
from typing import (
    List,
    Optional,
    Tuple,
    Union,
)

import dpdata
import numpy as np
from dargs import (
    Argument,
    Variant,
)

from .conf_generator import (
    ConfGenerator,
)
from .unit_cells import (
    generate_unit_cell,
)


[docs] class AlloyConfGenerator(ConfGenerator): """ Parameters ---------- numb_confs int Number of configurations to generate lattice Union[dpdata.System, Tuple[str,float]] Lattice of the alloy confs. can be `dpdata.System`: lattice in `dpdata.System` `Tuple[str, float]`: pair of lattice type and lattice constant. lattice type can be "bcc", "fcc", "hcp", "sc" or "diamond" replicate Union[List[int], Tuple[int], int] replicate of the lattice concentration List[List[float]] or List[float] or None If `List[float]`, the concentrations of each element. The length of the list should be the same as the `type_map`. If `List[List[float]]`, a list of concentrations (`List[float]`) is randomly picked from the List. If `None`, the elements are assumed to be of equal concentration. cell_pert_frac float fraction of cell perturbation atom_pert_dist float the atom perturbation distance (unit angstrom). """ def __init__( self, numb_confs, lattice: Union[dpdata.System, Tuple[str, float]], replicate: Union[List[int], Tuple[int, int, int], int, None] = None, concentration: Union[List[List[float]], List[float], None] = None, cell_pert_frac: float = 0.0, atom_pert_dist: float = 0.0, ): self.numb_confs = numb_confs self.lattice = lattice self.replicate = replicate self.concentration = concentration self.cell_pert_frac = cell_pert_frac self.atom_pert_dist = atom_pert_dist
[docs] def generate( self, type_map, ) -> dpdata.MultiSystems: r"""Method of generating configurations. Parameters ---------- type_map : List[str] The type map. Returns ------- confs: dpdata.MultiSystems The returned configurations in `dpdata.MultiSystems` format """ ms = dpdata.MultiSystems(type_map=type_map) ac = AlloyConf(self.lattice, type_map, replicate=self.replicate) systems = ac.generate_systems( self.numb_confs, concentration=self.concentration, cell_pert_frac=self.cell_pert_frac, atom_pert_dist=self.atom_pert_dist, ) for ss in systems: ms.append(ss) return ms
[docs] @staticmethod def doc() -> str: from dpgen2.entrypoint.args import ( make_link, ) return f"Generate alloys with {make_link('a certain lattice or user proided structure', 'explore[lmp]/configurations[alloy]/lattice')}, the elements randomly occuping the lattice with {make_link('user provided probability', 'explore[lmp]/configurations[alloy]/concentration')} ."
[docs] @staticmethod def args() -> List[Argument]: from dpgen2.entrypoint.args import ( make_link, ) link_to_type_map = make_link("type_map", "inputs/type_map") doc_numb_confs = "The number of configurations to generate" doc_lattice = 'The lattice. Should be a list providing [ "lattice_type", lattice_const ], or a list providing [ "/path/to/dpdata/system", "fmt" ]. The two styles are distinguished by the type of the second element. Currently "lattice_type" can be "bcc", "fcc", "hcp", "sc" or "diamond".' doc_replicate = "The number of replicates in each direction" doc_concentration = f"The concentration of each element. `List[List[float]]` or `List[float]` or `None`. If `List[float]`, the concentrations of each element. The length of the list should be the same as the {link_to_type_map}. If `List[List[float]]`, a list of concentrations (`List[float]`) is randomly picked from the List. If `None`, the elements are assumed to be of equal concentration." doc_cell_pert_frac = "The faction of cell perturbation" doc_atom_pert_dist = "The distance of atomic position perturbation" return [ Argument("numb_confs", int, optional=True, default=1, doc=doc_numb_confs), Argument("lattice", [list, tuple], doc=doc_lattice), Argument("replicate", list, optional=True, default=None, doc=doc_replicate), Argument( "concentration", list, optional=True, default=None, doc=doc_concentration, ), Argument( "cell_pert_frac", float, optional=True, default=0.0, doc=doc_cell_pert_frac, ), Argument( "atom_pert_dist", float, optional=True, default=0.0, doc=doc_atom_pert_dist, ), ]
[docs] class AlloyConf: """ Parameters ---------- lattice Union[dpdata.System, Tuple[str,float]] Lattice of the alloy confs. can be `dpdata.System`: lattice in `dpdata.System` `Tuple[str, float]`: pair of lattice type and lattice constant. lattice type can be "bcc", "fcc", "hcp", "sc" or "diamond" replicate Union[List[int], Tuple[int], int] replicate of the lattice type_map List[str] The type map """ def __init__( self, lattice: Union[dpdata.System, Tuple[str, float]], type_map: List[str], replicate: Union[List[int], Tuple[int, int, int], int, None] = None, ) -> None: # init sys if not isinstance(lattice, dpdata.System): sys = generate_unit_cell(lattice[0], lattice[1]) else: sys = lattice assert not isinstance(sys, tuple) # replicate if isinstance(replicate, int): replicate = [replicate] * 3 if replicate is not None: sys = sys.replicate(replicate) # set atom types self.ntypes = len(type_map) self.natoms = sum(sys["atom_numbs"]) # type: ignore sys.data["atom_names"] = type_map sys.data["atom_numbs"] = [0] * self.ntypes sys.data["atom_numbs"][0] = self.natoms sys.data["atom_types"] = np.array([0] * self.natoms, dtype=int) self.type_population = [ii for ii in range(self.ntypes)] # record sys self.sys = sys
[docs] def generate_file_content( self, numb_confs, concentration: Union[List[List[float]], List[float], None] = None, cell_pert_frac: float = 0.0, atom_pert_dist: float = 0.0, fmt: str = "lammps/lmp", ) -> List[str]: """ Parameters ---------- numb_confs int Number of configurations to generate concentration List[List[float]] or List[float] or None If `List[float]`, the concentrations of each element. The length of the list should be the same as the `type_map`. If `List[List[float]]`, a list of concentrations (`List[float]`) is randomly picked from the List. If `None`, the elements are assumed to be of equal concentration. cell_pert_frac float fraction of cell perturbation atom_pert_dist float the atom perturbation distance (unit angstrom). fmt str the format of the returned conf strings. Should be one of the formats supported by `dpdata` Returns ------- conf_list List[str] A list of file content of configurations. """ ret = [] for ii in range(numb_confs): ss = self._generate_one_sys(concentration, cell_pert_frac, atom_pert_dist) tf = Path(tempfile.NamedTemporaryFile().name) ss.to(fmt, tf) ret.append(tf.read_text()) tf.unlink() return ret
[docs] def generate_systems( self, numb_confs, concentration: Union[List[List[float]], List[float], None] = None, cell_pert_frac: float = 0.0, atom_pert_dist: float = 0.0, ) -> List[dpdata.System]: """ Parameters ---------- numb_confs int Number of configurations to generate concentration List[List[float]] or List[float] or None If `List[float]`, the concentrations of each element. The length of the list should be the same as the `type_map`. If `List[List[float]]`, a list of concentrations (`List[float]`) is randomly picked from the List. If `None`, the elements are assumed to be of equal concentration. cell_pert_frac float fraction of cell perturbation atom_pert_dist float the atom perturbation distance (unit angstrom). Returns ------- conf_list List[dpdata.System] A list of generated confs in `dpdata.System`. """ ret = [ self._generate_one_sys(concentration, cell_pert_frac, atom_pert_dist) for ii in range(numb_confs) ] return ret
def _generate_one_sys( self, concentration: Union[List[List[float]], List[float], None] = None, cell_pert_frac: float = 0.0, atom_pert_dist: float = 0.0, ) -> dpdata.System: if concentration is None: cc = [1.0 / float(self.ntypes) for ii in range(self.ntypes)] elif type(concentration) is list and type(concentration[0]) is list: cc = random.choice(concentration) elif type(concentration) is list and ( type(concentration[0]) is float or type(concentration[0]) is int ): cc = concentration else: raise RuntimeError("unsupported concentration type") ret_sys = self.sys.perturb(1, cell_pert_frac, atom_pert_dist)[0] ret_sys.data["atom_types"] = np.array( random.choices( self.type_population, weights=cc, # type: ignore k=self.natoms, ), dtype=int, ) ret_sys.data["atom_numbs"] = list( np.bincount( ret_sys.data["atom_types"], minlength=self.ntypes, ) ) return ret_sys
[docs] def generate_alloy_conf_args(): doc_lattice = 'The lattice. Should be a list providing [ "lattice_type", lattice_const ], or a list providing [ "/path/to/dpdata/system", "fmt" ]. The two styles are distinguished by the type of the second element.' doc_replicate = "The number of replicates in each direction" doc_type_map = "The type map of the system" doc_numb_confs = "The number of configurations to generate" doc_concentration = "The concentration of each element. If None all elements have the same concentration" doc_cell_pert_frac = "The faction of cell perturbation" doc_atom_pert_dist = "The distance of atomic position perturbation" doc_fmt = "The format of file content" return [ Argument("lattice", [list, tuple], doc=doc_lattice), Argument("type_map", list, doc=doc_type_map), Argument("replicate", list, optional=True, default=None, doc=doc_replicate), Argument("numb_confs", int, optional=True, default=1, doc=doc_numb_confs), Argument( "concentration", list, optional=True, default=None, doc=doc_concentration ), Argument( "cell_pert_frac", float, optional=True, default=0.0, doc=doc_cell_pert_frac ), Argument( "atom_pert_dist", float, optional=True, default=0.0, doc=doc_atom_pert_dist ), Argument("fmt", str, optional=True, default="lammps/lmp", doc=doc_fmt), ]
[docs] def normalize(data): sca = generate_alloy_conf_args() base = Argument("base", dict, sca) data = base.normalize_value(data, trim_pattern="_*") base.check_value(data, strict=True) return data
[docs] def gen_doc(*, make_anchor=True, make_link=True, **kwargs): if make_link: make_anchor = True sca = generate_alloy_conf_args() base = Argument( "conf_config", dict, sca, doc="Generate file content of alloy configurations" ) ptr = [] ptr.append(base.gen_doc(make_anchor=make_anchor, make_link=make_link, **kwargs)) key_words = [] for ii in "\n\n".join(ptr).split("\n"): if "argument path" in ii: key_words.append(ii.split(":")[1].replace("`", "").strip()) return "\n\n".join(ptr)
[docs] def generate_alloy_conf_file_content( lattice: Union[dpdata.System, Tuple[str, float]], type_map: List[str], numb_confs, replicate: Union[List[int], Tuple[int, int, int], int, None] = None, concentration: Union[List[List[float]], List[float], None] = None, cell_pert_frac: float = 0.0, atom_pert_dist: float = 0.0, fmt: str = "lammps/lmp", ): ac = AlloyConf(lattice, type_map, replicate=replicate) return ac.generate_file_content( numb_confs, concentration=concentration, fmt=fmt, cell_pert_frac=cell_pert_frac, atom_pert_dist=atom_pert_dist, )