class MEAMPotential(AbstractPotential, EAMlikeMixin): def __init__(self, init=None, identifier=None, export_file=None, species=None): super().__init__(init=init) if init is None: self.pair_interactions = DataContainer( table_name="pair_interactions") self.electron_densities = DataContainer( table_name="electron_densities") self.embedding_energies = DataContainer( table_name="embedding_energies") self.f_functions = DataContainer(table_name="f_functions") self.g_functions = DataContainer(table_name="g_functions") self.identifier = identifier self.export_file = export_file self.species = species @property def _function_tuple(self): return (self.pair_interactions, self.electron_densities, self.embedding_energies, self.f_functions, self.g_functions) def write_xml_file(self, directory): """ Internal function to convert to an xml element """ meam = ET.Element("meam") meam.set("id", f"{self.identifier}") meam.set("species-a", f"{self.species[0]}") meam.set("species-b", f"{self.species[1]}") if self.export_file: export = ET.SubElement(meam, "export-functions") export.text = f"{self.export_file}" mapping = ET.SubElement(meam, "mapping") functions = ET.SubElement(meam, "functions") for pot in self.pair_interactions.values(): pair_interaction = ET.SubElement(mapping, "pair-interaction") pair_interaction.set("species-a", f"{pot.species[0]}") pair_interaction.set("species-b", f"{pot.species[1]}") pair_interaction.set("function", f"{pot.identifier}") functions.append(pot._to_xml_element()) for pot in self.electron_densities.values(): electron_density = ET.SubElement(mapping, "electron-density") electron_density.set("species-a", f"{pot.species[0]}") electron_density.set("species-b", f"{pot.species[1]}") electron_density.set("function", f"{pot.identifier}") functions.append(pot._to_xml_element()) for pot in self.f_functions.values(): f_function = ET.SubElement(mapping, "f-function") f_function.set("species-a", f"{pot.species[0]}") f_function.set("species-b", f"{pot.species[1]}") f_function.set("function", f"{pot.identifier}") functions.append(pot._to_xml_element()) for pot in self.g_functions.values(): g_function = ET.SubElement(mapping, "g-function") g_function.set("species-a", f"{pot.species[0]}") g_function.set("species-b", f"{pot.species[1]}") g_function.set("species-c", f"{pot.species[2]}") g_function.set("function", f"{pot.identifier}") functions.append(pot._to_xml_element()) for pot in self.embedding_energies.values(): embedding_energy = ET.SubElement(mapping, "embedding-energy") embedding_energy.set("species", f"{pot.species[0]}") embedding_energy.set("function", f"{pot.identifier}") functions.append(pot._to_xml_element()) filename = posixpath.join(directory, "potential.xml") write_pretty_xml(meam, filename) def _parse_final_parameters(self, lines): """ Internal Function. Parse function parameters from atomicrex output. Args: lines (list[str]): atomicrex output lines Raises: KeyError: Raises if a parsed parameter can't be matched to a function. """ for l in lines: identifier, param, value = _parse_parameter_line(l) if identifier in self.pair_interactions: self.pair_interactions[identifier]._parse_final_parameter( leftover, value) elif identifier in self.electron_densities: self.electron_densities[identifier]._parse_final_parameter( leftover, value) elif identifier in self.f_functions: self.f_functions[identifier]._parse_final_parameter( leftover, value) elif identifier in self.g_functions: self.g_functions[identifier]._parse_final_parameter( leftover, value) elif identifier in self.embedding_energies: self.embedding_energies[identifier]._parse_final_parameter( leftover, value) else: raise KeyError( f"Can't find {identifier} in potential, probably something went wrong during parsing.\n" "Fitting parameters of screening functions probably doesn't work right now" )
class EAMPotential(AbstractPotential, EAMlikeMixin): """ Embedded-Atom-Method potential. Usage: Create using the potential factory class. Add functions defined using the function_factory to self.pair_interactions, self.electron_densities and self.embedding_energies in dictionary style, using the identifier of the function as key. Example: eam.pair_interactions["V"] = morse_function """ def __init__(self, init=None, identifier=None, export_file=None, rho_range_factor=None, resolution=None, species=None): super().__init__(init=init) if init is None: self.pair_interactions = DataContainer( table_name="pair_interactions") self.electron_densities = DataContainer( table_name="electron_densities") self.embedding_energies = DataContainer( table_name="embedding_energies") self.identifier = identifier self.export_file = export_file self.rho_range_factor = rho_range_factor self.resolution = resolution self.species = species @property def _function_tuple(self): return (self.pair_interactions, self.electron_densities, self.embedding_energies) def _potential_as_pd_df(self, job): """ Makes the tabulated eam potential written by atomicrex usable for pyiron lammps jobs. """ if self.export_file is None: raise ValueError( "export_file must be set to use the potential with lammps") species = [el for el in job.input.atom_types.keys()] species_str = "" for s in species: species_str += f"{s} " pot = pd.DataFrame({ "Name": f"{self.identifier}", "Filename": [[f"{job.working_directory}/{self.export_file}"]], 'Model': ['Custom'], "Species": [species], "Config": [[ "pair_style eam/fs\n", f"pair_coeff * * {job.working_directory}/{self.export_file} {species_str}\n", ]] }) return pot def write_xml_file(self, directory): """ Internal function to convert to an xml element """ eam = ET.Element("eam") eam.set("id", f"{self.identifier}") eam.set("species-a", f"{self.species[0]}") eam.set("species-b", f"{self.species[1]}") if self.export_file: export = ET.SubElement(eam, "export-eam-file") export.set("resolution", f"{self.resolution}") export.set("rho-range-factor", f"{self.rho_range_factor}") export.text = f"{self.export_file}" mapping = ET.SubElement(eam, "mapping") functions = ET.SubElement(eam, "functions") for pot in self.pair_interactions.values(): pair_interaction = ET.SubElement(mapping, "pair-interaction") pair_interaction.set("species-a", f"{pot.species[0]}") pair_interaction.set("species-b", f"{pot.species[1]}") pair_interaction.set("function", f"{pot.identifier}") functions.append(pot._to_xml_element()) for pot in self.electron_densities.values(): electron_density = ET.SubElement(mapping, "electron-density") electron_density.set("species-a", f"{pot.species[0]}") electron_density.set("species-b", f"{pot.species[1]}") electron_density.set("function", f"{pot.identifier}") functions.append(pot._to_xml_element()) for pot in self.embedding_energies.values(): embedding_energy = ET.SubElement(mapping, "embedding-energy") embedding_energy.set("species", f"{pot.species[0]}") embedding_energy.set("function", f"{pot.identifier}") functions.append(pot._to_xml_element()) filename = posixpath.join(directory, "potential.xml") write_pretty_xml(eam, filename) def _parse_final_parameters(self, lines): """ Internal Function. Parse function parameters from atomicrex output. Args: lines (list[str]): atomicrex output lines Raises: KeyError: Raises if a parsed parameter can't be matched to a function. """ for l in lines: identifier, leftover, value = _parse_parameter_line(l) if identifier in self.pair_interactions: self.pair_interactions[identifier]._parse_final_parameter( leftover, value) elif identifier in self.electron_densities: self.electron_densities[identifier]._parse_final_parameter( leftover, value) elif identifier in self.embedding_energies: self.embedding_energies[identifier]._parse_final_parameter( leftover, value) else: raise KeyError( f"Can't find {identifier} in potential, probably something went wrong during parsing.\n" "Fitting parameters of screening functions probably doesn't work right now" ) def plot_final_potential(self, job, filename=None): """ Plots the the fitted potential. Reads the necessary data from eam.fs file (funcfl format used by lammps), therefore does not work if no table is written. Can be used with disabled fitting to plot functions that can't be plotted directly like splines. Args: job (AtomicrexJob): Instance of the job to construct filename. filename (str, optional): If a filename is given this eam is plotted instead of the fitted one. Defaults to None. """ # Read the cotents of the eam file # this is copy pasted from one of my old scripts and could probably be reworked elements = {} if filename is None: filename = f"{job.working_directory}/{self.export_file}" with open(filename, "r") as f: # skip comment lines for _ in range(3): f.readline() element_list = f.readline().split() for element in element_list[1:]: elements[element] = {} Nrho, drho, Nr, dr, cutoff = f.readline().split() Nrho = int(Nrho) drho = float(drho) Nr = int(Nr) dr = float(dr) cutoff = float(cutoff) rho_values = np.linspace(0, Nrho * drho, Nrho, endpoint=False) r_values = np.linspace(0, Nr * dr, Nr, endpoint=False) for element in elements: # skip a line with unnecessary information f.readline() elements[element]["F"] = np.fromfile(f, count=Nrho, sep=" ") for rho_element in elements: elements[element]["rho_{}{}".format( element, rho_element)] = np.fromfile(f, count=Nr, sep=" ") # V_ij = V_ji so it is written only once in the file => avoid attempts to read it twice # with a list of elements where it has been read # TODO: Have another look how to do this checking V_written = [] for element in elements: for V_element in elements: elementV_element = "{}{}".format(element, V_element) V_elementelement = "{}{}".format(V_element, element) if not elementV_element in V_written and not V_elementelement in V_written: elements[element]["V_{}".format( elementV_element)] = np.fromfile(f, count=Nr, sep=" ") # The tabulated values are not V(r) but V(r) * r, so they are divided by r here, # with exception of the first value to prevent division by 0. elements[element]["V_{}".format(elementV_element)][ 1:] = elements[element]["V_{}".format( elementV_element)][1:] / r_values[1:] V_written.append(elementV_element) # TODO: figure out how to index ax for multiple elements fig, ax = plt.subplots(nrows=3 * len(elements), ncols=len(elements), figsize=(len(elements) * 8, len(elements) * 3 * 6), squeeze=False) for i, (el, pot_dict) in enumerate(elements.items()): for j, (pot, y) in enumerate(pot_dict.items()): if pot == "F": xdata = rho_values xlabel = "$\\rho $ [a.u.]" k = 0 elif "rho" in pot: xdata = r_values xlabel = "r [$\AA$]" k = 1 elif "V" in pot: xdata = r_values[1:] y = y[1:] xlabel = "r [$\AA$]" k = 2 ax[i + k, 0].plot(xdata, y) ax[i + k, 0].set(ylim=(-5, 5), title=f"{el} {pot}", xlabel=xlabel) return fig, ax