class HasStorage(ABC): """ A base class for objects that use HDF5 data serialization via the `DataContainer` class. """ def __init__(self, *args, **kwargs): self._storage = DataContainer(table_name='storage') @property def storage(self): return self._storage def to_hdf(self, hdf, group_name=None): """ Serialize everything in the `storage` field to HDF5. Args: hdf (ProjectHDFio): HDF5 group object. group_name (str, optional): HDF5 subgroup name. """ if group_name is not None: hdf = hdf.create_group(group_name) self._storage.to_hdf(hdf=hdf) def from_hdf(self, hdf, group_name=None): """ Restore the everything in the `storage` field from an HDF5 file. Args: hdf (ProjectHDFio): HDF5 group object. group_name (str, optional): HDF5 subgroup name. """ if group_name is not None: hdf = hdf.create_group(group_name) self._storage.from_hdf(hdf=hdf)
def __init__(self, project, job_name): super().__init__(project, job_name) self.input = DataContainer(inputdict, table_name="inputdata") self._potential_initial = None self._potential_final = None self.input.potential_initial_name = None self.input.potential_final_name = None self.input.structure = None self.output = DataContainer(table_name="output") self._data = None self.input._pot_dict_initial = None self.input._pot_dict_final = None
def __init__(self, project, job_name): super(SQSJob, self).__init__(project, job_name) # self.input = InputList(table_name='input') self.input = DataContainer(table_name='custom_dict') self.input.mole_fractions = dict() self.input.weights = None self.input.objective = 0.0 self.input.iterations = 1e6 self.input.n_output_structures = 1 self._python_only_job = True self._lst_of_struct = [] self.__hdf_version__ = "0.2.0" self.__name__ = "SQSJob" s.publication_add(self.publication)
def structure_lst(self): """ :class:`.DataContainer`: list of :class:`~.Atoms` """ if len(self._structure_lst) != len(self._container): self._structure_lst = DataContainer(list(self._container.iter_structures())) return self._structure_lst
def __init__(self, project, job_name): super().__init__(project, job_name) self.__version__ = "0.2.0" self.__hdf_version__ = "0.3.0" self._structure_lst = DataContainer(table_name="structures") self._container = StructureStorage() self.server.run_mode.interactive = True
def __init__(self, project, job_name): super().__init__(project, job_name) self._training_ids = [] self.input = DataContainer(table_name="input") self.input.element = "Cu" s.publication_add(self.publication)
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
def __init__(self, project, job_name): super(ART, self).__init__(project, job_name) self.input = DataContainer(table_name="custom_dict") self.input.gamma = 0.1 self.input.fix_layer = False self.input.non_art_id = None self.input.art_id = None self.input.direction = None self.output = ARTIntOutput(job=self) self.server.run_mode.interactive = True self._interactive_interface = None self._art = None
class AbstractMetaFunction(DataContainer, MetaFunctionMixin): def __init__(self, identifier=None, species=None, table_name=None): super().__init__() self.identifier = identifier self.functions = DataContainer(table_name=table_name) self.species = species def _to_xml_element(self, func_name): root = ET.Element(func_name) root.set("id", self.identifier) for k, v in self.functions.items(): root.append(v._to_xml_element()) return root def _parse_final_parameter(self, leftover, value): identifier = leftover[0].split("[")[0] leftover = leftover[1:] try: self.functions[identifier]._parse_final_parameter(leftover, value) except KeyError: raise KeyError( f"Function {identifier} not found in {self.identifier}")
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
def __init__(self, project, job_name): super(ImageJob, self).__init__(project, job_name) self.__name__ = "ImageJob" self._images = DistributingList() self.input = DataContainer(table_name="input") self.output = DataContainer(table_name="output")
class Calphy(GenericJob): """ Class to set up and run calphy jobs for calculation of free energies using LAMMPS. An input structure (:attr:`structure`) and interatomic potential (:attr:`potential`) are necessary input options. The additional input options such as the temperature and pressure are specified in the :meth:`.calc_free_energy` method. Depending on the input parameters, a corresponding calculation mode is selected. Further input options can be accessed through :attr:`input.md` and :attr:`input.tolerance`. An example which calculates the free energy of Cu using an interatomic potential: ```python job.structure = pr.create.structure.ase.bulk('Cu', cubic=True).repeat(5) job.potential = "2001--Mishin-Y--Cu-1--LAMMPS--ipr1" job.calc_free_energy(temperature=1100, pressure=0, reference_phase="solid") job.run() ``` In order to calculate the free energy of the liquid phase, the `reference_phase` should be set to `liquid`. The different modes can be selected as follows: For free energy at a given temperature and pressure: ```python job.calc_free_energy(temperature=1100, pressure=0, reference_phase="solid") ``` Alternatively, :func:`calc_mode_fe` can be used. To obtain the free energy between a given temperature range (temperature scaling): ```python job.calc_free_energy(temperature=[1100, 1400], pressure=0, reference_phase="solid") ``` Alternatively, :func:`calc_mode_ts` can be used. For free energy between a given pressure range (pressure scaling) ```python job.calc_free_energy(temperature=1000, pressure=[0, 100000], reference_phase="solid") ``` Alternatively, :func:`calc_mode_pscale` can be used. To obtain the free energy difference between two interatomic potentials (alchemy/upsampling) ```python job.potential = ["2001--Mishin-Y--Cu-1--LAMMPS--ipr1", "1986--Foiles-S-M--Cu--LAMMPS--ipr1"] job.calc_free_energy(temperature=1100, pressure=0, reference_phase="solid") job.run() ``` Alternatively, :func:`calc_mode_alchemy` can be used. The way `pressure` is specified determines how the barostat affects the system. For isotropic pressure control: ```python job.calc_free_energy(temperature=[1100, 1400], pressure=0, reference_phase="solid") ``` For anisotropic pressure control: ```python job.calc_free_energy(temperature=[1100, 1400], pressure=[0, 0, 0], reference_phase="solid") ``` To constrain the lattice: ```python job.calc_free_energy(temperature=[1100, 1400], pressure=None, reference_phase="solid") ``` In addition the boolean option :attr:`input.npt` can be used to determine the MD ensemble. If True, temperature integration and alchemy/upsampling are carried out in the NPT ensemble. If False, the NVT ensemble is employed. After the calculation is over, the various output options can be accessed through `job.output`. """ def __init__(self, project, job_name): super().__init__(project, job_name) self.input = DataContainer(inputdict, table_name="inputdata") self._potential_initial = None self._potential_final = None self.input.potential_initial_name = None self.input.potential_final_name = None self.input.structure = None self.output = DataContainer(table_name="output") self._data = None self.input._pot_dict_initial = None self.input._pot_dict_final = None def set_potentials(self, potential_filenames: Union[list, str]): """ Set the interatomic potential from a given name Args: potential_filenames (list, str): list of filenames Returns: None """ if not isinstance(potential_filenames, list): potential_filenames = [potential_filenames] if len(potential_filenames) > 0: if isinstance(potential_filenames[0], pd.DataFrame): potential = potential_filenames[0] self.input._pot_dict_initial = potential # .to_dict() else: potential = LammpsPotentialFile().find_by_name( potential_filenames[0]) self.input.potential_initial_name = potential_filenames[0] self._potential_initial = LammpsPotential() self._potential_initial.df = potential if len(potential_filenames) > 1: if isinstance(potential_filenames[1], pd.DataFrame): potential = potential_filenames[1] self.input._pot_dict_final = potential # .to_dict() else: potential = LammpsPotentialFile().find_by_name( potential_filenames[1]) self.input.potential_final_name = potential_filenames[1] self._potential_final = LammpsPotential() self._potential_final.df = potential if len(potential_filenames) > 2: raise ValueError("Maximum two potentials can be provided") def get_potentials(self) -> List[str]: """ Return the interatomic potentials Args: None Returns: list of str: list of interatomic potentials """ if self._potential_final is None: return [self._potential_initial.df] else: return [self._potential_initial.df, self._potential_final.df] def copy_pot_files(self): """ Copy potential files to the working directory Args: None Returns: None """ if self._potential_initial is not None: self._potential_initial.copy_pot_files(self.working_directory) if self._potential_final is not None: self._potential_final.copy_pot_files(self.working_directory) def _prepare_pair_styles(self) -> Tuple[List, List]: """ Prepare pair style and pair coeff Args: None Returns: list: pair style and pair coeff """ pair_style = [] pair_coeff = [] if self._potential_initial is not None: pair_style.append(self._potential_initial.df["Config"].to_list()[0] [0].strip().split()[1:][0]) pair_coeff.append( " ".join(self._potential_initial.df["Config"].to_list()[0] [1].strip().split()[1:])) if self._potential_final is not None: pair_style.append(self._potential_final.df["Config"].to_list()[0] [0].strip().split()[1:][0]) pair_coeff.append( " ".join(self._potential_final.df["Config"].to_list()[0] [1].strip().split()[1:])) return pair_style, pair_coeff def _potential_from_hdf(self): """ Recreate the potential from filename stored in hdf5 Args: None Returns: None """ filenames = [] if self.input.potential_initial_name is not None: filenames.append(self.input.potential_initial_name) elif self.input._pot_dict_initial is not None: filenames.append(pd.DataFrame(data=self.input._pot_dict_initial)) if self.input.potential_final_name is not None: filenames.append(self.input.potential_final_name) elif self.input._pot_dict_final is not None: filenames.append(pd.DataFrame(data=self.input._pot_dict_final)) self.set_potentials(filenames) @property def potential(self): potentials = self.get_potentials() if len(potentials) == 1: return potentials[0] return potentials @potential.setter def potential(self, potential_filenames): self.set_potentials(potential_filenames) @property def structure(self): return self.input.structure @structure.setter def structure(self, val): self.input.structure = val def view_potentials(self) -> List: """ View a list of available interatomic potentials Args: None Returns: list: list of available potentials """ if not self.structure: raise ValueError("please assign a structure first") else: list_of_elements = set(self.structure.get_chemical_symbols()) list_of_potentials = LammpsPotentialFile().find(list_of_elements) if list_of_potentials is not None: return list_of_potentials else: raise TypeError( "No potentials found for this kind of structure: ", str(list_of_elements), ) def structure_to_lammps(self): """ Convert structure to LAMMPS structure Args: None Returns: list: pair style and pair coeff """ prism = UnfoldingPrism(self.input.structure.cell) lammps_structure = self.input.structure.copy() lammps_structure.set_cell(prism.A) lammps_structure.positions = np.matmul(self.input.structure.positions, prism.R) return lammps_structure def write_structure(self, structure, file_name: str, working_directory: str): """ Write structure to file Args: structure: input structure file_name (str): output file name working_directory (str): output working directory Returns: None """ lmp_structure = LammpsStructure() lmp_structure.potential = self._potential_initial lmp_structure.el_eam_lst = set(structure.get_chemical_symbols()) lmp_structure.structure = self.structure_to_lammps() lmp_structure.write_file(file_name=file_name, cwd=working_directory) def determine_mode(self): """ Determine the calculation mode Args: None Returns: None """ if len(self.get_potentials()) == 2: self.input.mode = "alchemy" self.input.reference_phase = "alchemy" elif isinstance(self.input.pressure, list): if len(self.input.pressure) == 2: self.input.mode = "pscale" elif isinstance(self.input.temperature, list): if len(self.input.temperature) == 2: self.input.mode = "ts" else: self.input.mode = "fe" # if mode was not set, raise Error if self.input.mode is None: raise RuntimeError("Could not determine the mode") def write_input(self): """ Write input for calphy calculation Args: None Returns: None """ # now prepare the calculation calc = Calculation() for key in inputdict.keys(): if key not in ["md", "tolerance"]: setattr(calc, key, self.input[key]) for key in inputdict["md"].keys(): setattr(calc.md, key, self.input["md"][key]) for key in inputdict["tolerance"].keys(): setattr(calc.tolerance, key, self.input["tolerance"][key]) file_name = "conf.data" self.write_structure(self.structure, file_name, self.working_directory) calc.lattice = os.path.join(self.working_directory, "conf.data") self.copy_pot_files() pair_style, pair_coeff = self._prepare_pair_styles() calc._fix_potential_path = False calc.pair_style = pair_style calc.pair_coeff = pair_coeff calc.element = list(np.unique(self.structure.get_chemical_symbols())) calc.mass = list(np.unique(self.structure.get_masses())) calc.queue.cores = self.server.cores self.calc = calc def calc_mode_fe( self, temperature: float = None, pressure: Union[list, float, None] = None, reference_phase: str = None, n_equilibration_steps: int = 15000, n_switching_steps: int = 25000, n_print_steps: int = 0, n_iterations: int = 1, ): """ Calculate free energy at given conditions Args: None Returns: None """ if temperature is None: raise ValueError("provide a temperature") self.input.temperature = temperature self.input.pressure = pressure self.input.reference_phase = reference_phase self.input.n_equilibration_steps = n_equilibration_steps self.input.n_switching_steps = n_switching_steps self.input.n_print_steps = n_print_steps self.input.n_iterations = n_iterations self.input.mode = "fe" def calc_mode_ts( self, temperature: float = None, pressure: Union[list, float, None] = None, reference_phase: str = None, n_equilibration_steps: int = 15000, n_switching_steps: int = 25000, n_print_steps: int = 0, n_iterations: int = 1, ): """ Calculate free energy between given temperatures Args: None Returns: None """ if temperature is None: raise ValueError("provide a temperature") self.input.temperature = temperature self.input.pressure = pressure self.input.reference_phase = reference_phase self.input.n_equilibration_steps = n_equilibration_steps self.input.n_switching_steps = n_switching_steps self.input.n_print_steps = n_print_steps self.input.n_iterations = n_iterations self.input.mode = "ts" def calc_mode_alchemy( self, temperature: float = None, pressure: Union[list, float, None] = None, reference_phase: str = None, n_equilibration_steps: int = 15000, n_switching_steps: int = 25000, n_print_steps: int = 0, n_iterations: int = 1, ): """ Perform upsampling/alchemy between two interatomic potentials Args: None Returns: None """ if temperature is None: raise ValueError("provide a temperature") self.input.temperature = temperature self.input.pressure = pressure self.input.reference_phase = reference_phase self.input.n_equilibration_steps = n_equilibration_steps self.input.n_switching_steps = n_switching_steps self.input.n_print_steps = n_print_steps self.input.n_iterations = n_iterations self.input.mode = "alchemy" def calc_mode_pscale( self, temperature: float = None, pressure: Union[list, float, None] = None, reference_phase: str = None, n_equilibration_steps: int = 15000, n_switching_steps: int = 25000, n_print_steps: int = 0, n_iterations: int = 1, ): """ Calculate free energy between two given pressures Args: None Returns: None """ if temperature is None: raise ValueError("provide a temperature") self.input.temperature = temperature self.input.pressure = pressure self.input.reference_phase = reference_phase self.input.n_equilibration_steps = n_equilibration_steps self.input.n_switching_steps = n_switching_steps self.input.n_print_steps = n_print_steps self.input.n_iterations = n_iterations self.input.mode = "pscale" def calc_free_energy( self, temperature: float = None, pressure: Union[list, float, None] = None, reference_phase: str = None, n_equilibration_steps: int = 15000, n_switching_steps: int = 25000, n_print_steps: int = 0, n_iterations: int = 1, ): """ Calculate free energy at given conditions Args: None Returns: None """ if temperature is None: raise ValueError("provide a temperature") self.input.temperature = temperature self.input.pressure = pressure self.input.reference_phase = reference_phase self.input.n_equilibration_steps = n_equilibration_steps self.input.n_switching_steps = n_switching_steps self.input.n_print_steps = n_print_steps self.input.n_iterations = n_iterations self.determine_mode() def run_static(self): self.status.running = True if self.input.reference_phase == "alchemy": job = Alchemy(calculation=self.calc, simfolder=self.working_directory) elif self.input.reference_phase == "solid": job = Solid(calculation=self.calc, simfolder=self.working_directory) elif self.input.reference_phase == "liquid": job = Liquid(calculation=self.calc, simfolder=self.working_directory) else: raise ValueError("Unknown reference state") if self.input.mode == "alchemy": routine_alchemy(job) elif self.input.mode == "fe": routine_fe(job) elif self.input.mode == "ts": routine_ts(job) elif self.input.mode == "pscale": routine_pscale(job) else: raise ValueError("Unknown mode") self._data = job.report # save conc for later use self.input.concentration = job.concentration del self._data["input"] self.status.collect = True self.run() def collect_general_output(self): """ Collect the output from calphy Args: None Returns: None """ if self._data is not None: if "spring_constant" in self._data["average"].keys(): self.output.spring_constant = self._data["average"][ "spring_constant"] self.output.energy_free = self._data["results"]["free_energy"] self.output.energy_free_error = self._data["results"]["error"] self.output.energy_free_reference = self._data["results"][ "reference_system"] self.output.energy_work = self._data["results"]["work"] self.output.temperature = self.input.temperature f_ediff, b_ediff, flambda, blambda = self.collect_ediff() self.output.forward_energy_diff = list(f_ediff) self.output.backward_energy_diff = list(b_ediff) self.output.forward_lambda = list(flambda) self.output.backward_lambda = list(blambda) if self.input.mode == "ts": datfile = os.path.join(self.working_directory, "temperature_sweep.dat") t, fe, ferr = np.loadtxt(datfile, unpack=True, usecols=(0, 1, 2)) self.output.energy_free = np.array(fe) self.output.energy_free_error = np.array(ferr) self.output.temperature = np.array(t) elif self.input.mode == "pscale": datfile = os.path.join(self.working_directory, "pressure_sweep.dat") p, fe, ferr = np.loadtxt(datfile, unpack=True, usecols=(0, 1, 2)) self.output.energy_free = np.array(fe) self.output.energy_free_error = np.array(ferr) self.output.pressure = np.array(p) def collect_ediff(self): """ Calculate the energy difference between reference system and system of interest Args: None Returns: None """ f_ediff = [] b_ediff = [] for i in range(1, self.input.n_iterations + 1): fwdfilename = os.path.join(self.working_directory, "forward_%d.dat" % i) bkdfilename = os.path.join(self.working_directory, "backward_%d.dat" % i) nelements = self.calc.n_elements if self.input.reference_phase == "solid": fdui = np.loadtxt(fwdfilename, unpack=True, comments="#", usecols=(0, )) bdui = np.loadtxt(bkdfilename, unpack=True, comments="#", usecols=(0, )) fdur = np.zeros(len(fdui)) bdur = np.zeros(len(bdui)) for i in range(nelements): fdur += self.input.concentration[i] * np.loadtxt( fwdfilename, unpack=True, comments="#", usecols=(i + 1, )) bdur += self.input.concentration[i] * np.loadtxt( bkdfilename, unpack=True, comments="#", usecols=(i + 1, )) flambda = np.loadtxt(fwdfilename, unpack=True, comments="#", usecols=(nelements + 1, )) blambda = np.loadtxt(bkdfilename, unpack=True, comments="#", usecols=(nelements + 1, )) else: fdui, fdur, flambda = np.loadtxt(fwdfilename, unpack=True, comments="#", usecols=(0, 1, 2)) bdui, bdur, blambda = np.loadtxt(bkdfilename, unpack=True, comments="#", usecols=(0, 1, 2)) f_ediff.append(fdui - fdur) b_ediff.append(bdui - bdur) return f_ediff, b_ediff, flambda, blambda def collect_output(self): self.collect_general_output() self.to_hdf() def to_hdf(self, hdf=None, group_name=None): super().to_hdf(hdf=hdf, group_name=group_name) self.input.to_hdf(self.project_hdf5) self.output.to_hdf(self.project_hdf5) def from_hdf(self, hdf=None, group_name=None): super().from_hdf(hdf=hdf, group_name=group_name) self.input.from_hdf(self.project_hdf5) self.output.from_hdf(self.project_hdf5) self._potential_from_hdf()
class SQSJob(AtomisticGenericJob): """ Produces special quasirandom structures designed to duplicate truly random chemical distributions as well as possible while using finite periodic cells. A pyiron wrapper for the [SQS code of Dominik Gehringer](https://github.com/dgehringer/sqsgenerator). 'Structural models used in calculations of properties of substitutionally random $A_{1-x}B_x$ alloys are usually constructed by randomly occupying each of the N sites of a periodic cell by A or B. We show that it is possible to design ‘‘special quasirandom structures’’ (SQS’s) that mimic for small N (even N=8) the first few, physically most relevant radial correlation functions of a perfectly random structure far better than the standard technique does. We demonstrate the usefulness of these SQS’s by calculating optical and thermodynamic properties of a number of semiconductor alloys in the local-density formalism.' From the abstract of Zunger, Wei, Ferreira, and Bernard, Phys. Rev. Lett. 65 (1990) 353, DOI:https://doi.org/10.1103/PhysRevLett.65.353 Input: - mole_fractions (dict): Approximate chemical composition for the output structure(s), using chemical symbols as the keys and floats as the values. Values should sum to 1, but within reason will be automatically adjusted to accommodate the number of atoms in the provided structure (adjustments printed to standard out). Vacancies can also be included using the key '0'. - weights (list/numpy.ndarray): Specifies the weights of the individual shells. (Default is None, which uses the inverse of the shell number for the weight, i.e. [1, 0.5, 0.3333333333, 0.25, 0.2, 0.166666667, 0.1428571429].) - objective (float): Specifies the value the objective functions. The program tries to reach the specified objective function. (Default is 0.) - iterations (int): How many iterations to make searching for the most special quasirandom structure using a random shuffling procedure. (Default is 1e6) - n_output_structures (int): How many different SQS structures to return (in decreasing special quasirandomness). (Default is 1.) Example: Case I: Get SQS for a given mole fraction: >>> job = SQSJob(job_name='sqs') >>> job.structure = structure >>> job.input.mole_fractions = {'Al': 0.8, 'Ni':0.2} >>> job.run() Case II: Get SQS for a given structure already containing at least 2 elements: >>> job = SQSJob(job_name='sqs') >>> job.structure = structure >>> job.run() In Case II, if the mole fractions will be overwritten if you specify the values (like in Case I) """ publication = { "sqs": { "method": { "title": "Special quasirandom structures", "author": [ "Zunger, A.", "Wei, S.-H.", "Ferreira, L.G.", "Bernard, J.E." ], "journal": "Phys. Rev. Lett.", "volume": "65", "issue": "3", "pages": "353", "numpages": "0", "month": "July", "publisher": "American Physical Society", "doi": "10.1103/PhysRevLett.65.353", "url": "https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.65.353", } } } @import_alarm def __init__(self, project, job_name): super(SQSJob, self).__init__(project, job_name) # self.input = InputList(table_name='input') self.input = DataContainer(table_name='custom_dict') self.input.mole_fractions = dict() self.input.weights = None self.input.objective = 0.0 self.input.iterations = 1e6 self.input.n_output_structures = 1 self._python_only_job = True self._lst_of_struct = [] self.__hdf_version__ = "0.2.0" self.__name__ = "SQSJob" s.publication_add(self.publication) @property def list_of_structures(self): return self._lst_of_struct def validate_ready_to_run(self): super(SQSJob, self).validate_ready_to_run() if len(self.input.mole_fractions) == 0: chem = np.unique(self.structure.get_chemical_symbols(), return_counts=True) self.input.mole_fractions = dict( zip(chem[0], chem[1] / np.sum(chem[1]))) if len(self.input.mole_fractions) == 1: raise ValueError('There must be at least two chemical elements') def db_entry(self): """ Generate the initial database entry Returns: (dict): db_dict """ db_dict = super(SQSJob, self).db_entry() if self.structure: struct_len = len(self.structure) mole_fractions = { k: v for k, v in self.input.mole_fractions.items() } el_lst = sorted(mole_fractions.keys()) atom_number_lst = [ int(np.round(mole_fractions[el] * struct_len)) for el in el_lst ] db_dict["ChemicalFormula"] = "".join( [e + str(n) for e, n in zip(el_lst, atom_number_lst)]) return db_dict def list_structures(self): if self.status.finished: return self._lst_of_struct else: return [] def get_structure(self, iteration_step=-1, wrap_atoms=True): """ Gets the structure from a given iteration step of the simulation (MD/ionic relaxation). For static calculations there is only one ionic iteration step Args: iteration_step (int): Step for which the structure is requested wrap_atoms (bool): True if the atoms are to be wrapped back into the unit cell Returns: pyiron.atomistics.structure.atoms.Atoms: The required structure """ return self.list_structures()[iteration_step] # This function is executed def run_static(self): self._lst_of_struct, decmp, iterations, cycle_time = get_sqs_structures( structure=self.structure, mole_fractions={ k: v for k, v in self.input.mole_fractions.items() }, weights=self.input.weights, objective=self.input.objective, iterations=self.input.iterations, output_structures=self.input.n_output_structures, num_threads=self.server.cores) for i, structure in enumerate(self._lst_of_struct): with self.project_hdf5.open("output/structures/structure_" + str(i)) as h5: structure.to_hdf(h5) with self.project_hdf5.open("output") as h5: h5["decmp"] = decmp h5["cycle_time"] = cycle_time h5["iterations"] = iterations self.status.finished = True self.project.db.item_update(self._runtime(), self.job_id) def to_hdf(self, hdf=None, group_name=None): super().to_hdf(hdf=hdf, group_name=group_name) self._structure_to_hdf() with self.project_hdf5.open("input") as h5in: self.input.to_hdf(h5in) def from_hdf(self, hdf=None, group_name=None): super().from_hdf(hdf=hdf, group_name=group_name) self._structure_from_hdf() self._backwards_compatible_input_from_hdf() with self.project_hdf5.open("output/structures") as hdf5_output: structure_names = hdf5_output.list_groups() for group in structure_names: with self.project_hdf5.open("output/structures/" + group) as hdf5_output: self._lst_of_struct.append(Atoms().from_hdf(hdf5_output)) def _backwards_compatible_input_from_hdf(self): if "HDF_VERSION" in self.project_hdf5.list_nodes(): version = self.project_hdf5["HDF_VERSION"] else: version = "0.1.0" if version == "0.1.0": with self.project_hdf5.open("input") as hdf5_input: gp = GenericParameters(table_name="custom_dict") gp.from_hdf(hdf5_input) for k in gp.keys(): self.input[k] = gp[k] elif version == "0.2.0": with self.project_hdf5.open("input") as hdf5_input: self.input.from_hdf(hdf5_input) else: raise ValueError("Cannot handle hdf version: {}".format(version))
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
def __init__(self, *args, **kwargs): self._storage = DataContainer(table_name='storage')
class ImageJob(GenericJob): """ A job type for storing and processing image data. TODO: Consider allowing the `data` field of each image to be saved to hdf5... Attributes: images (DistributingList): A list of `Image` objects. """ def __init__(self, project, job_name): super(ImageJob, self).__init__(project, job_name) self.__name__ = "ImageJob" self._images = DistributingList() self.input = DataContainer(table_name="input") self.output = DataContainer(table_name="output") @property def images(self): return self._images @images.setter def images(self, val): if isinstance(val, DistributingList): self._images = val elif isinstance(val, (tuple, list, np.ndarray)): if not all([isinstance(obj, Image) for obj in val]): raise ValueError( "Only `Image`-type objects can be set to the `images` attribute." ) self._images = DistributingList(val) else: raise ValueError( "Images was expecting a list-like object, but got {}".format( type(val))) @staticmethod def _get_factors(n): i = int(n**0.5 + 0.5) while n % i != 0: i -= 1 return i, int(n / i) def plot(self, mask=None, subplots_kwargs=None, imshow_kwargs=None, hide_axes=True): """ Make a simple matplotlib `imshow` plot for each of the images on a grid. Args: mask (list/numpy.ndarray): An integer index mask for selecting a subset of the images to plot. subplots_kwargs (dict): Keyword arguments to pass to the figure generation. (Default is None.) imshow_kwargs (dict): Keyword arguments to pass to the `imshow` plotting command. (Default is None.) hide_axes (bool): Whether to hide axis ticks and labels. (Default is True.) Returns: (matplotlib.figure.Figure): The figure the plots are in. (list): The axes the plot is on. """ if mask is not None: images = self.images[mask] else: images = self.images subplots_kwargs = subplots_kwargs or {} imshow_kwargs = imshow_kwargs or {} nrows, ncols = self._get_factors(len(images)) fig, axes = plt.subplots(nrows=nrows, ncols=ncols, **subplots_kwargs) axes = np.atleast_2d(axes) for n, img in enumerate(images): i = int(np.floor(n / ncols)) j = n % ncols ax = axes[i, j] img.plot(ax=ax, imshow_kwargs=imshow_kwargs, hide_axes=hide_axes) fig.tight_layout() return fig, axes def add_image(self, source, metadata=None, as_gray=False, relative_path=True): """ Add an image to the job. Args: source (str/numpy.ndarray): The filepath to the data, or the raw array of data itself. metadata (Metadata): The metadata associated with the source. (Default is None.) as_gray (bool): Whether to interpret the new data as grayscale. (Default is False.) relative_path (bool): Whether the path provided is relative. (Default is True, automatically converts to an absolute path before setting the `source` value of the image.) """ if not isfile(source) and not isinstance(source, np.ndarray): raise ValueError( "Could not find a file at {}, nor is source an array.".format( source)) if isinstance(source, str) and relative_path: source = abspath(source) self.images.append( Image(source=source, metadata=metadata, as_gray=as_gray)) def add_images(self, sources, metadata=None, as_gray=False): """ Add multiple images to the job. Args: sources (str/list/tuple/numpy.ndarray): When a string, uses the `glob` module to look for matching files. When list-like, iteratively uses each element as a new source. metadata (Metadata): The metadata associated with all these sources. (Default is None.) as_gray (bool): Whether to interpret all this data as grayscale. (Default is False.) relative_path (bool): Whether the path provided is relative. (Default is True, automatically converts to an absolute path before setting the `source` value of the image.) """ if isinstance(sources, str): for match in iglob(sources): self.add_image(match, metadata=metadata, as_gray=as_gray) elif isinstance(sources, (list, tuple, np.ndarray)): for source in sources: self.add_image(source, metadata=metadata, as_gray=as_gray) def run(self, run_again=False, repair=False, debug=False, run_mode=None): super(ImageJob, self).run(run_again=run_again, repair=repair, debug=debug, run_mode=run_mode) def run_static(self): """This is just a toy example right now.""" self.status.running = True if hasattr(self.input, 'filter') and self.input.filter == 'brightness_filter': fractions = [] cutoffs = [] masks = [] for img in self.images: frac, cut, mask = brightness_filter(img) fractions.append(frac) cutoffs.append(cut) masks.append(mask) self.output.fractions = np.array(fractions) self.output.cutoffs = np.array(cutoffs) self.output.masks = np.array(masks) else: self.logger.warning("Didn't run anything. Check input.") self.status.collect = True self.run() def write_input(self): """Must define abstract method""" pass def collect_output(self): """Must define abstract method""" self.to_hdf() def to_hdf(self, hdf=None, group_name=None): """ Store the job in an HDF5 file. Args: hdf (ProjectHDFio): HDF5 group object - optional group_name (str): HDF5 subgroup name - optional """ super(ImageJob, self).to_hdf(hdf=hdf, group_name=group_name) self.input.to_hdf(hdf=self._hdf5, group_name=None) self.output.to_hdf(hdf=self._hdf5, group_name=None) with self._hdf5.open("images") as hdf5_server: for n, image in enumerate(self.images): image.to_hdf(hdf=hdf5_server, group_name="img{}".format(n)) self._hdf5["n_images"] = n + 1 def from_hdf(self, hdf=None, group_name=None): """ Load the Protocol from an HDF5 file. Args: hdf (ProjectHDFio): HDF5 group object - optional group_name (str): HDF5 subgroup name - optional """ super(ImageJob, self).from_hdf(hdf=hdf, group_name=group_name) self.input.from_hdf(hdf=self._hdf5, group_name=None) self.output.from_hdf(hdf=self._hdf5, group_name=None) with self._hdf5.open("images") as hdf5_server: for n in np.arange(self._hdf5["n_images"], dtype=int): img = Image(source=None) img.from_hdf(hdf=hdf5_server, group_name="img{}".format(n)) self.images.append(img)
def __init__(self, identifier=None, species=None, table_name=None): super().__init__() self.identifier = identifier self.functions = DataContainer(table_name=table_name) self.species = species