From 5c0999b08f88427f1a2f9630f6a101bc8754129c Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 8 Mar 2023 18:41:56 +0000 Subject: [PATCH 01/74] not store useless extra species info --- .../atomistics/structure/atoms.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyiron_atomistics/atomistics/structure/atoms.py b/pyiron_atomistics/atomistics/structure/atoms.py index 0425c348f..25795ed84 100644 --- a/pyiron_atomistics/atomistics/structure/atoms.py +++ b/pyiron_atomistics/atomistics/structure/atoms.py @@ -110,8 +110,6 @@ def __init__( ): state.logger.debug("Not supported parameter used!") - self._store_elements = dict() - self._species_to_index_dict = None self._is_scaled = False self._species = list() @@ -290,12 +288,16 @@ def set_species(self, value): value (list): A list atomistics.structure.periodic_table.ChemicalElement instances """ - if value is None: - return - value = list(value) - self._species_to_index_dict = {el: i for i, el in enumerate(value)} - self._species = value[:] - self._store_elements = {el.Abbreviation: el for el in value} + if value is not None: + self._species = list(value)[:] + + @property + def _store_elements(self) -> dict: + return {el.Abbreviation: el for el in self.species} + + @property + def _species_to_index_dict(self) -> dict: + return {el: i for i, el in enumerate(self.species)} @property def symbols(self): @@ -743,7 +745,6 @@ def convert_element(self, el, pse=None): else: raise ValueError("Unknown static type to specify a element") - self._store_elements[el] = element if hasattr(self, "species"): if element not in self.species: self._species.append(element) @@ -2078,7 +2079,6 @@ def extend(self, other): ind_conv[ind_old] = ind_new else: new_species_lst.append(el) - sum_atoms._store_elements[el.Abbreviation] = el ind_conv[ind_old] = len(new_species_lst) - 1 for key, val in ind_conv.items(): From b9f1c2c2a2f766d345a9fb554dfa7509e9c603d2 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Sun, 16 Apr 2023 18:05:02 +0000 Subject: [PATCH 02/74] create potentials.py --- pyiron_atomistics/lammps/potentials.py | 242 +++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 pyiron_atomistics/lammps/potentials.py diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py new file mode 100644 index 000000000..41f05b3ed --- /dev/null +++ b/pyiron_atomistics/lammps/potentials.py @@ -0,0 +1,242 @@ +# coding: utf-8 +# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department +# Distributed under the terms of "New BSD License", see the LICENSE file. + +__author__ = "Joerg Neugebauer, Sudarsan Surendralal, Jan Janssen" +__copyright__ = ( + "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - " + "Computational Materials Design (CM) Department" +) +__version__ = "1.0" +__maintainer__ = "Sudarsan Surendralal" +__email__ = "surendralal@mpie.de" +__status__ = "production" +__date__ = "Sep 1, 2017" + +import pandas as pd +from pyiron_atomistics.lammps.potential import LammpsPotentialFile +import numpy as np + + +class LammpsPotential: + def copy(self): + new_pot = LammpsPotential() + new_pot.set_df(self.get_df()) + return new_pot + + @staticmethod + def _harmonize_args(args): + if len(args) == 0: + raise ValueError("Chemical elements not specified") + if len(args) == 1: + args *= 2 + return list(args) + + @property + def model(self): + return "_and_".join(set(self._df.model)) + + @property + def name(self): + return "_and_".join(set(self._df.name)) + + @property + def species(self): + species = set([ss for s in self._df.interacting_species for ss in s]) + preset = set(["___".join(s) for s in self._df.preset_species if len(s) > 0]) + if len(preset) == 0: + return list(species) + elif len(preset) > 1: + raise NotImplementedError("Currently not possible to have multiple file-based potentials") + preset = list(preset)[0].split('___') + return preset + list(species - set(preset)) + + @property + def filename(self): + return [f for f in set(self._df.filename) if len(f) > 0] + + @property + def citations(self): + return "".join(np.unique([c for c in self._df.citations if len(c) > 0])) + + @property + def is_scaled(self): + return "scale" in self._df + + @property + def pair_style(self): + if len(set(self._df.pair_style)) == 1: + return "pair_style " + list(set(self._df.pair_style))[0] + elif "scale" not in self._df: + return "pair_style hybrid" + elif all(self._df.scale == 1): + return "pair_style hybrid/overlay " + " ".join(list(self._df.pair_style)) + return "pair_style hybrid/scaled " + " ".join([str(ss) for s in self._df[["scale", "pair_style"]].values for ss in s]) + + @property + def pair_coeff(self): + def convert(c, species=self.species): + s_dict = dict( + zip(species, (np.arange(len(species)) + 1).astype(str)) + ) + s_dict.update({"*": "*"}) + return [s_dict[cc] for cc in c] + if "hybrid" in self.pair_style: + return [ + " ".join( + ["pair_coeff"] + convert(c[0]) + [c[1]] + [c[2]] + ["\n"] + ) + for c in self._df[["interacting_species", "pair_style", "pair_coeff"]].values + ] + return [ + " ".join( + ["pair_coeff"] + convert(c[0]) + [c[1]] + ["\n"] + ) + for c in self._df[["interacting_species", "pair_coeff"]].values + ] + + def __repr__(self): + return self._df.__repr__() + + def _repr_html_(self): + return self._df._repr_html_() + + def set_df(self, df): + for key in ["pair_style", "interacting_species", "pair_coeff", "preset_species"]: + if key not in df: + raise ValueError(f"{key} missing") + self._df = df + + def get_df(self, default_scale=None): + if default_scale is None or "scale" in self._df: + return self._df.copy() + df = self._df.copy() + df["scale"] = 1 + return df + + def __mul__(self, scale_or_potential): + if isinstance(scale_or_potential, LammpsPotential): + if self.is_scaled or scale_or_potential.is_scaled: + raise ValueError("You cannot mix hybrid types") + new_pot = LammpsPotential() + new_pot.set_df(pd.concat((self.get_df(), scale_or_potential.get_df()), ignore_index=True)) + return new_pot + if self.is_scaled: + raise NotImplementedError("Currently you cannot scale twice") + new_pot = self.copy() + new_pot._df['scale'] = scale_or_potential + return new_pot + + __rmul__ = __mul__ + + def __add__(self, potential): + new_pot = LammpsPotential() + new_pot.set_df(pd.concat((self.get_df(default_scale=1), potential.get_df(default_scale=1)), ignore_index=True)) + return new_pot + + def _initialize_df( + self, + pair_style, + interacting_species, + pair_coeff, + preset_species=None, + model=None, + citations=None, + filename=None, + name=None, + scale=None + ): + def check_none_n_length(variable, default, length=len(pair_coeff)): + if variable is None: + variable = default + if len(variable) == 1 and len(variable) < length: + variable = length * variable + return variable + arg_dict = { + "pair_style": pair_style, + "interacting_species": interacting_species, + "pair_coeff": pair_coeff, + "preset_species": check_none_n_length(preset_species, [[]]), + "model": check_none_n_length(model, pair_style), + "citations": check_none_n_length(citations, [[]]), + "filename": check_none_n_length(filename, [""]), + "name": check_none_n_length(name, pair_style) + } + if scale is not None: + arg_dict["scale"] = scale + self.set_df(pd.DataFrame(arg_dict)) + + +class EAM(LammpsPotential): + @staticmethod + def _get_pair_style(config): + if any(["hybrid" in c for c in config]): + return [c.split()[3] for c in config if "pair_coeff" in c] + for c in config: + if "pair_style" in c: + return [" ".join(c.replace('\n', '').split()[1:])] * sum(["pair_coeff" in c for c in config]) + raise ValueError(f"pair_style could not determined: {config}") + + @staticmethod + def _get_pair_coeff(config): + try: + if any(["hybrid" in c for c in config]): + return [" ".join(c.split()[4:]) for c in config if "pair_coeff" in c] + return [" ".join(c.split()[3:]) for c in config if "pair_coeff" in c] + except IndexError: + raise AssertionError(f"{config} does not follow the format 'pair_coeff element_1 element_2 args'") + + @staticmethod + def _get_interacting_species(config, species): + def _convert(c, s): + if c == "*": + return c + return s[int(c) - 1] + return [[_convert(cc, species) for cc in c.split()[1:3]] for c in config if c.startswith('pair_coeff')] + + + @staticmethod + def _get_scale(config): + for c in config: + if not c.startswith("pair_style"): + continue + if "hybrid/overlay" in c: + return 1 + elif "hybrid/scaled" in c: + raise NotImplementedError("Too much work for something inexistent in pyiron database for now") + return + + def __init__(self, *chemical_elements, name=None, pair_style=None): + if name is None: + df = LammpsPotentialFile().find(list(chemical_elements)).iloc[0] + else: + df = LammpsPotentialFile().find_by_name(name).iloc[0] + pair_style = df.Config[0].split()[1].replace('\n', '') + self._initialize_df( + pair_style=self._get_pair_style(df.Config), + interacting_species=self._get_interacting_species(df.Config, df.Species), + pair_coeff=self._get_pair_coeff(df.Config), + preset_species=[df.Species], + model=df.Model, + citations=df.Citations, + filename=df.Filename, + name=df.Name, + scale=self._get_scale(df.Config) + ) + + +class Morse(LammpsPotential): + def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="morse"): + self._initialize_df( + pair_style=[pair_style], + interacting_species=[self._harmonize_args(chemical_elements)], + pair_coeff=[" ".join([str(cc) for cc in [D_0, alpha, r_0, cutoff]])], + ) + +class CustomPotential(LammpsPotential): + def __init__(self, pair_style, *chemical_elements, **kwargs): + self._initialize_df( + pair_style=[pair_style], + interacting_species=[self._harmonize_args(chemical_elements)], + pair_coeff=[" ".join([str(cc) for cc in kwargs.values()])], + ) From 24aaedbb1bdf60868fec114970acdb07d9cac587 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Sun, 16 Apr 2023 18:25:23 +0000 Subject: [PATCH 03/74] make it possible to choose potentials --- pyiron_atomistics/lammps/potentials.py | 95 ++++++++++++++++---------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 41f05b3ed..3cc6e8ac5 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -16,9 +16,15 @@ import pandas as pd from pyiron_atomistics.lammps.potential import LammpsPotentialFile import numpy as np +import warnings class LammpsPotential: + def __new__(cls, *args, **kwargs): + obj = super().__new__(cls) + cls._df = None + return obj + def copy(self): new_pot = LammpsPotential() new_pot.set_df(self.get_df()) @@ -34,16 +40,16 @@ def _harmonize_args(args): @property def model(self): - return "_and_".join(set(self._df.model)) + return "_and_".join(set(self.df.model)) @property def name(self): - return "_and_".join(set(self._df.name)) + return "_and_".join(set(self.df.name)) @property def species(self): - species = set([ss for s in self._df.interacting_species for ss in s]) - preset = set(["___".join(s) for s in self._df.preset_species if len(s) > 0]) + species = set([ss for s in self.df.interacting_species for ss in s]) + preset = set(["___".join(s) for s in self.df.preset_species if len(s) > 0]) if len(preset) == 0: return list(species) elif len(preset) > 1: @@ -53,25 +59,25 @@ def species(self): @property def filename(self): - return [f for f in set(self._df.filename) if len(f) > 0] + return [f for f in set(self.df.filename) if len(f) > 0] @property def citations(self): - return "".join(np.unique([c for c in self._df.citations if len(c) > 0])) + return "".join(np.unique([c for c in self.df.citations if len(c) > 0])) @property def is_scaled(self): - return "scale" in self._df + return "scale" in self.df @property def pair_style(self): - if len(set(self._df.pair_style)) == 1: - return "pair_style " + list(set(self._df.pair_style))[0] - elif "scale" not in self._df: + if len(set(self.df.pair_style)) == 1: + return "pair_style " + list(set(self.df.pair_style))[0] + elif "scale" not in self.df: return "pair_style hybrid" - elif all(self._df.scale == 1): - return "pair_style hybrid/overlay " + " ".join(list(self._df.pair_style)) - return "pair_style hybrid/scaled " + " ".join([str(ss) for s in self._df[["scale", "pair_style"]].values for ss in s]) + elif all(self.df.scale == 1): + return "pair_style hybrid/overlay " + " ".join(list(self.df.pair_style)) + return "pair_style hybrid/scaled " + " ".join([str(ss) for s in self.df[["scale", "pair_style"]].values for ss in s]) @property def pair_coeff(self): @@ -86,20 +92,20 @@ def convert(c, species=self.species): " ".join( ["pair_coeff"] + convert(c[0]) + [c[1]] + [c[2]] + ["\n"] ) - for c in self._df[["interacting_species", "pair_style", "pair_coeff"]].values + for c in self.df[["interacting_species", "pair_style", "pair_coeff"]].values ] return [ " ".join( ["pair_coeff"] + convert(c[0]) + [c[1]] + ["\n"] ) - for c in self._df[["interacting_species", "pair_coeff"]].values + for c in self.df[["interacting_species", "pair_coeff"]].values ] def __repr__(self): - return self._df.__repr__() + return self.df.__repr__() def _repr_html_(self): - return self._df._repr_html_() + return self.df._repr_html_() def set_df(self, df): for key in ["pair_style", "interacting_species", "pair_coeff", "preset_species"]: @@ -107,10 +113,14 @@ def set_df(self, df): raise ValueError(f"{key} missing") self._df = df + @property + def df(self): + return self._df + def get_df(self, default_scale=None): - if default_scale is None or "scale" in self._df: - return self._df.copy() - df = self._df.copy() + if default_scale is None or "scale" in self.df: + return self.df.copy() + df = self.df.copy() df["scale"] = 1 return df @@ -124,7 +134,7 @@ def __mul__(self, scale_or_potential): if self.is_scaled: raise NotImplementedError("Currently you cannot scale twice") new_pot = self.copy() - new_pot._df['scale'] = scale_or_potential + new_pot.df['scale'] = scale_or_potential return new_pot __rmul__ = __mul__ @@ -207,22 +217,35 @@ def _get_scale(config): return def __init__(self, *chemical_elements, name=None, pair_style=None): - if name is None: - df = LammpsPotentialFile().find(list(chemical_elements)).iloc[0] + if name is not None: + self._df_candidates = LammpsPotentialFile().find_by_name(name) else: - df = LammpsPotentialFile().find_by_name(name).iloc[0] - pair_style = df.Config[0].split()[1].replace('\n', '') - self._initialize_df( - pair_style=self._get_pair_style(df.Config), - interacting_species=self._get_interacting_species(df.Config, df.Species), - pair_coeff=self._get_pair_coeff(df.Config), - preset_species=[df.Species], - model=df.Model, - citations=df.Citations, - filename=df.Filename, - name=df.Name, - scale=self._get_scale(df.Config) - ) + self._df_candidates = LammpsPotentialFile().find(list(chemical_elements)) + + def list_potentials(self): + return self._df_candidates.Name + + def view_potentials(self): + return self._df_candidates + + @property + def df(self): + if self._df is None: + df = self._df_candidates.iloc[0] + if len(self._df_candidates) > 1: + warnings.warn(f"Potential not specified - chose {df.Name}") + self._initialize_df( + pair_style=self._get_pair_style(df.Config), + interacting_species=self._get_interacting_species(df.Config, df.Species), + pair_coeff=self._get_pair_coeff(df.Config), + preset_species=[df.Species], + model=df.Model, + citations=df.Citations, + filename=df.Filename, + name=df.Name, + scale=self._get_scale(df.Config) + ) + return self._df class Morse(LammpsPotential): From 13600e02017e730ad0fac021ef374997d652cbd9 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 17 Apr 2023 09:30:18 +0000 Subject: [PATCH 04/74] make multiple potential enumeration and NULL-filling available --- pyiron_atomistics/lammps/potentials.py | 132 ++++++++++++++++++++----- 1 file changed, 107 insertions(+), 25 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 3cc6e8ac5..017d0900e 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -55,7 +55,7 @@ def species(self): elif len(preset) > 1: raise NotImplementedError("Currently not possible to have multiple file-based potentials") preset = list(preset)[0].split('___') - return preset + list(species - set(preset)) + return [p for p in preset + list(species - set(preset)) if p != "*"] @property def filename(self): @@ -72,34 +72,110 @@ def is_scaled(self): @property def pair_style(self): if len(set(self.df.pair_style)) == 1: - return "pair_style " + list(set(self.df.pair_style))[0] + pair_style = "pair_style " + list(set(self.df.pair_style))[0] + if np.max(self.df.cutoff) > 0: + pair_style += f" {np.max(self.df.cutoff)}" + return pair_style + "\n" elif "scale" not in self.df: - return "pair_style hybrid" + pair_style = "pair_style hybrid" elif all(self.df.scale == 1): - return "pair_style hybrid/overlay " + " ".join(list(self.df.pair_style)) - return "pair_style hybrid/scaled " + " ".join([str(ss) for s in self.df[["scale", "pair_style"]].values for ss in s]) + pair_style = "pair_style hybrid/overlay" + else: + pair_style = "pair_style hybrid/scaled" + for ii, s in enumerate(self.df[["pair_style", "cutoff"]].values): + if pair_style.startswith("pair_style hybrid/scaled"): + pair_style += f" {self.df.iloc[ii].scale}" + pair_style += f" {s[0]}" + if s[1] > 0: + pair_style += f" {s[1]}" + return pair_style + "\n" @property def pair_coeff(self): - def convert(c, species=self.species): - s_dict = dict( - zip(species, (np.arange(len(species)) + 1).astype(str)) - ) - s_dict.update({"*": "*"}) - return [s_dict[cc] for cc in c] - if "hybrid" in self.pair_style: - return [ - " ".join( - ["pair_coeff"] + convert(c[0]) + [c[1]] + [c[2]] + ["\n"] + class PairCoeff: + def __init__( + self, + is_hybrid, + pair_style, + interacting_species, + pair_coeff, + species, + preset_species + ): + self.is_hybrid = is_hybrid + self._interacting_species = interacting_species + self._pair_coeff = pair_coeff + self._species = species + self._preset_species = preset_species + self._pair_style = pair_style + + @property + def counter(self): + key, count = np.unique(self._pair_style, return_counts=True) + counter = {kk: 1 for kk in key[count > 1]} + results = [] + for coeff in self._pair_style: + if coeff in counter and self.is_hybrid: + results.append(str(counter[coeff])) + counter[coeff] += 1 + else: + results.append("") + return results + + @property + def pair_style(self): + if self.is_hybrid: + return self._pair_style + else: + return len(self._pair_style) * [""] + + @property + def results(self): + return [ + " ".join((" ".join(("pair_coeff", ) + c)).split()) + "\n" + for c in zip(self.interacting_species, self.pair_style, self.counter, self.pair_coeff) + ] + + @property + def interacting_species(self): + s_dict = dict( + zip(self._species, (np.arange(len(self._species)) + 1).astype(str)) ) - for c in self.df[["interacting_species", "pair_style", "pair_coeff"]].values - ] - return [ - " ".join( - ["pair_coeff"] + convert(c[0]) + [c[1]] + ["\n"] - ) - for c in self.df[["interacting_species", "pair_coeff"]].values - ] + s_dict.update({"*": "*"}) + return [" ".join([s_dict[cc] for cc in c]) for c in self._interacting_species] + + @property + def pair_coeff(self): + if not self.is_hybrid: + return self._pair_coeff + results = [] + for pc, ps in zip(self._pair_coeff, self._preset_species): + if len(ps) > 0 and "eam" in pc: + s = " ".join(ps + (len(self._species) - len(ps)) * ["NULL"]) + pc = pc.replace(" ".join(ps), s) + results.append(pc) + return results + + + return PairCoeff( + is_hybrid="hybrid" in self.pair_style, + pair_style=self.df.pair_style, + interacting_species=self.df.interacting_species, + pair_coeff=self.df.pair_coeff, + species=self.species, + preset_species=self.df.preset_species + ).results + + @property + def pyiron_df(self): + return pd.DataFrame({ + "Config": [[self.pair_style] + self.pair_coeff], + "Filename": [self.filename], + "Model": [self.model], + "Name": [self.name], + "Species": [self.species], + "Citations": [self.citations], + }) def __repr__(self): return self.df.__repr__() @@ -154,11 +230,14 @@ def _initialize_df( citations=None, filename=None, name=None, - scale=None + scale=None, + cutoff=None ): def check_none_n_length(variable, default, length=len(pair_coeff)): if variable is None: variable = default + if not isinstance(variable, list): + return variable if len(variable) == 1 and len(variable) < length: variable = length * variable return variable @@ -167,6 +246,7 @@ def check_none_n_length(variable, default, length=len(pair_coeff)): "interacting_species": interacting_species, "pair_coeff": pair_coeff, "preset_species": check_none_n_length(preset_species, [[]]), + "cutoff": check_none_n_length(cutoff, 0), "model": check_none_n_length(model, pair_style), "citations": check_none_n_length(citations, [[]]), "filename": check_none_n_length(filename, [""]), @@ -254,12 +334,14 @@ def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="mors pair_style=[pair_style], interacting_species=[self._harmonize_args(chemical_elements)], pair_coeff=[" ".join([str(cc) for cc in [D_0, alpha, r_0, cutoff]])], + cutoff=cutoff, ) class CustomPotential(LammpsPotential): - def __init__(self, pair_style, *chemical_elements, **kwargs): + def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): self._initialize_df( pair_style=[pair_style], interacting_species=[self._harmonize_args(chemical_elements)], pair_coeff=[" ".join([str(cc) for cc in kwargs.values()])], + cutoff=cutoff, ) From 88bb9e4a949a515b082a9152082e2fa5f5bae0b4 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 17 Apr 2023 09:37:01 +0000 Subject: [PATCH 05/74] make it compatible with job.potential --- pyiron_atomistics/lammps/base.py | 3 +++ pyiron_atomistics/lammps/potentials.py | 24 ++++++++++++++---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/pyiron_atomistics/lammps/base.py b/pyiron_atomistics/lammps/base.py index 167198f38..b56fe11aa 100644 --- a/pyiron_atomistics/lammps/base.py +++ b/pyiron_atomistics/lammps/base.py @@ -17,6 +17,7 @@ view_potentials, list_potentials, ) +from pyiron_atomistics.lammps.potentials import LammpsPotentials from pyiron_atomistics.atomistics.job.atomistic import AtomisticGenericJob from pyiron_atomistics.lammps.control import LammpsControl from pyiron_atomistics.lammps.potential import LammpsPotential @@ -206,6 +207,8 @@ def potential(self, potential_filename): potential = potential_db.find_by_name(potential_filename) elif isinstance(potential_filename, pd.DataFrame): potential = potential_filename + elif isinstance(potential_filename, LammpsPotentials): + potential = potential_filename.pyiron_df else: raise TypeError("Potentials have to be strings or pandas dataframes.") if self.structure: diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 017d0900e..548b8a73d 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -19,14 +19,14 @@ import warnings -class LammpsPotential: +class LammpsPotentials: def __new__(cls, *args, **kwargs): obj = super().__new__(cls) cls._df = None return obj def copy(self): - new_pot = LammpsPotential() + new_pot = LammpsPotentials() new_pot.set_df(self.get_df()) return new_pot @@ -201,10 +201,10 @@ def get_df(self, default_scale=None): return df def __mul__(self, scale_or_potential): - if isinstance(scale_or_potential, LammpsPotential): + if isinstance(scale_or_potential, LammpsPotentials): if self.is_scaled or scale_or_potential.is_scaled: raise ValueError("You cannot mix hybrid types") - new_pot = LammpsPotential() + new_pot = LammpsPotentials() new_pot.set_df(pd.concat((self.get_df(), scale_or_potential.get_df()), ignore_index=True)) return new_pot if self.is_scaled: @@ -216,7 +216,7 @@ def __mul__(self, scale_or_potential): __rmul__ = __mul__ def __add__(self, potential): - new_pot = LammpsPotential() + new_pot = LammpsPotentials() new_pot.set_df(pd.concat((self.get_df(default_scale=1), potential.get_df(default_scale=1)), ignore_index=True)) return new_pot @@ -257,7 +257,7 @@ def check_none_n_length(variable, default, length=len(pair_coeff)): self.set_df(pd.DataFrame(arg_dict)) -class EAM(LammpsPotential): +class EAM(LammpsPotentials): @staticmethod def _get_pair_style(config): if any(["hybrid" in c for c in config]): @@ -293,7 +293,9 @@ def _get_scale(config): if "hybrid/overlay" in c: return 1 elif "hybrid/scaled" in c: - raise NotImplementedError("Too much work for something inexistent in pyiron database for now") + raise NotImplementedError( + "Too much work for something inexistent in pyiron database for now" + ) return def __init__(self, *chemical_elements, name=None, pair_style=None): @@ -313,7 +315,9 @@ def df(self): if self._df is None: df = self._df_candidates.iloc[0] if len(self._df_candidates) > 1: - warnings.warn(f"Potential not specified - chose {df.Name}") + warnings.warn( + f"Potential not uniquely specified - use default {df.Name}" + ) self._initialize_df( pair_style=self._get_pair_style(df.Config), interacting_species=self._get_interacting_species(df.Config, df.Species), @@ -328,7 +332,7 @@ def df(self): return self._df -class Morse(LammpsPotential): +class Morse(LammpsPotentials): def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="morse"): self._initialize_df( pair_style=[pair_style], @@ -337,7 +341,7 @@ def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="mors cutoff=cutoff, ) -class CustomPotential(LammpsPotential): +class CustomPotential(LammpsPotentials): def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): self._initialize_df( pair_style=[pair_style], From 3569669463e508f9a6ed4e694fc11eea5bb408ce Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 17 Apr 2023 09:38:45 +0000 Subject: [PATCH 06/74] add cutoff in CustomPotential --- pyiron_atomistics/lammps/potentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 548b8a73d..1a776f952 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -346,6 +346,6 @@ def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): self._initialize_df( pair_style=[pair_style], interacting_species=[self._harmonize_args(chemical_elements)], - pair_coeff=[" ".join([str(cc) for cc in kwargs.values()])], + pair_coeff=[" ".join([str(cc) for cc in kwargs.values()])] + f" {cutoff}", cutoff=cutoff, ) From c67533a46efe016e7cbe5738b20a8d05ef9b398c Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 17 Apr 2023 09:49:32 +0000 Subject: [PATCH 07/74] make pub_lst available only if length > 0 --- pyiron_atomistics/lammps/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/base.py b/pyiron_atomistics/lammps/base.py index b56fe11aa..8e82d41d9 100644 --- a/pyiron_atomistics/lammps/base.py +++ b/pyiron_atomistics/lammps/base.py @@ -223,7 +223,7 @@ def potential(self, potential_filename): if "Citations" in potential.columns.values: pot_pub_dict = {} pub_lst = potential["Citations"].values[0] - if isinstance(pub_lst, str): + if isinstance(pub_lst, str) and len(pub_lst) > 0: for p in ast.literal_eval(pub_lst): for k in p.keys(): pot_pub_dict[k] = p[k] From 8a589aeb14cf25079bba729ca73a77cac97bfd1c Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 17 Apr 2023 10:02:12 +0000 Subject: [PATCH 08/74] use list in custom potential --- pyiron_atomistics/lammps/potentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 1a776f952..345a4ccd8 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -346,6 +346,6 @@ def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): self._initialize_df( pair_style=[pair_style], interacting_species=[self._harmonize_args(chemical_elements)], - pair_coeff=[" ".join([str(cc) for cc in kwargs.values()])] + f" {cutoff}", + pair_coeff=[" ".join([str(cc) for cc in kwargs.values()])] + [f" {cutoff}"], cutoff=cutoff, ) From 529d1f11661db60b53883ce44a4aa6a8e87bbd14 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 17 Apr 2023 10:03:56 +0000 Subject: [PATCH 09/74] and of course I didn't add tags correctly --- pyiron_atomistics/lammps/potentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 345a4ccd8..f79c79e84 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -346,6 +346,6 @@ def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): self._initialize_df( pair_style=[pair_style], interacting_species=[self._harmonize_args(chemical_elements)], - pair_coeff=[" ".join([str(cc) for cc in kwargs.values()])] + [f" {cutoff}"], + pair_coeff=[" ".join([str(cc) for cc in kwargs.values()]) + f" {cutoff}"], cutoff=cutoff, ) From 54a6b763d2c74ac21365f039d15a4caaac03346f Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 17 Apr 2023 10:18:50 +0000 Subject: [PATCH 10/74] Format black --- pyiron_atomistics/lammps/potentials.py | 91 ++++++++++++++++++-------- 1 file changed, 64 insertions(+), 27 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index f79c79e84..372e51d36 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -53,8 +53,10 @@ def species(self): if len(preset) == 0: return list(species) elif len(preset) > 1: - raise NotImplementedError("Currently not possible to have multiple file-based potentials") - preset = list(preset)[0].split('___') + raise NotImplementedError( + "Currently not possible to have multiple file-based potentials" + ) + preset = list(preset)[0].split("___") return [p for p in preset + list(species - set(preset)) if p != "*"] @property @@ -100,7 +102,7 @@ def __init__( interacting_species, pair_coeff, species, - preset_species + preset_species, ): self.is_hybrid = is_hybrid self._interacting_species = interacting_species @@ -132,8 +134,13 @@ def pair_style(self): @property def results(self): return [ - " ".join((" ".join(("pair_coeff", ) + c)).split()) + "\n" - for c in zip(self.interacting_species, self.pair_style, self.counter, self.pair_coeff) + " ".join((" ".join(("pair_coeff",) + c)).split()) + "\n" + for c in zip( + self.interacting_species, + self.pair_style, + self.counter, + self.pair_coeff, + ) ] @property @@ -142,7 +149,10 @@ def interacting_species(self): zip(self._species, (np.arange(len(self._species)) + 1).astype(str)) ) s_dict.update({"*": "*"}) - return [" ".join([s_dict[cc] for cc in c]) for c in self._interacting_species] + return [ + " ".join([s_dict[cc] for cc in c]) + for c in self._interacting_species + ] @property def pair_coeff(self): @@ -156,26 +166,27 @@ def pair_coeff(self): results.append(pc) return results - return PairCoeff( is_hybrid="hybrid" in self.pair_style, pair_style=self.df.pair_style, interacting_species=self.df.interacting_species, pair_coeff=self.df.pair_coeff, species=self.species, - preset_species=self.df.preset_species + preset_species=self.df.preset_species, ).results @property def pyiron_df(self): - return pd.DataFrame({ - "Config": [[self.pair_style] + self.pair_coeff], - "Filename": [self.filename], - "Model": [self.model], - "Name": [self.name], - "Species": [self.species], - "Citations": [self.citations], - }) + return pd.DataFrame( + { + "Config": [[self.pair_style] + self.pair_coeff], + "Filename": [self.filename], + "Model": [self.model], + "Name": [self.name], + "Species": [self.species], + "Citations": [self.citations], + } + ) def __repr__(self): return self.df.__repr__() @@ -184,7 +195,12 @@ def _repr_html_(self): return self.df._repr_html_() def set_df(self, df): - for key in ["pair_style", "interacting_species", "pair_coeff", "preset_species"]: + for key in [ + "pair_style", + "interacting_species", + "pair_coeff", + "preset_species", + ]: if key not in df: raise ValueError(f"{key} missing") self._df = df @@ -205,19 +221,28 @@ def __mul__(self, scale_or_potential): if self.is_scaled or scale_or_potential.is_scaled: raise ValueError("You cannot mix hybrid types") new_pot = LammpsPotentials() - new_pot.set_df(pd.concat((self.get_df(), scale_or_potential.get_df()), ignore_index=True)) + new_pot.set_df( + pd.concat( + (self.get_df(), scale_or_potential.get_df()), ignore_index=True + ) + ) return new_pot if self.is_scaled: raise NotImplementedError("Currently you cannot scale twice") new_pot = self.copy() - new_pot.df['scale'] = scale_or_potential + new_pot.df["scale"] = scale_or_potential return new_pot __rmul__ = __mul__ def __add__(self, potential): new_pot = LammpsPotentials() - new_pot.set_df(pd.concat((self.get_df(default_scale=1), potential.get_df(default_scale=1)), ignore_index=True)) + new_pot.set_df( + pd.concat( + (self.get_df(default_scale=1), potential.get_df(default_scale=1)), + ignore_index=True, + ) + ) return new_pot def _initialize_df( @@ -231,7 +256,7 @@ def _initialize_df( filename=None, name=None, scale=None, - cutoff=None + cutoff=None, ): def check_none_n_length(variable, default, length=len(pair_coeff)): if variable is None: @@ -241,6 +266,7 @@ def check_none_n_length(variable, default, length=len(pair_coeff)): if len(variable) == 1 and len(variable) < length: variable = length * variable return variable + arg_dict = { "pair_style": pair_style, "interacting_species": interacting_species, @@ -250,7 +276,7 @@ def check_none_n_length(variable, default, length=len(pair_coeff)): "model": check_none_n_length(model, pair_style), "citations": check_none_n_length(citations, [[]]), "filename": check_none_n_length(filename, [""]), - "name": check_none_n_length(name, pair_style) + "name": check_none_n_length(name, pair_style), } if scale is not None: arg_dict["scale"] = scale @@ -264,7 +290,9 @@ def _get_pair_style(config): return [c.split()[3] for c in config if "pair_coeff" in c] for c in config: if "pair_style" in c: - return [" ".join(c.replace('\n', '').split()[1:])] * sum(["pair_coeff" in c for c in config]) + return [" ".join(c.replace("\n", "").split()[1:])] * sum( + ["pair_coeff" in c for c in config] + ) raise ValueError(f"pair_style could not determined: {config}") @staticmethod @@ -274,7 +302,9 @@ def _get_pair_coeff(config): return [" ".join(c.split()[4:]) for c in config if "pair_coeff" in c] return [" ".join(c.split()[3:]) for c in config if "pair_coeff" in c] except IndexError: - raise AssertionError(f"{config} does not follow the format 'pair_coeff element_1 element_2 args'") + raise AssertionError( + f"{config} does not follow the format 'pair_coeff element_1 element_2 args'" + ) @staticmethod def _get_interacting_species(config, species): @@ -282,8 +312,12 @@ def _convert(c, s): if c == "*": return c return s[int(c) - 1] - return [[_convert(cc, species) for cc in c.split()[1:3]] for c in config if c.startswith('pair_coeff')] + return [ + [_convert(cc, species) for cc in c.split()[1:3]] + for c in config + if c.startswith("pair_coeff") + ] @staticmethod def _get_scale(config): @@ -320,14 +354,16 @@ def df(self): ) self._initialize_df( pair_style=self._get_pair_style(df.Config), - interacting_species=self._get_interacting_species(df.Config, df.Species), + interacting_species=self._get_interacting_species( + df.Config, df.Species + ), pair_coeff=self._get_pair_coeff(df.Config), preset_species=[df.Species], model=df.Model, citations=df.Citations, filename=df.Filename, name=df.Name, - scale=self._get_scale(df.Config) + scale=self._get_scale(df.Config), ) return self._df @@ -341,6 +377,7 @@ def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="mors cutoff=cutoff, ) + class CustomPotential(LammpsPotentials): def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): self._initialize_df( From a0bbcecc5d87539a3efa49b14cc3ab23633aec9d Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 17 Apr 2023 10:36:11 +0000 Subject: [PATCH 11/74] add a few docstrings --- pyiron_atomistics/lammps/potentials.py | 45 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index f79c79e84..b61826a25 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -31,7 +31,7 @@ def copy(self): return new_pot @staticmethod - def _harmonize_args(args): + def _harmonize_args(args) -> str: if len(args) == 0: raise ValueError("Chemical elements not specified") if len(args) == 1: @@ -39,15 +39,18 @@ def _harmonize_args(args): return list(args) @property - def model(self): + def model(self): -> str + """Model name (required in pyiron df)""" return "_and_".join(set(self.df.model)) @property - def name(self): + def name(self) -> str: + """Potential name (required in pyiron df)""" return "_and_".join(set(self.df.name)) @property def species(self): + """Species defined in the potential""" species = set([ss for s in self.df.interacting_species for ss in s]) preset = set(["___".join(s) for s in self.df.preset_species if len(s) > 0]) if len(preset) == 0: @@ -58,19 +61,23 @@ def species(self): return [p for p in preset + list(species - set(preset)) if p != "*"] @property - def filename(self): + def filename(self) -> list: + """LAMMPS potential files""" return [f for f in set(self.df.filename) if len(f) > 0] @property - def citations(self): + def citations(self) -> str: + """Citations to be included""" return "".join(np.unique([c for c in self.df.citations if len(c) > 0])) @property - def is_scaled(self): + def is_scaled(self) -> bool: + """Scaling in pair_style hybrid/scaled and hybrid/overlay (whih is scale=1)""" return "scale" in self.df @property - def pair_style(self): + def pair_style(self) -> str: + """LAMMPS pair_style""" if len(set(self.df.pair_style)) == 1: pair_style = "pair_style " + list(set(self.df.pair_style))[0] if np.max(self.df.cutoff) > 0: @@ -91,7 +98,8 @@ def pair_style(self): return pair_style + "\n" @property - def pair_coeff(self): + def pair_coeff(self) -> list: + """LAMMPS pair_coeff""" class PairCoeff: def __init__( self, @@ -111,6 +119,10 @@ def __init__( @property def counter(self): + """ + Enumeration of potentials if a potential is used multiple + times in hybrid (which is a requirement from LAMMPS) + """ key, count = np.unique(self._pair_style, return_counts=True) counter = {kk: 1 for kk in key[count > 1]} results = [] @@ -124,6 +136,7 @@ def counter(self): @property def pair_style(self): + """pair_style to be output only in hybrid""" if self.is_hybrid: return self._pair_style else: @@ -131,13 +144,18 @@ def pair_style(self): @property def results(self): + """pair_coeff lines to be used in pyiron df""" return [ " ".join((" ".join(("pair_coeff", ) + c)).split()) + "\n" for c in zip(self.interacting_species, self.pair_style, self.counter, self.pair_coeff) ] @property - def interacting_species(self): + def interacting_species(self) -> list: + """ + Species in LAMMPS notation (i.e. in numbers instead of chemical + symbols) + """ s_dict = dict( zip(self._species, (np.arange(len(self._species)) + 1).astype(str)) ) @@ -145,7 +163,12 @@ def interacting_species(self): return [" ".join([s_dict[cc] for cc in c]) for c in self._interacting_species] @property - def pair_coeff(self): + def pair_coeff(self) -> list: + """ + Args for pair_coeff. Elements defined in EAM files are + complemented with the ones defined in other potentials in the + case of hybrid (filled with NULL) + """ if not self.is_hybrid: return self._pair_coeff results = [] @@ -168,6 +191,7 @@ def pair_coeff(self): @property def pyiron_df(self): + """df used in pyiron potential""" return pd.DataFrame({ "Config": [[self.pair_style] + self.pair_coeff], "Filename": [self.filename], @@ -191,6 +215,7 @@ def set_df(self, df): @property def df(self): + """DataFrame containing all info for each pairwise interactions""" return self._df def get_df(self, default_scale=None): From 4a2321badee7bdeae6d8a0a321139c2fab1a5ae8 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 17 Apr 2023 11:14:45 +0000 Subject: [PATCH 12/74] typo --- pyiron_atomistics/lammps/potentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 7fd49c055..896bb1a95 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -39,7 +39,7 @@ def _harmonize_args(args) -> str: return list(args) @property - def model(self): -> str + def model(self) -> str: """Model name (required in pyiron df)""" return "_and_".join(set(self.df.model)) From a88e59d822d94db64f6b4f67be3dc50d96dfe6b2 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 17 Apr 2023 13:00:49 +0000 Subject: [PATCH 13/74] Format black --- pyiron_atomistics/lammps/potentials.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 896bb1a95..b0d9caa87 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -102,6 +102,7 @@ def pair_style(self) -> str: @property def pair_coeff(self) -> list: """LAMMPS pair_coeff""" + class PairCoeff: def __init__( self, From a8c496008eeab737f92ef7cf7adb81295d651d57 Mon Sep 17 00:00:00 2001 From: Sam Dareska <37879103+samwaseda@users.noreply.github.com> Date: Tue, 18 Apr 2023 22:14:30 +0200 Subject: [PATCH 14/74] Update pyiron_atomistics/lammps/potentials.py Co-authored-by: Liam Huber --- pyiron_atomistics/lammps/potentials.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index b0d9caa87..f09fc246a 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -2,16 +2,16 @@ # Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department # Distributed under the terms of "New BSD License", see the LICENSE file. -__author__ = "Joerg Neugebauer, Sudarsan Surendralal, Jan Janssen" +__author__ = "Sam Waseda" __copyright__ = ( - "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - " + "Copyright 2023, Max-Planck-Institut für Eisenforschung GmbH - " "Computational Materials Design (CM) Department" ) __version__ = "1.0" -__maintainer__ = "Sudarsan Surendralal" -__email__ = "surendralal@mpie.de" +__maintainer__ = "Sam Waseda" +__email__ = "waseda@mpie.de" __status__ = "production" -__date__ = "Sep 1, 2017" +__date__ = "April 18, 2023" import pandas as pd from pyiron_atomistics.lammps.potential import LammpsPotentialFile From 05b6eb01ed398c0d2edfe17715f5d73990514181 Mon Sep 17 00:00:00 2001 From: Sam Dareska <37879103+samwaseda@users.noreply.github.com> Date: Tue, 18 Apr 2023 22:14:58 +0200 Subject: [PATCH 15/74] Update pyiron_atomistics/lammps/potentials.py Co-authored-by: Liam Huber --- pyiron_atomistics/lammps/potentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index f09fc246a..4f83858fc 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -239,7 +239,7 @@ def get_df(self, default_scale=None): if default_scale is None or "scale" in self.df: return self.df.copy() df = self.df.copy() - df["scale"] = 1 + df["scale"] = default_scale return df def __mul__(self, scale_or_potential): From 9f5f968a3e05b2cb9133e6b7a818883228106f96 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 18 Apr 2023 20:21:01 +0000 Subject: [PATCH 16/74] make PairCoeff private --- pyiron_atomistics/lammps/potentials.py | 177 +++++++++++++------------ 1 file changed, 89 insertions(+), 88 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index b0d9caa87..34ee9bad2 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -99,98 +99,99 @@ def pair_style(self) -> str: pair_style += f" {s[1]}" return pair_style + "\n" + class _PairCoeff: + def __init__( + self, + is_hybrid, + pair_style, + interacting_species, + pair_coeff, + species, + preset_species, + ): + self.is_hybrid = is_hybrid + self._interacting_species = interacting_species + self._pair_coeff = pair_coeff + self._species = species + self._preset_species = preset_species + self._pair_style = pair_style + + @property + def counter(self): + """ + Enumeration of potentials if a potential is used multiple + times in hybrid (which is a requirement from LAMMPS) + """ + key, count = np.unique(self._pair_style, return_counts=True) + counter = {kk: 1 for kk in key[count > 1]} + results = [] + for coeff in self._pair_style: + if coeff in counter and self.is_hybrid: + results.append(str(counter[coeff])) + counter[coeff] += 1 + else: + results.append("") + return results + + @property + def pair_style(self): + """pair_style to be output only in hybrid""" + if self.is_hybrid: + return self._pair_style + else: + return len(self._pair_style) * [""] + + @property + def results(self): + """pair_coeff lines to be used in pyiron df""" + return [ + " ".join((" ".join(("pair_coeff",) + c)).split()) + "\n" + for c in zip( + self.interacting_species, + self.pair_style, + self.counter, + self.pair_coeff, + ) + ] + + @property + def interacting_species(self) -> list: + """ + Species in LAMMPS notation (i.e. in numbers instead of chemical + symbols) + """ + s_dict = dict( + zip(self._species, (np.arange(len(self._species)) + 1).astype(str)) + ) + s_dict.update({"*": "*"}) + return [ + " ".join([s_dict[cc] for cc in c]) + for c in self._interacting_species + ] + + @property + def pair_coeff(self) -> list: + """ + Args for pair_coeff. Elements defined in EAM files are + complemented with the ones defined in other potentials in the + case of hybrid (filled with NULL) + """ + if not self.is_hybrid: + return self._pair_coeff + results = [] + for pc, ps in zip(self._pair_coeff, self._preset_species): + if len(ps) > 0 and "eam" in pc: + s = " ".join(ps + (len(self._species) - len(ps)) * ["NULL"]) + pc = pc.replace(" ".join(ps), s) + results.append(pc) + return results + @property def pair_coeff(self) -> list: """LAMMPS pair_coeff""" - class PairCoeff: - def __init__( - self, - is_hybrid, - pair_style, - interacting_species, - pair_coeff, - species, - preset_species, - ): - self.is_hybrid = is_hybrid - self._interacting_species = interacting_species - self._pair_coeff = pair_coeff - self._species = species - self._preset_species = preset_species - self._pair_style = pair_style - - @property - def counter(self): - """ - Enumeration of potentials if a potential is used multiple - times in hybrid (which is a requirement from LAMMPS) - """ - key, count = np.unique(self._pair_style, return_counts=True) - counter = {kk: 1 for kk in key[count > 1]} - results = [] - for coeff in self._pair_style: - if coeff in counter and self.is_hybrid: - results.append(str(counter[coeff])) - counter[coeff] += 1 - else: - results.append("") - return results - - @property - def pair_style(self): - """pair_style to be output only in hybrid""" - if self.is_hybrid: - return self._pair_style - else: - return len(self._pair_style) * [""] - - @property - def results(self): - """pair_coeff lines to be used in pyiron df""" - return [ - " ".join((" ".join(("pair_coeff",) + c)).split()) + "\n" - for c in zip( - self.interacting_species, - self.pair_style, - self.counter, - self.pair_coeff, - ) - ] - - @property - def interacting_species(self) -> list: - """ - Species in LAMMPS notation (i.e. in numbers instead of chemical - symbols) - """ - s_dict = dict( - zip(self._species, (np.arange(len(self._species)) + 1).astype(str)) - ) - s_dict.update({"*": "*"}) - return [ - " ".join([s_dict[cc] for cc in c]) - for c in self._interacting_species - ] - - @property - def pair_coeff(self) -> list: - """ - Args for pair_coeff. Elements defined in EAM files are - complemented with the ones defined in other potentials in the - case of hybrid (filled with NULL) - """ - if not self.is_hybrid: - return self._pair_coeff - results = [] - for pc, ps in zip(self._pair_coeff, self._preset_species): - if len(ps) > 0 and "eam" in pc: - s = " ".join(ps + (len(self._species) - len(ps)) * ["NULL"]) - pc = pc.replace(" ".join(ps), s) - results.append(pc) - return results - - return PairCoeff( + + return self._PairCoeff( is_hybrid="hybrid" in self.pair_style, pair_style=self.df.pair_style, interacting_species=self.df.interacting_species, From 6d17c8919e381cad79b34a635c1dadbf6d5367ff Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 18 Apr 2023 20:30:14 +0000 Subject: [PATCH 17/74] shorten if clause --- pyiron_atomistics/lammps/potentials.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index cf3bc5bfb..3353b82fd 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -288,9 +288,7 @@ def _initialize_df( def check_none_n_length(variable, default, length=len(pair_coeff)): if variable is None: variable = default - if not isinstance(variable, list): - return variable - if len(variable) == 1 and len(variable) < length: + if isinstance(variable, list) and len(variable) == 1 < length: variable = length * variable return variable From b76b23fb8a3cd5f6a1b49e5084b635494dded806 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 18 Apr 2023 20:38:35 +0000 Subject: [PATCH 18/74] add docstring to harmonize_species and change order --- pyiron_atomistics/lammps/potentials.py | 90 ++++++++++++++------------ 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 3353b82fd..27fb964e2 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -25,19 +25,58 @@ def __new__(cls, *args, **kwargs): cls._df = None return obj + @staticmethod + def _harmonize_args(species_symbols) -> list: + """ + Check whether species are set for the pairwise interactions. If only + one chemical species is given, duplicate the species. + """ + if len(species_symbols) == 0: + raise ValueError("Chemical elements not specified") + if len(species_symbols) == 1: + species_symbols *= 2 + return list(species_symbols) + + def _initialize_df( + self, + pair_style, + interacting_species, + pair_coeff, + preset_species=None, + model=None, + citations=None, + filename=None, + name=None, + scale=None, + cutoff=None, + ): + def check_none_n_length(variable, default, length=len(pair_coeff)): + if variable is None: + variable = default + if isinstance(variable, list) and len(variable) == 1 < length: + variable = length * variable + return variable + + arg_dict = { + "pair_style": pair_style, + "interacting_species": interacting_species, + "pair_coeff": pair_coeff, + "preset_species": check_none_n_length(preset_species, [[]]), + "cutoff": check_none_n_length(cutoff, 0), + "model": check_none_n_length(model, pair_style), + "citations": check_none_n_length(citations, [[]]), + "filename": check_none_n_length(filename, [""]), + "name": check_none_n_length(name, pair_style), + } + if scale is not None: + arg_dict["scale"] = scale + self.set_df(pd.DataFrame(arg_dict)) + def copy(self): new_pot = LammpsPotentials() new_pot.set_df(self.get_df()) return new_pot - @staticmethod - def _harmonize_args(args) -> str: - if len(args) == 0: - raise ValueError("Chemical elements not specified") - if len(args) == 1: - args *= 2 - return list(args) - @property def model(self) -> str: """Model name (required in pyiron df)""" @@ -272,41 +311,6 @@ def __add__(self, potential): ) return new_pot - def _initialize_df( - self, - pair_style, - interacting_species, - pair_coeff, - preset_species=None, - model=None, - citations=None, - filename=None, - name=None, - scale=None, - cutoff=None, - ): - def check_none_n_length(variable, default, length=len(pair_coeff)): - if variable is None: - variable = default - if isinstance(variable, list) and len(variable) == 1 < length: - variable = length * variable - return variable - - arg_dict = { - "pair_style": pair_style, - "interacting_species": interacting_species, - "pair_coeff": pair_coeff, - "preset_species": check_none_n_length(preset_species, [[]]), - "cutoff": check_none_n_length(cutoff, 0), - "model": check_none_n_length(model, pair_style), - "citations": check_none_n_length(citations, [[]]), - "filename": check_none_n_length(filename, [""]), - "name": check_none_n_length(name, pair_style), - } - if scale is not None: - arg_dict["scale"] = scale - self.set_df(pd.DataFrame(arg_dict)) - class EAM(LammpsPotentials): @staticmethod From d0d59a06645a59d865bc5c4a4748a5e86baec393 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 18 Apr 2023 20:40:02 +0000 Subject: [PATCH 19/74] change name of harmonize_species --- pyiron_atomistics/lammps/potentials.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 27fb964e2..6b13a0904 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -26,7 +26,7 @@ def __new__(cls, *args, **kwargs): return obj @staticmethod - def _harmonize_args(species_symbols) -> list: + def _harmonize_species(species_symbols) -> list: """ Check whether species are set for the pairwise interactions. If only one chemical species is given, duplicate the species. @@ -401,7 +401,7 @@ class Morse(LammpsPotentials): def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="morse"): self._initialize_df( pair_style=[pair_style], - interacting_species=[self._harmonize_args(chemical_elements)], + interacting_species=[self._harmonize_species(chemical_elements)], pair_coeff=[" ".join([str(cc) for cc in [D_0, alpha, r_0, cutoff]])], cutoff=cutoff, ) @@ -411,7 +411,7 @@ class CustomPotential(LammpsPotentials): def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): self._initialize_df( pair_style=[pair_style], - interacting_species=[self._harmonize_args(chemical_elements)], + interacting_species=[self._harmonize_species(chemical_elements)], pair_coeff=[" ".join([str(cc) for cc in kwargs.values()]) + f" {cutoff}"], cutoff=cutoff, ) From a036bd0fec4c496cd31d682386cabf51c0d50ece Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 18 Apr 2023 20:56:46 +0000 Subject: [PATCH 20/74] add doc --- pyiron_atomistics/lammps/potentials.py | 76 ++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 6b13a0904..b41a9c0d4 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -19,6 +19,29 @@ import warnings +general_doc = """ +How to combine potentials: + +Example I: Hybrid potential for a single element + +>>> from pyiron_atomistics.lammps.potentials import EAM, Morse +>>> eam = EAM("Al") +>>> morse = Morse("Al", D_0=0.5, alpha=1.1, r_0=2.1, cutoff=6) +>>> lammps_job.potential = eam + morse + +Example II: Hybrid potential for multiple elements + +>>> from pyiron_atomistics.lammps.potentials import EAM, Morse +>>> eam = EAM("Al") +>>> morse_Al_Ni = Morse("Al", "Ni", D_0=0.2, alpha=1.05, r_0=2.2, cutoff=6) +>>> morse_Ni = Morse("Ni", D_0=0.7, alpha=1.15, r_0=2.15, cutoff=6) +>>> lammps_job.potential = eam + morse_Al_Ni + morse_Ni # hybrid/overlay +>>> lammps_job.potential = eam * morse_Al_Ni * morse_Ni # hybrid +>>> lammps_job.potential = 0.4 * eam + 0.1 * morse_Al_Ni + morse_Ni # hybrid/scaled + +""" + + class LammpsPotentials: def __new__(cls, *args, **kwargs): obj = super().__new__(cls) @@ -313,6 +336,12 @@ def __add__(self, potential): class EAM(LammpsPotentials): + def __init__(self, *chemical_elements, name=None, pair_style=None): + if name is not None: + self._df_candidates = LammpsPotentialFile().find_by_name(name) + else: + self._df_candidates = LammpsPotentialFile().find(list(chemical_elements)) + @staticmethod def _get_pair_style(config): if any(["hybrid" in c for c in config]): @@ -361,12 +390,6 @@ def _get_scale(config): ) return - def __init__(self, *chemical_elements, name=None, pair_style=None): - if name is not None: - self._df_candidates = LammpsPotentialFile().find_by_name(name) - else: - self._df_candidates = LammpsPotentialFile().find(list(chemical_elements)) - def list_potentials(self): return self._df_candidates.Name @@ -398,7 +421,25 @@ def df(self): class Morse(LammpsPotentials): + """ + Morse potential defined by: + + E = D_0*[exp(-2*alpha*(r-r_0))-2*exp(-alpha*(r-r_0))] + """ def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="morse"): + """ + Args: + chemical_elements (str): Chemical elements + D_0 (float): parameter (s. eq. above) + alpha (float): parameter (s. eq. above) + r_0 (float): parameter (s. eq. above) + cutoff (float): cutoff length + pair_style (str): pair_style name (default: "morse") + + Example: + + >>> morse = Morse("Al", "Ni", D_0=1, alpha=0.5, r_0=2, cutoff=6) + """ self._initialize_df( pair_style=[pair_style], interacting_species=[self._harmonize_species(chemical_elements)], @@ -407,11 +448,34 @@ def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="mors ) +Morse.__doc__ += general_doc + + class CustomPotential(LammpsPotentials): + """ + Custom potential class to define LAMMPS potential not implemented in + pyiron + """ def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): + """ + Args: + pair_style (str): pair_style name (default: "morse") + chemical_elements (str): Chemical elements + cutoff (float): cutoff length + + Example: + + >>> morse = Morse("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + + Important: the order of parameters is conserved in the LAMMPS input + (except for `cutoff`, which is always the last argument). + """ self._initialize_df( pair_style=[pair_style], interacting_species=[self._harmonize_species(chemical_elements)], pair_coeff=[" ".join([str(cc) for cc in kwargs.values()]) + f" {cutoff}"], cutoff=cutoff, ) + + +CustomPotential.__doc__ += general_doc From e783f53ee682e39f33d02f2360285304a0fe920b Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 18 Apr 2023 21:03:09 +0000 Subject: [PATCH 21/74] add docstring to eam --- pyiron_atomistics/lammps/potentials.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index b41a9c0d4..8017daddd 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -336,6 +336,29 @@ def __add__(self, potential): class EAM(LammpsPotentials): + """ + EAM potential class to choose an EAM potential from an existing library. + You can either specify the chemical species and/or the name of the + potential. + + Example I: Via chemical species + + >>> eam = EAM("Al") + + Example II: Via potential name + + >>> eam = EAM(name="1995--Angelo-J-E--Ni-Al-H--LAMMPS--ipr1") + + If the variable `eam` is used without specifying the potential name (i.e. + in Example I), the first potential in the database corresponding with the + specified chemical species will be selected. In order to see the list of + potentials, you can also execute + + >>> eam = EAM("Al") + >>> eam.list_potentials() # See list of potential names + >>> eam.view_potentials() # See potential names and metadata + + """ def __init__(self, *chemical_elements, name=None, pair_style=None): if name is not None: self._df_candidates = LammpsPotentialFile().find_by_name(name) From ef7e88bd3d57c62e1d61c41f065bb94548260bb9 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 18 Apr 2023 21:03:29 +0000 Subject: [PATCH 22/74] remove in EAM which is not used --- pyiron_atomistics/lammps/potentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 8017daddd..1892e5b80 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -359,7 +359,7 @@ class EAM(LammpsPotentials): >>> eam.view_potentials() # See potential names and metadata """ - def __init__(self, *chemical_elements, name=None, pair_style=None): + def __init__(self, *chemical_elements, name=None): if name is not None: self._df_candidates = LammpsPotentialFile().find_by_name(name) else: From 5c770c35ed74f908fd2f4c83a3b8c06955397f59 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 18 Apr 2023 21:04:04 +0000 Subject: [PATCH 23/74] add docstring to eam __init__ --- pyiron_atomistics/lammps/potentials.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 1892e5b80..6f78bb108 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -360,6 +360,11 @@ class EAM(LammpsPotentials): """ def __init__(self, *chemical_elements, name=None): + """ + Args: + chemical_elements (str): chemical elements/species + name (str): potential name in the database + """ if name is not None: self._df_candidates = LammpsPotentialFile().find_by_name(name) else: From e60be2395a2a4ed6be9b2385bdebe6c60ce5cabb Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 18 Apr 2023 21:34:34 +0000 Subject: [PATCH 24/74] fix problem when there are multiple files --- pyiron_atomistics/lammps/potentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 6f78bb108..846c808f2 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -441,7 +441,7 @@ def df(self): preset_species=[df.Species], model=df.Model, citations=df.Citations, - filename=df.Filename, + filename=[df.Filename], name=df.Name, scale=self._get_scale(df.Config), ) From 517e64863933f57dfe61c8095a52d0a4a68f71f1 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Wed, 19 Apr 2023 13:03:34 +0000 Subject: [PATCH 25/74] Format black --- pyiron_atomistics/lammps/potentials.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 846c808f2..a1e316992 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -227,8 +227,7 @@ def interacting_species(self) -> list: ) s_dict.update({"*": "*"}) return [ - " ".join([s_dict[cc] for cc in c]) - for c in self._interacting_species + " ".join([s_dict[cc] for cc in c]) for c in self._interacting_species ] @property @@ -252,7 +251,6 @@ def pair_coeff(self) -> list: def pair_coeff(self) -> list: """LAMMPS pair_coeff""" - return self._PairCoeff( is_hybrid="hybrid" in self.pair_style, pair_style=self.df.pair_style, @@ -359,6 +357,7 @@ class EAM(LammpsPotentials): >>> eam.view_potentials() # See potential names and metadata """ + def __init__(self, *chemical_elements, name=None): """ Args: @@ -454,6 +453,7 @@ class Morse(LammpsPotentials): E = D_0*[exp(-2*alpha*(r-r_0))-2*exp(-alpha*(r-r_0))] """ + def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="morse"): """ Args: @@ -484,6 +484,7 @@ class CustomPotential(LammpsPotentials): Custom potential class to define LAMMPS potential not implemented in pyiron """ + def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): """ Args: From a5005ccf8bdc5c22dfaf499461540dbab3e18622 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 19 Apr 2023 13:06:46 +0000 Subject: [PATCH 26/74] overwrite value error --- pyiron_atomistics/lammps/potentials.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index a1e316992..51300633c 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -93,7 +93,12 @@ def check_none_n_length(variable, default, length=len(pair_coeff)): } if scale is not None: arg_dict["scale"] = scale - self.set_df(pd.DataFrame(arg_dict)) + try: + self.set_df(pd.DataFrame(arg_dict)) + except ValueError: + raise ValueError( + f"Initialization failed - inconsistency in data: {arg_dict}" + ) def copy(self): new_pot = LammpsPotentials() From 3fe1195263a1fc95a01148b27d2a739b9bc1c8c3 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Wed, 19 Apr 2023 13:11:54 +0000 Subject: [PATCH 27/74] replace EAM by Library --- pyiron_atomistics/lammps/potentials.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 51300633c..0409ad915 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -24,15 +24,15 @@ Example I: Hybrid potential for a single element ->>> from pyiron_atomistics.lammps.potentials import EAM, Morse ->>> eam = EAM("Al") +>>> from pyiron_atomistics.lammps.potentials import Library, Morse +>>> eam = Library("Al") >>> morse = Morse("Al", D_0=0.5, alpha=1.1, r_0=2.1, cutoff=6) >>> lammps_job.potential = eam + morse Example II: Hybrid potential for multiple elements ->>> from pyiron_atomistics.lammps.potentials import EAM, Morse ->>> eam = EAM("Al") +>>> from pyiron_atomistics.lammps.potentials import Library, Morse +>>> eam = Library("Al") >>> morse_Al_Ni = Morse("Al", "Ni", D_0=0.2, alpha=1.05, r_0=2.2, cutoff=6) >>> morse_Ni = Morse("Ni", D_0=0.7, alpha=1.15, r_0=2.15, cutoff=6) >>> lammps_job.potential = eam + morse_Al_Ni + morse_Ni # hybrid/overlay @@ -338,26 +338,27 @@ def __add__(self, potential): return new_pot -class EAM(LammpsPotentials): +class Library(LammpsPotentials): """ - EAM potential class to choose an EAM potential from an existing library. + Potential class to choose a file based potential from an existing library + (e.g. EAM). You can either specify the chemical species and/or the name of the potential. Example I: Via chemical species - >>> eam = EAM("Al") + >>> eam = Library("Al") Example II: Via potential name - >>> eam = EAM(name="1995--Angelo-J-E--Ni-Al-H--LAMMPS--ipr1") + >>> eam = Library(name="1995--Angelo-J-E--Ni-Al-H--LAMMPS--ipr1") If the variable `eam` is used without specifying the potential name (i.e. in Example I), the first potential in the database corresponding with the specified chemical species will be selected. In order to see the list of potentials, you can also execute - >>> eam = EAM("Al") + >>> eam = Library("Al") >>> eam.list_potentials() # See list of potential names >>> eam.view_potentials() # See potential names and metadata From 47d4d394fa44471c9ed1cea5dc452d8f00ecbee6 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 20 Apr 2023 07:42:02 +0000 Subject: [PATCH 28/74] add test_harmonize_species --- tests/lammps/test_potentials.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/lammps/test_potentials.py diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py new file mode 100644 index 000000000..08fd995a8 --- /dev/null +++ b/tests/lammps/test_potentials.py @@ -0,0 +1,19 @@ +# coding: utf-8 +# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department +# Distributed under the terms of "New BSD License", see the LICENSE file. + +from pyiron_atomistics.lammps.potentials import Library, Morse, CustomPotential, LammpsPotentials +import unittest + + +class TestPotentials(unittest.TestCase): + def test_harmonize_species(self): + pot = LammpsPotentials() + self.assertEqual(pot._harmonize_species(("Al",)), ["Al", "Al"]) + for i in [2, 3, 4]: + self.assertEqual(pot._harmonize_species(i * ("Al",)), i * ["Al"]) + self.assertRaises(ValueError, pot._harmonize_species, tuple()) + + +if __name__ == "__main__": + unittest.main() From 11f2304555decd9b1bed7fa831b32a1e52bf668b Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 20 Apr 2023 07:51:34 +0000 Subject: [PATCH 29/74] add test_set_df --- tests/lammps/test_potentials.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py index 08fd995a8..faf85f438 100644 --- a/tests/lammps/test_potentials.py +++ b/tests/lammps/test_potentials.py @@ -4,6 +4,7 @@ from pyiron_atomistics.lammps.potentials import Library, Morse, CustomPotential, LammpsPotentials import unittest +import pandas as pd class TestPotentials(unittest.TestCase): @@ -14,6 +15,22 @@ def test_harmonize_species(self): self.assertEqual(pot._harmonize_species(i * ("Al",)), i * ["Al"]) self.assertRaises(ValueError, pot._harmonize_species, tuple()) + def test_set_df(self): + pot = LammpsPotentials() + self.assertEqual(pot.df, None) + required_keys = [ + "pair_style", + "interacting_species", + "pair_coeff", + "preset_species", + ] + arg_dict = {k: [] for k in required_keys} + pot.set_df(pd.DataFrame(arg_dict)) + self.assertIsInstance(pot.df, pd.DataFrame) + for key in required_keys: + arg_dict = {k: [] for k in required_keys if k != key} + self.assertRaises(ValueError, pot.set_df, pd.DataFrame(arg_dict)) + if __name__ == "__main__": unittest.main() From 31dfa9e6ff6d7c8f05e5371538b1a9b44bf475e4 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 20 Apr 2023 15:39:19 +0000 Subject: [PATCH 30/74] add test_initialize_df and change name to potential_name to avoid conflict with DataFrame notation --- pyiron_atomistics/lammps/potentials.py | 14 +++++++------- tests/lammps/test_potentials.py | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 0409ad915..9101d0fd8 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -69,7 +69,7 @@ def _initialize_df( model=None, citations=None, filename=None, - name=None, + potential_name=None, scale=None, cutoff=None, ): @@ -89,7 +89,7 @@ def check_none_n_length(variable, default, length=len(pair_coeff)): "model": check_none_n_length(model, pair_style), "citations": check_none_n_length(citations, [[]]), "filename": check_none_n_length(filename, [""]), - "name": check_none_n_length(name, pair_style), + "potential_name": check_none_n_length(potential_name, pair_style), } if scale is not None: arg_dict["scale"] = scale @@ -111,9 +111,9 @@ def model(self) -> str: return "_and_".join(set(self.df.model)) @property - def name(self) -> str: + def potential_name(self) -> str: """Potential name (required in pyiron df)""" - return "_and_".join(set(self.df.name)) + return "_and_".join(set(self.df.potential_name)) @property def species(self): @@ -141,7 +141,7 @@ def citations(self) -> str: @property def is_scaled(self) -> bool: - """Scaling in pair_style hybrid/scaled and hybrid/overlay (whih is scale=1)""" + """Scaling in pair_style hybrid/scaled and hybrid/overlay (which is scale=1)""" return "scale" in self.df @property @@ -273,7 +273,7 @@ def pyiron_df(self): "Config": [[self.pair_style] + self.pair_coeff], "Filename": [self.filename], "Model": [self.model], - "Name": [self.name], + "Name": [self.potential_name], "Species": [self.species], "Citations": [self.citations], } @@ -447,7 +447,7 @@ def df(self): model=df.Model, citations=df.Citations, filename=[df.Filename], - name=df.Name, + potential_name=df.Name, scale=self._get_scale(df.Config), ) return self._df diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py index faf85f438..28cbc2d8b 100644 --- a/tests/lammps/test_potentials.py +++ b/tests/lammps/test_potentials.py @@ -31,6 +31,31 @@ def test_set_df(self): arg_dict = {k: [] for k in required_keys if k != key} self.assertRaises(ValueError, pot.set_df, pd.DataFrame(arg_dict)) + def test_initialize_df(self): + pot = LammpsPotentials() + pot._initialize_df( + pair_style=["some_potential"], + interacting_species=[["Al", "Al"]], + pair_coeff=["something"], + ) + self.assertIsInstance(pot.df, pd.DataFrame) + self.assertEqual(len(pot.df), 1) + self.assertEqual(pot.df.iloc[0].pair_style, "some_potential") + self.assertEqual(pot.df.iloc[0].interacting_species, ["Al", "Al"]) + self.assertEqual(pot.df.iloc[0].pair_coeff, "something") + self.assertEqual(pot.df.iloc[0].preset_species, []) + self.assertEqual(pot.df.iloc[0].cutoff, 0) + self.assertEqual(pot.df.iloc[0].model, "some_potential") + self.assertEqual(pot.df.iloc[0].citations, []) + self.assertEqual(pot.df.iloc[0].filename, "") + self.assertEqual(pot.df.iloc[0].potential_name, "some_potential") + with self.assertRaises(ValueError): + pot._initialize_df( + pair_style=["some_potential", "one_too_many"], + interacting_species=[["Al", "Al"]], + pair_coeff=["something"], + ) + if __name__ == "__main__": unittest.main() From 6aa0b0f36e2ebea0f45c99388efa10593b2a283a Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 20 Apr 2023 15:42:54 +0000 Subject: [PATCH 31/74] add test_initialize_df and change name to potential_name to avoid conflict with DataFrame notation --- pyiron_atomistics/lammps/potentials.py | 2 +- tests/lammps/test_potentials.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 9101d0fd8..fb9593ebc 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -500,7 +500,7 @@ def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): Example: - >>> morse = Morse("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + >>> custom_pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) Important: the order of parameters is conserved in the LAMMPS input (except for `cutoff`, which is always the last argument). diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py index 28cbc2d8b..7366f974e 100644 --- a/tests/lammps/test_potentials.py +++ b/tests/lammps/test_potentials.py @@ -56,6 +56,9 @@ def test_initialize_df(self): pair_coeff=["something"], ) + def test_custom_potential(self): + pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + self.assertEqual(pot.df.iloc[0].pair_coeff, "0.5 1 3") if __name__ == "__main__": unittest.main() From daf19c3bb475ca50fd3fef6128927dd9bc97c93d Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 20 Apr 2023 15:48:16 +0000 Subject: [PATCH 32/74] add test_copy --- tests/lammps/test_potentials.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py index 7366f974e..9dd358925 100644 --- a/tests/lammps/test_potentials.py +++ b/tests/lammps/test_potentials.py @@ -5,6 +5,7 @@ from pyiron_atomistics.lammps.potentials import Library, Morse, CustomPotential, LammpsPotentials import unittest import pandas as pd +import numpy as np class TestPotentials(unittest.TestCase): @@ -60,5 +61,12 @@ def test_custom_potential(self): pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) self.assertEqual(pot.df.iloc[0].pair_coeff, "0.5 1 3") + def test_copy(self): + pot_1 = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + pot_2 = pot_1.copy() + self.assertTrue(np.all(pot_1.df == pot_2.df)) + pot_2.df.cutoff = 1 + self.assertFalse(np.all(pot_1.df == pot_2.df)) + if __name__ == "__main__": unittest.main() From 6cde6860890c281682b99e8206a9c3157e3a5f94 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 20 Apr 2023 16:07:58 +0000 Subject: [PATCH 33/74] extend explanation for why pyiron failed when pair_style not identified --- pyiron_atomistics/lammps/potentials.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index fb9593ebc..e5a780367 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -384,7 +384,18 @@ def _get_pair_style(config): return [" ".join(c.replace("\n", "").split()[1:])] * sum( ["pair_coeff" in c for c in config] ) - raise ValueError(f"pair_style could not determined: {config}") + raise ValueError( + f""" + pair_style could not determined: {config}. + + The reason why you are seeing this error is most likely because + the potential you chose had a corrupt config. It is + supposed to have at least one item which starts with "pair_style". + If you are using the standard pyiron database, feel free to + submit an issue on https://github.com/pyiron/pyiron_atomistics/issues + Typically you can get a reply within 24h. + """ + ) @staticmethod def _get_pair_coeff(config): From 2198470eb2e9346122d56e7782eaff6bf0e76c62 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 20 Apr 2023 16:36:34 +0000 Subject: [PATCH 34/74] add test_model --- tests/lammps/test_potentials.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py index 9dd358925..ba69c4d7a 100644 --- a/tests/lammps/test_potentials.py +++ b/tests/lammps/test_potentials.py @@ -68,5 +68,18 @@ def test_copy(self): pot_2.df.cutoff = 1 self.assertFalse(np.all(pot_1.df == pot_2.df)) + def test_model(self): + pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + self.assertEqual(pot.model, "lj/cut") + pot = LammpsPotentials() + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + model=["first", "second"] + ) + self.assertEqual(pot.model, "first_and_second") + + if __name__ == "__main__": unittest.main() From d084ac59584aceb3e15a61fd77515feef4eaa54d Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 20 Apr 2023 16:37:23 +0000 Subject: [PATCH 35/74] add test_model --- tests/lammps/test_potentials.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py index ba69c4d7a..1f8400d4e 100644 --- a/tests/lammps/test_potentials.py +++ b/tests/lammps/test_potentials.py @@ -80,6 +80,18 @@ def test_model(self): ) self.assertEqual(pot.model, "first_and_second") + def test_potential_name(self): + pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + self.assertEqual(pot.potential_name, "lj/cut") + pot = LammpsPotentials() + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"] + ) + self.assertEqual(pot.potential_name, "first_and_second") + if __name__ == "__main__": unittest.main() From 1055eea0f22eaac5bfcdcbfb83668950926527eb Mon Sep 17 00:00:00 2001 From: samwaseda Date: Thu, 20 Apr 2023 20:17:00 +0000 Subject: [PATCH 36/74] add unique and test --- pyiron_atomistics/lammps/potentials.py | 9 +++++++-- tests/lammps/test_potentials.py | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index e5a780367..771850d8d 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -105,15 +105,20 @@ def copy(self): new_pot.set_df(self.get_df()) return new_pot + @staticmethod + def _unique(args): + labels, indices = np.unique(args, return_index=True) + return labels[np.argsort(indices)] + @property def model(self) -> str: """Model name (required in pyiron df)""" - return "_and_".join(set(self.df.model)) + return "_and_".join(self._unique(self.df.model)) @property def potential_name(self) -> str: """Potential name (required in pyiron df)""" - return "_and_".join(set(self.df.potential_name)) + return "_and_".join(self._unique(self.df.potential_name)) @property def species(self): diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py index 1f8400d4e..9a6849253 100644 --- a/tests/lammps/test_potentials.py +++ b/tests/lammps/test_potentials.py @@ -92,6 +92,14 @@ def test_potential_name(self): ) self.assertEqual(pot.potential_name, "first_and_second") + def test_is_scaled(self): + pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) + self.assertFalse(pot.is_scaled) + + def test_unique(self): + pot = LammpsPotentials() + self.assertEqual(pot._unique([1, 0, 2, 1, 3]).tolist(), [1, 0, 2, 3]) + if __name__ == "__main__": unittest.main() From 9390fa8f3f79af24c8f89b4ec3a18e65c655abf5 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Fri, 21 Apr 2023 08:44:02 +0000 Subject: [PATCH 37/74] replace set by _unique. I don't think there's a real difference in functioanlity, but it might change the input file and therefore might cause a problem when input hashing is introduced --- pyiron_atomistics/lammps/potentials.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 771850d8d..157a5459d 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -123,8 +123,8 @@ def potential_name(self) -> str: @property def species(self): """Species defined in the potential""" - species = set([ss for s in self.df.interacting_species for ss in s]) - preset = set(["___".join(s) for s in self.df.preset_species if len(s) > 0]) + species = self._unique([ss for s in self.df.interacting_species for ss in s]) + preset = self._unique(["___".join(s) for s in self.df.preset_species if len(s) > 0]) if len(preset) == 0: return list(species) elif len(preset) > 1: @@ -132,12 +132,12 @@ def species(self): "Currently not possible to have multiple file-based potentials" ) preset = list(preset)[0].split("___") - return [p for p in preset + list(species - set(preset)) if p != "*"] + return [p for p in preset + list(species - self._unique(preset)) if p != "*"] @property def filename(self) -> list: """LAMMPS potential files""" - return [f for f in set(self.df.filename) if len(f) > 0] + return [f for f in self._unique(self.df.filename) if len(f) > 0] @property def citations(self) -> str: From 19c2515e42961ba3d0e5bf98975dd4f317c3383f Mon Sep 17 00:00:00 2001 From: samwaseda Date: Fri, 21 Apr 2023 08:54:36 +0000 Subject: [PATCH 38/74] add test_pair_style --- tests/lammps/test_potentials.py | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py index 9a6849253..96f300d5b 100644 --- a/tests/lammps/test_potentials.py +++ b/tests/lammps/test_potentials.py @@ -100,6 +100,56 @@ def test_unique(self): pot = LammpsPotentials() self.assertEqual(pot._unique([1, 0, 2, 1, 3]).tolist(), [1, 0, 2, 3]) + def test_pair_style(self): + pot = LammpsPotentials() + pot._initialize_df( + pair_style=["a"], + interacting_species=[["Al"]], + pair_coeff=["one"], + potential_name=["first"] + ) + self.assertEqual(pot.pair_style, "pair_style a\n") + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"] + ) + self.assertEqual(pot.pair_style, "pair_style hybrid a b\n") + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"], + cutoff=[1, 1], + ) + self.assertEqual(pot.pair_style, "pair_style hybrid a 1 b 1\n") + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"], + scale=[1, 1], + cutoff=[1, 1], + ) + self.assertEqual(pot.pair_style, "pair_style hybrid/overlay a 1 b 1\n") + pot._initialize_df( + pair_style=["a", "b"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"], + scale=[1, 0.5], + cutoff=[1, 1], + ) + self.assertEqual(pot.pair_style, "pair_style hybrid/scaled 1.0 a 1 0.5 b 1\n") + pot._initialize_df( + pair_style=["a", "a"], + interacting_species=[["Al"], ["Ni"]], + pair_coeff=["one", "two"], + potential_name=["first", "second"], + ) + self.assertEqual(pot.pair_style, "pair_style a\n") + if __name__ == "__main__": unittest.main() From 91d58d4cfd63ee8f08193fb7549aea22c3e4ef13 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Fri, 21 Apr 2023 09:33:48 +0000 Subject: [PATCH 39/74] Check cutoff length --- pyiron_atomistics/lammps/potentials.py | 54 +++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 157a5459d..10adbdd2d 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -42,6 +42,38 @@ """ +doc_pyiron_df = """ +- "Config": Lammps commands in a list. Lines are separated by list items and + each entry must finish with a new line +- "Filename": Potential file name in either absolute path or relative to + the pyiron-resources path (optional) +- "Model": Model name (optional) +- "Name": Name of the potential (optional) +- "Species": Order of species as defined in pair_coeff +- "Citations": Citations (optional) + +Example + +>>> import pandas as pd +>>> return pd.DataFrame( +... { +... "Config": [[ +... 'pair_style my_potential 3.2\n', +... 'pair_coeff 2 1 1.1 2.3 3.2\n' +... ]], +... "Filename": [""], +... "Model": ["my_model"], +... "Name": ["my_potential"], +... "Species": [["Fe", "Al"]], +... "Citations": [], +... } +... ) +""" + + +issue_page = "https://github.com/pyiron/pyiron_atomistics/issues" + + class LammpsPotentials: def __new__(cls, *args, **kwargs): obj = super().__new__(cls) @@ -397,7 +429,7 @@ def _get_pair_style(config): the potential you chose had a corrupt config. It is supposed to have at least one item which starts with "pair_style". If you are using the standard pyiron database, feel free to - submit an issue on https://github.com/pyiron/pyiron_atomistics/issues + submit an issue on {issue_page} Typically you can get a reply within 24h. """ ) @@ -469,6 +501,24 @@ def df(self): return self._df +def check_cutoff(f): + def wrapper(*args, **kwargs): + if kwargs["cutoff"] == 0: + raise ValueError(f""" + It is not possible to set cutoff=0 for parameter-based + potentials. If you think this should be possible, you have the + following options: + + - Open an issue on our GitHub page: {issue_page} + + - Write your own potential in pyiron format. Here's how: + + {doc_pyiron_df} + """) + return f(*args, **kwargs) + return wrapper + + class Morse(LammpsPotentials): """ Morse potential defined by: @@ -476,6 +526,7 @@ class Morse(LammpsPotentials): E = D_0*[exp(-2*alpha*(r-r_0))-2*exp(-alpha*(r-r_0))] """ + @check_cutoff def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="morse"): """ Args: @@ -507,6 +558,7 @@ class CustomPotential(LammpsPotentials): pyiron """ + @check_cutoff def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): """ Args: From 6e5b030f857137445769c1085b3a5711bdf0d350 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Sat, 22 Apr 2023 11:59:43 +0000 Subject: [PATCH 40/74] add test_PairCoeff and test counter --- tests/lammps/test_potentials.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py index 96f300d5b..158f96ee1 100644 --- a/tests/lammps/test_potentials.py +++ b/tests/lammps/test_potentials.py @@ -150,6 +150,18 @@ def test_pair_style(self): ) self.assertEqual(pot.pair_style, "pair_style a\n") + def test_PairCoeff(self): + pot = LammpsPotentials() + pc = pot._PairCoeff( + is_hybrid=False, + pair_style=["my_style"], + interacting_species=[["Al", "Fe"]], + pair_coeff=["some arguments"], + species=["Al", "Fe"], + preset_species=[], + ) + self.assertEqual(pc.counter, [""]) + if __name__ == "__main__": unittest.main() From 1249c2d77e131e6cefcd37739129b5265c533ae6 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 24 Apr 2023 16:22:14 +0200 Subject: [PATCH 41/74] VASP: Parse the number of steps correctly from OUTCAR For VASP jobs output/generic/steps is generated in either of two ways: 1. vasprun.xml can be parsed -> np.arange(len(output/generic/energy_tot)) 2. vasprun.xml cannot be parsed -> np.linspace(0, #trigger) where #trigger is the number of times the string FREE ENERGIE OF THE ION-ELECTRON SYSTEM (eV) occurs in the OUTCAR file. The latter leads to fractional output and always 50 entries in that array. Both is contradictory to the first way and seems absurd. This commit changes this to align with the first way. Also this fixes a bug where the intent of the previous code seems to have been to multiply the step indices by the value of NBLOCK. This however is not done in the first way. --- pyiron_atomistics/vasp/outcar.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyiron_atomistics/vasp/outcar.py b/pyiron_atomistics/vasp/outcar.py index db546037a..d98e3893b 100644 --- a/pyiron_atomistics/vasp/outcar.py +++ b/pyiron_atomistics/vasp/outcar.py @@ -664,18 +664,16 @@ def get_steps(filename="OUTCAR", lines=None): nblock_trigger = "NBLOCK =" trigger = "FREE ENERGIE OF THE ION-ELECTRON SYSTEM (eV)" trigger_indices = list() - read_nblock = True n_block = 1 lines = _get_lines_from_file(filename=filename, lines=lines) for i, line in enumerate(lines): line = line.strip() if trigger in line: trigger_indices.append(i) - if read_nblock is None: - if nblock_trigger in line: - line = _clean_line(line) - n_block = int(line.split(nblock_trigger)[-1]) - return n_block * np.linspace(0, len(trigger_indices)) + if nblock_trigger in line: + line = _clean_line(line) + n_block = int(line.split(nblock_trigger)[-1]) + return n_block * np.arange(0, len(trigger_indices)) def get_time(self, filename="OUTCAR", lines=None): """ From d91a0220859580e44c235447ca2cf5416abcff2b Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 24 Apr 2023 17:06:23 +0200 Subject: [PATCH 42/74] Use proper arange syntax --- pyiron_atomistics/vasp/outcar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/vasp/outcar.py b/pyiron_atomistics/vasp/outcar.py index d98e3893b..30ab8c4b6 100644 --- a/pyiron_atomistics/vasp/outcar.py +++ b/pyiron_atomistics/vasp/outcar.py @@ -673,7 +673,7 @@ def get_steps(filename="OUTCAR", lines=None): if nblock_trigger in line: line = _clean_line(line) n_block = int(line.split(nblock_trigger)[-1]) - return n_block * np.arange(0, len(trigger_indices)) + return np.arange(0, len(trigger_indices) * n_block, n_block) def get_time(self, filename="OUTCAR", lines=None): """ From a12e294eab06039a59e9482151935c8f4be8f9a8 Mon Sep 17 00:00:00 2001 From: samwaseda Date: Mon, 24 Apr 2023 15:09:47 +0000 Subject: [PATCH 43/74] correct complementary list --- pyiron_atomistics/lammps/potentials.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 10adbdd2d..5956d44db 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -164,7 +164,8 @@ def species(self): "Currently not possible to have multiple file-based potentials" ) preset = list(preset)[0].split("___") - return [p for p in preset + list(species - self._unique(preset)) if p != "*"] + comp_lst = [s for s in species if s not in self._unique(preset)] + return [p for p in preset + comp_lst if p != "*"] @property def filename(self) -> list: @@ -503,7 +504,7 @@ def df(self): def check_cutoff(f): def wrapper(*args, **kwargs): - if kwargs["cutoff"] == 0: + if "cutoff" not in kwargs or kwargs["cutoff"] == 0: raise ValueError(f""" It is not possible to set cutoff=0 for parameter-based potentials. If you think this should be possible, you have the From 52dc19e878fa885135006240b7f192a5f0e9759e Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 25 Apr 2023 06:18:29 +0000 Subject: [PATCH 44/74] Format black --- pyiron_atomistics/lammps/potentials.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py index 5956d44db..33c304e69 100644 --- a/pyiron_atomistics/lammps/potentials.py +++ b/pyiron_atomistics/lammps/potentials.py @@ -156,7 +156,9 @@ def potential_name(self) -> str: def species(self): """Species defined in the potential""" species = self._unique([ss for s in self.df.interacting_species for ss in s]) - preset = self._unique(["___".join(s) for s in self.df.preset_species if len(s) > 0]) + preset = self._unique( + ["___".join(s) for s in self.df.preset_species if len(s) > 0] + ) if len(preset) == 0: return list(species) elif len(preset) > 1: @@ -505,7 +507,8 @@ def df(self): def check_cutoff(f): def wrapper(*args, **kwargs): if "cutoff" not in kwargs or kwargs["cutoff"] == 0: - raise ValueError(f""" + raise ValueError( + f""" It is not possible to set cutoff=0 for parameter-based potentials. If you think this should be possible, you have the following options: @@ -515,8 +518,10 @@ def wrapper(*args, **kwargs): - Write your own potential in pyiron format. Here's how: {doc_pyiron_df} - """) + """ + ) return f(*args, **kwargs) + return wrapper From a40a84f5e8070af7c68f6730800ec403ec934cd1 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 22 May 2023 03:44:56 +0200 Subject: [PATCH 45/74] Split NBLOCK line correctly --- pyiron_atomistics/vasp/outcar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiron_atomistics/vasp/outcar.py b/pyiron_atomistics/vasp/outcar.py index 30ab8c4b6..c476f2158 100644 --- a/pyiron_atomistics/vasp/outcar.py +++ b/pyiron_atomistics/vasp/outcar.py @@ -672,7 +672,7 @@ def get_steps(filename="OUTCAR", lines=None): trigger_indices.append(i) if nblock_trigger in line: line = _clean_line(line) - n_block = int(line.split(nblock_trigger)[-1]) + n_block = int(line.split(nblock_trigger.split(";", maxsplit=1)[0])[-1]) return np.arange(0, len(trigger_indices) * n_block, n_block) def get_time(self, filename="OUTCAR", lines=None): From f1f5922830b48f2196de7ba315b33a7c17ada6f0 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 22 May 2023 04:42:51 +0200 Subject: [PATCH 46/74] Use regex to match instead --- pyiron_atomistics/vasp/outcar.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyiron_atomistics/vasp/outcar.py b/pyiron_atomistics/vasp/outcar.py index c476f2158..5c4d1fa8d 100644 --- a/pyiron_atomistics/vasp/outcar.py +++ b/pyiron_atomistics/vasp/outcar.py @@ -661,19 +661,21 @@ def get_steps(filename="OUTCAR", lines=None): Returns: numpy.ndarray: Steps during the simulation """ - nblock_trigger = "NBLOCK =" + nblock_regex = re.compile(r"NBLOCK =\s+(\d+);") trigger = "FREE ENERGIE OF THE ION-ELECTRON SYSTEM (eV)" trigger_indices = list() - n_block = 1 + nblock = None lines = _get_lines_from_file(filename=filename, lines=lines) for i, line in enumerate(lines): line = line.strip() if trigger in line: trigger_indices.append(i) - if nblock_trigger in line: + if nblock is not None: line = _clean_line(line) - n_block = int(line.split(nblock_trigger.split(";", maxsplit=1)[0])[-1]) - return np.arange(0, len(trigger_indices) * n_block, n_block) + nblock = int(nblock_regex.findall(line)[0]) + if nblock is None: + nblock = 1 + return np.arange(0, len(trigger_indices) * nblock, nblock) def get_time(self, filename="OUTCAR", lines=None): """ From 6752735b2bc0aba3809b654720fd96660f8c40d1 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 22 May 2023 04:46:08 +0200 Subject: [PATCH 47/74] Do not keep unused list around --- pyiron_atomistics/vasp/outcar.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyiron_atomistics/vasp/outcar.py b/pyiron_atomistics/vasp/outcar.py index 5c4d1fa8d..a6c77d32f 100644 --- a/pyiron_atomistics/vasp/outcar.py +++ b/pyiron_atomistics/vasp/outcar.py @@ -663,19 +663,19 @@ def get_steps(filename="OUTCAR", lines=None): """ nblock_regex = re.compile(r"NBLOCK =\s+(\d+);") trigger = "FREE ENERGIE OF THE ION-ELECTRON SYSTEM (eV)" - trigger_indices = list() + steps = 0 nblock = None lines = _get_lines_from_file(filename=filename, lines=lines) for i, line in enumerate(lines): line = line.strip() if trigger in line: - trigger_indices.append(i) + steps += 1 if nblock is not None: line = _clean_line(line) nblock = int(nblock_regex.findall(line)[0]) if nblock is None: nblock = 1 - return np.arange(0, len(trigger_indices) * nblock, nblock) + return np.arange(0, steps * nblock, nblock) def get_time(self, filename="OUTCAR", lines=None): """ From 1082a7d4ce3d1299b308cf8b7a69070cda7c19e1 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 22 May 2023 05:03:09 +0200 Subject: [PATCH 48/74] Remove previous weird test case The old test checked that the output of the get_steps parser is always the linspace(0,1) regardless of the actual number of steps in the OUTCAR. This replaces the test by a naive parser. --- tests/vasp/test_outcar.py | 75 ++++++++++----------------------------- 1 file changed, 18 insertions(+), 57 deletions(-) diff --git a/tests/vasp/test_outcar.py b/tests/vasp/test_outcar.py index d6014196d..a16740109 100644 --- a/tests/vasp/test_outcar.py +++ b/tests/vasp/test_outcar.py @@ -819,65 +819,26 @@ def test_get_temperatures(self): self.assertEqual(temperatures.__str__(), output.__str__()) def test_get_steps(self): + def naive_parse(filename): + with open(filename) as f: + nblock = 1 + steps = 1 + for l in f: + if "NBLOCK" in l: + nblock = int(l.split(";")[0].split("=")[1]) + steps += "FREE ENERGIE OF THE ION-ELECTRON SYSTEM" in l + return steps, nblock + for filename in self.file_list: output = self.outcar_parser.get_steps(filename) - self.assertIsInstance(output, np.ndarray) - if int(filename.split("/OUTCAR_")[-1]) in [1, 2, 3, 4, 5, 6]: - steps = np.array( - [ - 0.0, - 0.02040816, - 0.04081633, - 0.06122449, - 0.08163265, - 0.10204082, - 0.12244898, - 0.14285714, - 0.16326531, - 0.18367347, - 0.20408163, - 0.2244898, - 0.24489796, - 0.26530612, - 0.28571429, - 0.30612245, - 0.32653061, - 0.34693878, - 0.36734694, - 0.3877551, - 0.40816327, - 0.42857143, - 0.44897959, - 0.46938776, - 0.48979592, - 0.51020408, - 0.53061224, - 0.55102041, - 0.57142857, - 0.59183673, - 0.6122449, - 0.63265306, - 0.65306122, - 0.67346939, - 0.69387755, - 0.71428571, - 0.73469388, - 0.75510204, - 0.7755102, - 0.79591837, - 0.81632653, - 0.83673469, - 0.85714286, - 0.87755102, - 0.89795918, - 0.91836735, - 0.93877551, - 0.95918367, - 0.97959184, - 1.0, - ] - ) - self.assertEqual(steps.__str__(), output.__str__()) + self.assertIsInstance(output, np.ndarray, "steps has to be an array!") + nblock, steps = naive_parse(filename) + total = steps * nblock + self.assertEqual( + (output == np.arange(0, total, nblock)).all(), + "Parsed output steps do not match numpy.arange(0, {total}, {nblock})!" + ) + def test_get_time(self): for filename in self.file_list: From 739668185ee0e82ef0a8700d4bd43ba8c5e79fb6 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Mon, 22 May 2023 05:43:29 +0200 Subject: [PATCH 49/74] Use array comparison --- tests/vasp/test_outcar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/vasp/test_outcar.py b/tests/vasp/test_outcar.py index a16740109..cbbf947f3 100644 --- a/tests/vasp/test_outcar.py +++ b/tests/vasp/test_outcar.py @@ -834,8 +834,8 @@ def naive_parse(filename): self.assertIsInstance(output, np.ndarray, "steps has to be an array!") nblock, steps = naive_parse(filename) total = steps * nblock - self.assertEqual( - (output == np.arange(0, total, nblock)).all(), + self.assertTrue( + np.array_equal(output, np.arange(0, total, nblock)), "Parsed output steps do not match numpy.arange(0, {total}, {nblock})!" ) From d5886233c233225935d44f36ac68257e9d5d2fd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 08:57:13 +0000 Subject: [PATCH 50/74] Bump mp-api from 0.33.2 to 0.33.3 Bumps [mp-api](https://github.com/materialsproject/api) from 0.33.2 to 0.33.3. - [Release notes](https://github.com/materialsproject/api/releases) - [Commits](https://github.com/materialsproject/api/compare/v0.33.2...v0.33.3) --- updated-dependencies: - dependency-name: mp-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6c5a8a90e..729c3fbe5 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ 'h5py==3.8.0', 'matplotlib==3.7.1', 'mendeleev==0.13.1', - 'mp-api==0.33.2', + 'mp-api==0.33.3', 'numpy==1.24.3', 'pandas==2.0.1', 'phonopy==2.19.0', From 5bf971ad94765b41c577234387fccc39967e9592 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 08:57:20 +0000 Subject: [PATCH 51/74] Bump seekpath from 2.0.1 to 2.1.0 Bumps [seekpath](https://github.com/giovannipizzi/seekpath) from 2.0.1 to 2.1.0. - [Changelog](https://github.com/giovannipizzi/seekpath/blob/main/CHANGELOG.md) - [Commits](https://github.com/giovannipizzi/seekpath/compare/v2.0.1...v2.1.0) --- updated-dependencies: - dependency-name: seekpath dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6c5a8a90e..0a500ff17 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ 'pyiron_base==0.5.38', 'pymatgen==2023.5.10', 'scipy==1.10.1', - 'seekpath==2.0.1', + 'seekpath==2.1.0', 'scikit-learn==1.2.2', 'spglib==2.0.2', 'structuretoolkit==0.0.3' From 7f049e185d93ed23c48b5b03c6d95cdc327b6fee Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 22 May 2023 09:00:20 +0000 Subject: [PATCH 52/74] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 1990e2923..1575ba582 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -9,7 +9,7 @@ dependencies: - h5py =3.8.0 - matplotlib-base =3.7.1 - mendeleev =0.13.1 -- mp-api =0.33.2 +- mp-api =0.33.3 - numpy =1.24.3 - pandas =2.0.1 - phonopy =2.19.0 From b9a3b1f4e496e4db8e7847ccad3298ad295b0d83 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 22 May 2023 09:09:23 +0000 Subject: [PATCH 53/74] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 1990e2923..26541f154 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -19,6 +19,6 @@ dependencies: - pyscal =2.10.18 - scikit-learn =1.2.2 - scipy =1.10.1 -- seekpath =2.0.1 +- seekpath =2.1.0 - spglib =2.0.2 - structuretoolkit =0.0.3 From f94841f8a49cb4f2662715d8420035105e4cca89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 10:06:17 +0000 Subject: [PATCH 54/74] Bump phonopy from 2.19.0 to 2.19.1 Bumps [phonopy](https://phonopy.github.io/phonopy/) from 2.19.0 to 2.19.1. --- updated-dependencies: - dependency-name: phonopy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 729c3fbe5..02dfa1996 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ 'mp-api==0.33.3', 'numpy==1.24.3', 'pandas==2.0.1', - 'phonopy==2.19.0', + 'phonopy==2.19.1', 'pint==0.21', 'pyiron_base==0.5.38', 'pymatgen==2023.5.10', From 937063f19dafc86a102dd807f900dceb0c6049f5 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 22 May 2023 10:09:10 +0000 Subject: [PATCH 55/74] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 1575ba582..5d6af4905 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -12,7 +12,7 @@ dependencies: - mp-api =0.33.3 - numpy =1.24.3 - pandas =2.0.1 -- phonopy =2.19.0 +- phonopy =2.19.1 - pint =0.21 - pyiron_base =0.5.38 - pymatgen =2023.5.10 From 5bbab469e3b0ad47a592b1ad8f1e6f656386710e Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 23 May 2023 15:36:48 +0000 Subject: [PATCH 56/74] remove potentials.py --- pyiron_atomistics/lammps/base.py | 5 +- pyiron_atomistics/lammps/potentials.py | 590 ------------------------- 2 files changed, 2 insertions(+), 593 deletions(-) delete mode 100644 pyiron_atomistics/lammps/potentials.py diff --git a/pyiron_atomistics/lammps/base.py b/pyiron_atomistics/lammps/base.py index 8e82d41d9..e55c69b66 100644 --- a/pyiron_atomistics/lammps/base.py +++ b/pyiron_atomistics/lammps/base.py @@ -17,7 +17,6 @@ view_potentials, list_potentials, ) -from pyiron_atomistics.lammps.potentials import LammpsPotentials from pyiron_atomistics.atomistics.job.atomistic import AtomisticGenericJob from pyiron_atomistics.lammps.control import LammpsControl from pyiron_atomistics.lammps.potential import LammpsPotential @@ -207,8 +206,8 @@ def potential(self, potential_filename): potential = potential_db.find_by_name(potential_filename) elif isinstance(potential_filename, pd.DataFrame): potential = potential_filename - elif isinstance(potential_filename, LammpsPotentials): - potential = potential_filename.pyiron_df + elif hasattr(potential_filename, "get_df"): + potential = potential_filename.get_df() else: raise TypeError("Potentials have to be strings or pandas dataframes.") if self.structure: diff --git a/pyiron_atomistics/lammps/potentials.py b/pyiron_atomistics/lammps/potentials.py deleted file mode 100644 index 33c304e69..000000000 --- a/pyiron_atomistics/lammps/potentials.py +++ /dev/null @@ -1,590 +0,0 @@ -# coding: utf-8 -# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department -# Distributed under the terms of "New BSD License", see the LICENSE file. - -__author__ = "Sam Waseda" -__copyright__ = ( - "Copyright 2023, Max-Planck-Institut für Eisenforschung GmbH - " - "Computational Materials Design (CM) Department" -) -__version__ = "1.0" -__maintainer__ = "Sam Waseda" -__email__ = "waseda@mpie.de" -__status__ = "production" -__date__ = "April 18, 2023" - -import pandas as pd -from pyiron_atomistics.lammps.potential import LammpsPotentialFile -import numpy as np -import warnings - - -general_doc = """ -How to combine potentials: - -Example I: Hybrid potential for a single element - ->>> from pyiron_atomistics.lammps.potentials import Library, Morse ->>> eam = Library("Al") ->>> morse = Morse("Al", D_0=0.5, alpha=1.1, r_0=2.1, cutoff=6) ->>> lammps_job.potential = eam + morse - -Example II: Hybrid potential for multiple elements - ->>> from pyiron_atomistics.lammps.potentials import Library, Morse ->>> eam = Library("Al") ->>> morse_Al_Ni = Morse("Al", "Ni", D_0=0.2, alpha=1.05, r_0=2.2, cutoff=6) ->>> morse_Ni = Morse("Ni", D_0=0.7, alpha=1.15, r_0=2.15, cutoff=6) ->>> lammps_job.potential = eam + morse_Al_Ni + morse_Ni # hybrid/overlay ->>> lammps_job.potential = eam * morse_Al_Ni * morse_Ni # hybrid ->>> lammps_job.potential = 0.4 * eam + 0.1 * morse_Al_Ni + morse_Ni # hybrid/scaled - -""" - - -doc_pyiron_df = """ -- "Config": Lammps commands in a list. Lines are separated by list items and - each entry must finish with a new line -- "Filename": Potential file name in either absolute path or relative to - the pyiron-resources path (optional) -- "Model": Model name (optional) -- "Name": Name of the potential (optional) -- "Species": Order of species as defined in pair_coeff -- "Citations": Citations (optional) - -Example - ->>> import pandas as pd ->>> return pd.DataFrame( -... { -... "Config": [[ -... 'pair_style my_potential 3.2\n', -... 'pair_coeff 2 1 1.1 2.3 3.2\n' -... ]], -... "Filename": [""], -... "Model": ["my_model"], -... "Name": ["my_potential"], -... "Species": [["Fe", "Al"]], -... "Citations": [], -... } -... ) -""" - - -issue_page = "https://github.com/pyiron/pyiron_atomistics/issues" - - -class LammpsPotentials: - def __new__(cls, *args, **kwargs): - obj = super().__new__(cls) - cls._df = None - return obj - - @staticmethod - def _harmonize_species(species_symbols) -> list: - """ - Check whether species are set for the pairwise interactions. If only - one chemical species is given, duplicate the species. - """ - if len(species_symbols) == 0: - raise ValueError("Chemical elements not specified") - if len(species_symbols) == 1: - species_symbols *= 2 - return list(species_symbols) - - def _initialize_df( - self, - pair_style, - interacting_species, - pair_coeff, - preset_species=None, - model=None, - citations=None, - filename=None, - potential_name=None, - scale=None, - cutoff=None, - ): - def check_none_n_length(variable, default, length=len(pair_coeff)): - if variable is None: - variable = default - if isinstance(variable, list) and len(variable) == 1 < length: - variable = length * variable - return variable - - arg_dict = { - "pair_style": pair_style, - "interacting_species": interacting_species, - "pair_coeff": pair_coeff, - "preset_species": check_none_n_length(preset_species, [[]]), - "cutoff": check_none_n_length(cutoff, 0), - "model": check_none_n_length(model, pair_style), - "citations": check_none_n_length(citations, [[]]), - "filename": check_none_n_length(filename, [""]), - "potential_name": check_none_n_length(potential_name, pair_style), - } - if scale is not None: - arg_dict["scale"] = scale - try: - self.set_df(pd.DataFrame(arg_dict)) - except ValueError: - raise ValueError( - f"Initialization failed - inconsistency in data: {arg_dict}" - ) - - def copy(self): - new_pot = LammpsPotentials() - new_pot.set_df(self.get_df()) - return new_pot - - @staticmethod - def _unique(args): - labels, indices = np.unique(args, return_index=True) - return labels[np.argsort(indices)] - - @property - def model(self) -> str: - """Model name (required in pyiron df)""" - return "_and_".join(self._unique(self.df.model)) - - @property - def potential_name(self) -> str: - """Potential name (required in pyiron df)""" - return "_and_".join(self._unique(self.df.potential_name)) - - @property - def species(self): - """Species defined in the potential""" - species = self._unique([ss for s in self.df.interacting_species for ss in s]) - preset = self._unique( - ["___".join(s) for s in self.df.preset_species if len(s) > 0] - ) - if len(preset) == 0: - return list(species) - elif len(preset) > 1: - raise NotImplementedError( - "Currently not possible to have multiple file-based potentials" - ) - preset = list(preset)[0].split("___") - comp_lst = [s for s in species if s not in self._unique(preset)] - return [p for p in preset + comp_lst if p != "*"] - - @property - def filename(self) -> list: - """LAMMPS potential files""" - return [f for f in self._unique(self.df.filename) if len(f) > 0] - - @property - def citations(self) -> str: - """Citations to be included""" - return "".join(np.unique([c for c in self.df.citations if len(c) > 0])) - - @property - def is_scaled(self) -> bool: - """Scaling in pair_style hybrid/scaled and hybrid/overlay (which is scale=1)""" - return "scale" in self.df - - @property - def pair_style(self) -> str: - """LAMMPS pair_style""" - if len(set(self.df.pair_style)) == 1: - pair_style = "pair_style " + list(set(self.df.pair_style))[0] - if np.max(self.df.cutoff) > 0: - pair_style += f" {np.max(self.df.cutoff)}" - return pair_style + "\n" - elif "scale" not in self.df: - pair_style = "pair_style hybrid" - elif all(self.df.scale == 1): - pair_style = "pair_style hybrid/overlay" - else: - pair_style = "pair_style hybrid/scaled" - for ii, s in enumerate(self.df[["pair_style", "cutoff"]].values): - if pair_style.startswith("pair_style hybrid/scaled"): - pair_style += f" {self.df.iloc[ii].scale}" - pair_style += f" {s[0]}" - if s[1] > 0: - pair_style += f" {s[1]}" - return pair_style + "\n" - - class _PairCoeff: - def __init__( - self, - is_hybrid, - pair_style, - interacting_species, - pair_coeff, - species, - preset_species, - ): - self.is_hybrid = is_hybrid - self._interacting_species = interacting_species - self._pair_coeff = pair_coeff - self._species = species - self._preset_species = preset_species - self._pair_style = pair_style - - @property - def counter(self): - """ - Enumeration of potentials if a potential is used multiple - times in hybrid (which is a requirement from LAMMPS) - """ - key, count = np.unique(self._pair_style, return_counts=True) - counter = {kk: 1 for kk in key[count > 1]} - results = [] - for coeff in self._pair_style: - if coeff in counter and self.is_hybrid: - results.append(str(counter[coeff])) - counter[coeff] += 1 - else: - results.append("") - return results - - @property - def pair_style(self): - """pair_style to be output only in hybrid""" - if self.is_hybrid: - return self._pair_style - else: - return len(self._pair_style) * [""] - - @property - def results(self): - """pair_coeff lines to be used in pyiron df""" - return [ - " ".join((" ".join(("pair_coeff",) + c)).split()) + "\n" - for c in zip( - self.interacting_species, - self.pair_style, - self.counter, - self.pair_coeff, - ) - ] - - @property - def interacting_species(self) -> list: - """ - Species in LAMMPS notation (i.e. in numbers instead of chemical - symbols) - """ - s_dict = dict( - zip(self._species, (np.arange(len(self._species)) + 1).astype(str)) - ) - s_dict.update({"*": "*"}) - return [ - " ".join([s_dict[cc] for cc in c]) for c in self._interacting_species - ] - - @property - def pair_coeff(self) -> list: - """ - Args for pair_coeff. Elements defined in EAM files are - complemented with the ones defined in other potentials in the - case of hybrid (filled with NULL) - """ - if not self.is_hybrid: - return self._pair_coeff - results = [] - for pc, ps in zip(self._pair_coeff, self._preset_species): - if len(ps) > 0 and "eam" in pc: - s = " ".join(ps + (len(self._species) - len(ps)) * ["NULL"]) - pc = pc.replace(" ".join(ps), s) - results.append(pc) - return results - - @property - def pair_coeff(self) -> list: - """LAMMPS pair_coeff""" - - return self._PairCoeff( - is_hybrid="hybrid" in self.pair_style, - pair_style=self.df.pair_style, - interacting_species=self.df.interacting_species, - pair_coeff=self.df.pair_coeff, - species=self.species, - preset_species=self.df.preset_species, - ).results - - @property - def pyiron_df(self): - """df used in pyiron potential""" - return pd.DataFrame( - { - "Config": [[self.pair_style] + self.pair_coeff], - "Filename": [self.filename], - "Model": [self.model], - "Name": [self.potential_name], - "Species": [self.species], - "Citations": [self.citations], - } - ) - - def __repr__(self): - return self.df.__repr__() - - def _repr_html_(self): - return self.df._repr_html_() - - def set_df(self, df): - for key in [ - "pair_style", - "interacting_species", - "pair_coeff", - "preset_species", - ]: - if key not in df: - raise ValueError(f"{key} missing") - self._df = df - - @property - def df(self): - """DataFrame containing all info for each pairwise interactions""" - return self._df - - def get_df(self, default_scale=None): - if default_scale is None or "scale" in self.df: - return self.df.copy() - df = self.df.copy() - df["scale"] = default_scale - return df - - def __mul__(self, scale_or_potential): - if isinstance(scale_or_potential, LammpsPotentials): - if self.is_scaled or scale_or_potential.is_scaled: - raise ValueError("You cannot mix hybrid types") - new_pot = LammpsPotentials() - new_pot.set_df( - pd.concat( - (self.get_df(), scale_or_potential.get_df()), ignore_index=True - ) - ) - return new_pot - if self.is_scaled: - raise NotImplementedError("Currently you cannot scale twice") - new_pot = self.copy() - new_pot.df["scale"] = scale_or_potential - return new_pot - - __rmul__ = __mul__ - - def __add__(self, potential): - new_pot = LammpsPotentials() - new_pot.set_df( - pd.concat( - (self.get_df(default_scale=1), potential.get_df(default_scale=1)), - ignore_index=True, - ) - ) - return new_pot - - -class Library(LammpsPotentials): - """ - Potential class to choose a file based potential from an existing library - (e.g. EAM). - You can either specify the chemical species and/or the name of the - potential. - - Example I: Via chemical species - - >>> eam = Library("Al") - - Example II: Via potential name - - >>> eam = Library(name="1995--Angelo-J-E--Ni-Al-H--LAMMPS--ipr1") - - If the variable `eam` is used without specifying the potential name (i.e. - in Example I), the first potential in the database corresponding with the - specified chemical species will be selected. In order to see the list of - potentials, you can also execute - - >>> eam = Library("Al") - >>> eam.list_potentials() # See list of potential names - >>> eam.view_potentials() # See potential names and metadata - - """ - - def __init__(self, *chemical_elements, name=None): - """ - Args: - chemical_elements (str): chemical elements/species - name (str): potential name in the database - """ - if name is not None: - self._df_candidates = LammpsPotentialFile().find_by_name(name) - else: - self._df_candidates = LammpsPotentialFile().find(list(chemical_elements)) - - @staticmethod - def _get_pair_style(config): - if any(["hybrid" in c for c in config]): - return [c.split()[3] for c in config if "pair_coeff" in c] - for c in config: - if "pair_style" in c: - return [" ".join(c.replace("\n", "").split()[1:])] * sum( - ["pair_coeff" in c for c in config] - ) - raise ValueError( - f""" - pair_style could not determined: {config}. - - The reason why you are seeing this error is most likely because - the potential you chose had a corrupt config. It is - supposed to have at least one item which starts with "pair_style". - If you are using the standard pyiron database, feel free to - submit an issue on {issue_page} - Typically you can get a reply within 24h. - """ - ) - - @staticmethod - def _get_pair_coeff(config): - try: - if any(["hybrid" in c for c in config]): - return [" ".join(c.split()[4:]) for c in config if "pair_coeff" in c] - return [" ".join(c.split()[3:]) for c in config if "pair_coeff" in c] - except IndexError: - raise AssertionError( - f"{config} does not follow the format 'pair_coeff element_1 element_2 args'" - ) - - @staticmethod - def _get_interacting_species(config, species): - def _convert(c, s): - if c == "*": - return c - return s[int(c) - 1] - - return [ - [_convert(cc, species) for cc in c.split()[1:3]] - for c in config - if c.startswith("pair_coeff") - ] - - @staticmethod - def _get_scale(config): - for c in config: - if not c.startswith("pair_style"): - continue - if "hybrid/overlay" in c: - return 1 - elif "hybrid/scaled" in c: - raise NotImplementedError( - "Too much work for something inexistent in pyiron database for now" - ) - return - - def list_potentials(self): - return self._df_candidates.Name - - def view_potentials(self): - return self._df_candidates - - @property - def df(self): - if self._df is None: - df = self._df_candidates.iloc[0] - if len(self._df_candidates) > 1: - warnings.warn( - f"Potential not uniquely specified - use default {df.Name}" - ) - self._initialize_df( - pair_style=self._get_pair_style(df.Config), - interacting_species=self._get_interacting_species( - df.Config, df.Species - ), - pair_coeff=self._get_pair_coeff(df.Config), - preset_species=[df.Species], - model=df.Model, - citations=df.Citations, - filename=[df.Filename], - potential_name=df.Name, - scale=self._get_scale(df.Config), - ) - return self._df - - -def check_cutoff(f): - def wrapper(*args, **kwargs): - if "cutoff" not in kwargs or kwargs["cutoff"] == 0: - raise ValueError( - f""" - It is not possible to set cutoff=0 for parameter-based - potentials. If you think this should be possible, you have the - following options: - - - Open an issue on our GitHub page: {issue_page} - - - Write your own potential in pyiron format. Here's how: - - {doc_pyiron_df} - """ - ) - return f(*args, **kwargs) - - return wrapper - - -class Morse(LammpsPotentials): - """ - Morse potential defined by: - - E = D_0*[exp(-2*alpha*(r-r_0))-2*exp(-alpha*(r-r_0))] - """ - - @check_cutoff - def __init__(self, *chemical_elements, D_0, alpha, r_0, cutoff, pair_style="morse"): - """ - Args: - chemical_elements (str): Chemical elements - D_0 (float): parameter (s. eq. above) - alpha (float): parameter (s. eq. above) - r_0 (float): parameter (s. eq. above) - cutoff (float): cutoff length - pair_style (str): pair_style name (default: "morse") - - Example: - - >>> morse = Morse("Al", "Ni", D_0=1, alpha=0.5, r_0=2, cutoff=6) - """ - self._initialize_df( - pair_style=[pair_style], - interacting_species=[self._harmonize_species(chemical_elements)], - pair_coeff=[" ".join([str(cc) for cc in [D_0, alpha, r_0, cutoff]])], - cutoff=cutoff, - ) - - -Morse.__doc__ += general_doc - - -class CustomPotential(LammpsPotentials): - """ - Custom potential class to define LAMMPS potential not implemented in - pyiron - """ - - @check_cutoff - def __init__(self, pair_style, *chemical_elements, cutoff, **kwargs): - """ - Args: - pair_style (str): pair_style name (default: "morse") - chemical_elements (str): Chemical elements - cutoff (float): cutoff length - - Example: - - >>> custom_pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) - - Important: the order of parameters is conserved in the LAMMPS input - (except for `cutoff`, which is always the last argument). - """ - self._initialize_df( - pair_style=[pair_style], - interacting_species=[self._harmonize_species(chemical_elements)], - pair_coeff=[" ".join([str(cc) for cc in kwargs.values()]) + f" {cutoff}"], - cutoff=cutoff, - ) - - -CustomPotential.__doc__ += general_doc From 8747da9e8042b5a688e985d413f79e30c8982489 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Tue, 23 May 2023 11:25:55 -0600 Subject: [PATCH 57/74] Update environment.yml --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 75be1200f..ab601f77e 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -14,7 +14,7 @@ dependencies: - pandas =2.0.1 - phonopy =2.19.1 - pint =0.21 -- pyiron_base =0.5.38 +- pyiron_base =0.5.39 - pymatgen =2023.5.10 - pyscal =2.10.18 - scikit-learn =1.2.2 From 5c0f8a9637f7f5a3cedd5138809db6b6544fe153 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Tue, 23 May 2023 11:26:23 -0600 Subject: [PATCH 58/74] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c7a517682..2c0cbe388 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ 'pandas==2.0.1', 'phonopy==2.19.1', 'pint==0.21', - 'pyiron_base==0.5.38', + 'pyiron_base==0.5.39', 'pymatgen==2023.5.10', 'scipy==1.10.1', 'seekpath==2.1.0', From 98a1f735cd959b9be2af77162e4d741681bd8818 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Tue, 23 May 2023 11:44:11 -0600 Subject: [PATCH 59/74] As plot3d() is now part of the structure toolkit, there is no longer a need for a separate visualize class --- .../atomistics/structure/_visualize.py | 122 ------------------ .../atomistics/structure/atoms.py | 66 ++++++++-- 2 files changed, 56 insertions(+), 132 deletions(-) delete mode 100644 pyiron_atomistics/atomistics/structure/_visualize.py diff --git a/pyiron_atomistics/atomistics/structure/_visualize.py b/pyiron_atomistics/atomistics/structure/_visualize.py deleted file mode 100644 index dae679a4f..000000000 --- a/pyiron_atomistics/atomistics/structure/_visualize.py +++ /dev/null @@ -1,122 +0,0 @@ -# coding: utf-8 -# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department -# Distributed under the terms of "New BSD License", see the LICENSE file. - -import numpy as np -from structuretoolkit.visualize import plot3d - -__author__ = "Joerg Neugebauer, Sudarsan Surendralal" -__copyright__ = ( - "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - " - "Computational Materials Design (CM) Department" -) -__version__ = "1.0" -__maintainer__ = "Sudarsan Surendralal" -__email__ = "surendralal@mpie.de" -__status__ = "production" -__date__ = "Sep 1, 2017" - - -class Visualize: - def __init__(self, atoms): - self._ref_atoms = atoms - - def plot3d( - self, - mode="NGLview", - show_cell=True, - show_axes=True, - camera="orthographic", - spacefill=True, - particle_size=1.0, - select_atoms=None, - background="white", - color_scheme=None, - colors=None, - scalar_field=None, - scalar_start=None, - scalar_end=None, - scalar_cmap=None, - vector_field=None, - vector_color=None, - magnetic_moments=False, - view_plane=np.array([0, 0, 1]), - distance_from_camera=1.0, - opacity=1.0, - ): - """ - Plot3d relies on NGLView or plotly to visualize atomic structures. Here, we construct a string in the "protein database" - - The final widget is returned. If it is assigned to a variable, the visualization is suppressed until that - variable is evaluated, and in the meantime more NGL operations can be applied to it to modify the visualization. - - Args: - mode (str): `NGLView`, `plotly` or `ase` - show_cell (bool): Whether or not to show the frame. (Default is True.) - show_axes (bool): Whether or not to show xyz axes. (Default is True.) - camera (str): 'perspective' or 'orthographic'. (Default is 'perspective'.) - spacefill (bool): Whether to use a space-filling or ball-and-stick representation. (Default is True, use - space-filling atoms.) - particle_size (float): Size of the particles. (Default is 1.) - select_atoms (numpy.ndarray): Indices of atoms to show, either as integers or a boolean array mask. - (Default is None, show all atoms.) - background (str): Background color. (Default is 'white'.) - color_scheme (str): NGLView color scheme to use. (Default is None, color by element.) - colors (numpy.ndarray): A per-atom array of HTML color names or hex color codes to use for atomic colors. - (Default is None, use coloring scheme.) - scalar_field (numpy.ndarray): Color each atom according to the array value (Default is None, use coloring - scheme.) - scalar_start (float): The scalar value to be mapped onto the low end of the color map (lower values are - clipped). (Default is None, use the minimum value in `scalar_field`.) - scalar_end (float): The scalar value to be mapped onto the high end of the color map (higher values are - clipped). (Default is None, use the maximum value in `scalar_field`.) - scalar_cmap (matplotlib.cm): The colormap to use. (Default is None, giving a blue-red divergent map.) - vector_field (numpy.ndarray): Add vectors (3 values) originating at each atom. (Default is None, no - vectors.) - vector_color (numpy.ndarray): Colors for the vectors (only available with vector_field). (Default is None, - vectors are colored by their direction.) - magnetic_moments (bool): Plot magnetic moments as 'scalar_field' or 'vector_field'. - view_plane (numpy.ndarray): A Nx3-array (N = 1,2,3); the first 3d-component of the array specifies - which plane of the system to view (for example, [1, 0, 0], [1, 1, 0] or the [1, 1, 1] planes), the - second 3d-component (if specified, otherwise [1, 0, 0]) gives the horizontal direction, and the third - component (if specified) is the vertical component, which is ignored and calculated internally. The - orthonormality of the orientation is internally ensured, and therefore is not required in the function - call. (Default is np.array([0, 0, 1]), which is view normal to the x-y plane.) - distance_from_camera (float): Distance of the camera from the structure. Higher = farther away. - (Default is 14, which also seems to be the NGLView default value.) - - Possible NGLView color schemes: - " ", "picking", "random", "uniform", "atomindex", "residueindex", - "chainindex", "modelindex", "sstruc", "element", "resname", "bfactor", - "hydrophobicity", "value", "volume", "occupancy" - - Returns: - (nglview.NGLWidget): The NGLView widget itself, which can be operated on further or viewed as-is. - - Warnings: - * Many features only work with space-filling atoms (e.g. coloring by a scalar field). - * The colour interpretation of some hex codes is weird, e.g. 'green'. - """ - return plot3d( - structure=self._ref_atoms, - mode=mode, - show_cell=show_cell, - show_axes=show_axes, - camera=camera, - spacefill=spacefill, - particle_size=particle_size, - select_atoms=select_atoms, - background=background, - color_scheme=color_scheme, - colors=colors, - scalar_field=scalar_field, - scalar_start=scalar_start, - scalar_end=scalar_end, - scalar_cmap=scalar_cmap, - vector_field=vector_field, - vector_color=vector_color, - magnetic_moments=magnetic_moments, - view_plane=view_plane, - distance_from_camera=distance_from_camera, - opacity=opacity, - ) diff --git a/pyiron_atomistics/atomistics/structure/atoms.py b/pyiron_atomistics/atomistics/structure/atoms.py index 73a82e35d..7f72cf499 100644 --- a/pyiron_atomistics/atomistics/structure/atoms.py +++ b/pyiron_atomistics/atomistics/structure/atoms.py @@ -20,12 +20,12 @@ find_mic, ) from structuretoolkit.common import center_coordinates_in_unit_cell +from structuretoolkit.visualize import plot3d from pyiron_atomistics.atomistics.structure.atom import ( Atom, ase_to_pyiron as ase_to_pyiron_atom, ) from pyiron_atomistics.atomistics.structure.pyscal import pyiron_to_pyscal_system -from pyiron_atomistics.atomistics.structure._visualize import Visualize from pyiron_atomistics.atomistics.structure.analyse import Analyse from pyiron_atomistics.atomistics.structure.sparse_list import SparseArray, SparseList from pyiron_atomistics.atomistics.structure.periodic_table import ( @@ -216,7 +216,6 @@ def __init__( self.dimension = len(self.positions[0]) else: self.dimension = 0 - self._visualize = Visualize(self) self._analyse = Analyse(self) # Velocities were not handled at all during file writing self._velocities = None @@ -254,10 +253,6 @@ def spins(self, val): if val is not None: self.set_array("initial_magmoms", np.asarray(val)) - @property - def visualize(self): - return self._visualize - @property def analyse(self): return self._analyse @@ -1213,7 +1208,61 @@ def plot3d( distance_from_camera=1.0, opacity=1.0, ): - return self.visualize.plot3d( + """ + Plot3d relies on NGLView or plotly to visualize atomic structures. Here, we construct a string in the "protein database" + + The final widget is returned. If it is assigned to a variable, the visualization is suppressed until that + variable is evaluated, and in the meantime more NGL operations can be applied to it to modify the visualization. + + Args: + mode (str): `NGLView`, `plotly` or `ase` + show_cell (bool): Whether or not to show the frame. (Default is True.) + show_axes (bool): Whether or not to show xyz axes. (Default is True.) + camera (str): 'perspective' or 'orthographic'. (Default is 'perspective'.) + spacefill (bool): Whether to use a space-filling or ball-and-stick representation. (Default is True, use + space-filling atoms.) + particle_size (float): Size of the particles. (Default is 1.) + select_atoms (numpy.ndarray): Indices of atoms to show, either as integers or a boolean array mask. + (Default is None, show all atoms.) + background (str): Background color. (Default is 'white'.) + color_scheme (str): NGLView color scheme to use. (Default is None, color by element.) + colors (numpy.ndarray): A per-atom array of HTML color names or hex color codes to use for atomic colors. + (Default is None, use coloring scheme.) + scalar_field (numpy.ndarray): Color each atom according to the array value (Default is None, use coloring + scheme.) + scalar_start (float): The scalar value to be mapped onto the low end of the color map (lower values are + clipped). (Default is None, use the minimum value in `scalar_field`.) + scalar_end (float): The scalar value to be mapped onto the high end of the color map (higher values are + clipped). (Default is None, use the maximum value in `scalar_field`.) + scalar_cmap (matplotlib.cm): The colormap to use. (Default is None, giving a blue-red divergent map.) + vector_field (numpy.ndarray): Add vectors (3 values) originating at each atom. (Default is None, no + vectors.) + vector_color (numpy.ndarray): Colors for the vectors (only available with vector_field). (Default is None, + vectors are colored by their direction.) + magnetic_moments (bool): Plot magnetic moments as 'scalar_field' or 'vector_field'. + view_plane (numpy.ndarray): A Nx3-array (N = 1,2,3); the first 3d-component of the array specifies + which plane of the system to view (for example, [1, 0, 0], [1, 1, 0] or the [1, 1, 1] planes), the + second 3d-component (if specified, otherwise [1, 0, 0]) gives the horizontal direction, and the third + component (if specified) is the vertical component, which is ignored and calculated internally. The + orthonormality of the orientation is internally ensured, and therefore is not required in the function + call. (Default is np.array([0, 0, 1]), which is view normal to the x-y plane.) + distance_from_camera (float): Distance of the camera from the structure. Higher = farther away. + (Default is 14, which also seems to be the NGLView default value.) + + Possible NGLView color schemes: + " ", "picking", "random", "uniform", "atomindex", "residueindex", + "chainindex", "modelindex", "sstruc", "element", "resname", "bfactor", + "hydrophobicity", "value", "volume", "occupancy" + + Returns: + (nglview.NGLWidget): The NGLView widget itself, which can be operated on further or viewed as-is. + + Warnings: + * Many features only work with space-filling atoms (e.g. coloring by a scalar field). + * The colour interpretation of some hex codes is weird, e.g. 'green'. + """ + return plot3d( + structure=pyiron_to_ase(self), mode=mode, show_cell=show_cell, show_axes=show_axes, @@ -1236,8 +1285,6 @@ def plot3d( opacity=opacity, ) - plot3d.__doc__ = Visualize.plot3d.__doc__ - def pos_xyz(self): """ @@ -2038,7 +2085,6 @@ def __copy__(self): for key, val in self.__dict__.items(): if key not in ase_keys: atoms_new.__dict__[key] = copy(val) - atoms_new._visualize = Visualize(atoms_new) atoms_new._analyse = Analyse(atoms_new) return atoms_new From dcb215c28230dfa2cb9cdcb729c9a2fc404a3376 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Tue, 23 May 2023 12:16:30 -0600 Subject: [PATCH 60/74] Fix for spins --- pyiron_atomistics/atomistics/structure/atoms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_atomistics/atomistics/structure/atoms.py b/pyiron_atomistics/atomistics/structure/atoms.py index 73a82e35d..ffee2a50b 100644 --- a/pyiron_atomistics/atomistics/structure/atoms.py +++ b/pyiron_atomistics/atomistics/structure/atoms.py @@ -2468,7 +2468,7 @@ def get_initial_magnetic_moments(self): isinstance(spin_lst, (list, np.ndarray)) and isinstance(spin_lst[0], str) ) - ) and "[" in list(set(spin_lst))[0]: + ) and list(set(spin_lst))[0] is not None and "[" in list(set(spin_lst))[0]: return np.array( [ [ @@ -2484,7 +2484,7 @@ def get_initial_magnetic_moments(self): ] ) elif isinstance(spin_lst, (list, np.ndarray)): - return np.array(spin_lst) + return np.array([float(s) if s else 0.0 for s in spin_lst]) else: return np.array([float(spin) if spin else 0.0 for spin in spin_lst]) else: From 0e165bb1b83dbead9d960a61f2894745d3e77830 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Tue, 23 May 2023 18:20:34 +0000 Subject: [PATCH 61/74] Format black --- pyiron_atomistics/atomistics/structure/atoms.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pyiron_atomistics/atomistics/structure/atoms.py b/pyiron_atomistics/atomistics/structure/atoms.py index ffee2a50b..dd6abfa9f 100644 --- a/pyiron_atomistics/atomistics/structure/atoms.py +++ b/pyiron_atomistics/atomistics/structure/atoms.py @@ -2463,12 +2463,16 @@ def get_initial_magnetic_moments(self): ] if any(spin_lst): if ( - isinstance(spin_lst, str) - or ( - isinstance(spin_lst, (list, np.ndarray)) - and isinstance(spin_lst[0], str) + ( + isinstance(spin_lst, str) + or ( + isinstance(spin_lst, (list, np.ndarray)) + and isinstance(spin_lst[0], str) + ) ) - ) and list(set(spin_lst))[0] is not None and "[" in list(set(spin_lst))[0]: + and list(set(spin_lst))[0] is not None + and "[" in list(set(spin_lst))[0] + ): return np.array( [ [ From 0775367035b2a11b345f55d25b942b6b743bc96b Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 25 May 2023 14:18:14 +0200 Subject: [PATCH 62/74] Fix get_time Previous implementation never actually found POTIM, but would iterate over the whole file anyway. --- pyiron_atomistics/vasp/outcar.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyiron_atomistics/vasp/outcar.py b/pyiron_atomistics/vasp/outcar.py index a6c77d32f..73a6c9872 100644 --- a/pyiron_atomistics/vasp/outcar.py +++ b/pyiron_atomistics/vasp/outcar.py @@ -690,15 +690,14 @@ def get_time(self, filename="OUTCAR", lines=None): """ potim_trigger = "POTIM =" - read_potim = True potim = 1.0 lines = _get_lines_from_file(filename=filename, lines=lines) for i, line in enumerate(lines): - line = line.strip() - if read_potim is None: - if potim_trigger in line: - line = _clean_line(line) - potim = float(line.split(potim_trigger)[0]) + if potim_trigger in line: + line = line.strip() + line = _clean_line(line) + potim = float(line.split(potim_trigger)[0]) + break return potim * self.get_steps(filename) @staticmethod From e6de7ab8d2eedc08c427964cced34de6103b9190 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 25 May 2023 15:00:45 +0200 Subject: [PATCH 63/74] Fix get_time test; same issue as get_steps --- tests/vasp/test_outcar.py | 92 +++++++++++---------------------------- 1 file changed, 26 insertions(+), 66 deletions(-) diff --git a/tests/vasp/test_outcar.py b/tests/vasp/test_outcar.py index cbbf947f3..260a83560 100644 --- a/tests/vasp/test_outcar.py +++ b/tests/vasp/test_outcar.py @@ -830,76 +830,36 @@ def naive_parse(filename): return steps, nblock for filename in self.file_list: - output = self.outcar_parser.get_steps(filename) - self.assertIsInstance(output, np.ndarray, "steps has to be an array!") - nblock, steps = naive_parse(filename) - total = steps * nblock - self.assertTrue( - np.array_equal(output, np.arange(0, total, nblock)), - "Parsed output steps do not match numpy.arange(0, {total}, {nblock})!" - ) + with self.subTest(filename=filename): + output = self.outcar_parser.get_steps(filename) + self.assertIsInstance(output, np.ndarray, "steps has to be an array!") + steps, nblock = naive_parse(filename) + total = steps * nblock + self.assertEqual( + output, np.arange(0, total, nblock), + f"Parsed output steps do not match numpy.arange(0, {total}, {nblock})!" + ) def test_get_time(self): + def naive_parse(filename): + with open(filename) as f: + for l in f: + if "POTIM" in l: + return float( + l.split("=")[1].strip().split()[0] + ) + return 1.0 for filename in self.file_list: - output = self.outcar_parser.get_time(filename) - self.assertIsInstance(output, np.ndarray) - if int(filename.split("/OUTCAR_")[-1]) in [1, 2, 3, 4, 5, 6]: - time = np.array( - [ - 0.0, - 0.02040816, - 0.04081633, - 0.06122449, - 0.08163265, - 0.10204082, - 0.12244898, - 0.14285714, - 0.16326531, - 0.18367347, - 0.20408163, - 0.2244898, - 0.24489796, - 0.26530612, - 0.28571429, - 0.30612245, - 0.32653061, - 0.34693878, - 0.36734694, - 0.3877551, - 0.40816327, - 0.42857143, - 0.44897959, - 0.46938776, - 0.48979592, - 0.51020408, - 0.53061224, - 0.55102041, - 0.57142857, - 0.59183673, - 0.6122449, - 0.63265306, - 0.65306122, - 0.67346939, - 0.69387755, - 0.71428571, - 0.73469388, - 0.75510204, - 0.7755102, - 0.79591837, - 0.81632653, - 0.83673469, - 0.85714286, - 0.87755102, - 0.89795918, - 0.91836735, - 0.93877551, - 0.95918367, - 0.97959184, - 1.0, - ] - ) - self.assertEqual(time.__str__(), output.__str__()) + with self.subTest(filename=filename): + potim = naive_parse(filename) + time = self.outcar_parser.get_time(filename) + step = self.outcar_parser.get_steps(filename) + for t, s in zip(time, step): + self.assertEqual( + t, s * potim, + f"Time {t} is not equal to steps times POTIM {potim * s}!" + ) def test_get_fermi_level(self): for filename in self.file_list: From 65765fcfbb1871dcaf96e7c1dd9aa0213efa33b6 Mon Sep 17 00:00:00 2001 From: Sam Dareska <37879103+samwaseda@users.noreply.github.com> Date: Thu, 25 May 2023 15:28:35 +0200 Subject: [PATCH 64/74] [minor] Remove superfluous cell multiplication --- pyiron_atomistics/lammps/output.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyiron_atomistics/lammps/output.py b/pyiron_atomistics/lammps/output.py index 57ccf5ade..5bac69f6c 100644 --- a/pyiron_atomistics/lammps/output.py +++ b/pyiron_atomistics/lammps/output.py @@ -339,10 +339,7 @@ def collect_dump_file(file_name, prism, structure, potential_elements): axis=1, ) dump.mean_unwrapped_positions.append( - np.matmul( - np.matmul(pos, lammps_cell), - rotation_lammps2orig, - ) + np.matmul(pos, rotation_lammps2orig) ) for k in columns: if k.startswith("c_"): From 1a0652561649b7e5e7f0fa68683c95cf400e8511 Mon Sep 17 00:00:00 2001 From: Marvin Poul Date: Thu, 25 May 2023 15:01:01 +0200 Subject: [PATCH 65/74] fix typos --- pyiron_atomistics/vasp/outcar.py | 8 ++++---- tests/vasp/test_outcar.py | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pyiron_atomistics/vasp/outcar.py b/pyiron_atomistics/vasp/outcar.py index 73a6c9872..be1844016 100644 --- a/pyiron_atomistics/vasp/outcar.py +++ b/pyiron_atomistics/vasp/outcar.py @@ -666,11 +666,11 @@ def get_steps(filename="OUTCAR", lines=None): steps = 0 nblock = None lines = _get_lines_from_file(filename=filename, lines=lines) - for i, line in enumerate(lines): - line = line.strip() + for line in lines: if trigger in line: steps += 1 - if nblock is not None: + if nblock is None and "NBLOCK" in line: + line = line.strip() line = _clean_line(line) nblock = int(nblock_regex.findall(line)[0]) if nblock is None: @@ -696,7 +696,7 @@ def get_time(self, filename="OUTCAR", lines=None): if potim_trigger in line: line = line.strip() line = _clean_line(line) - potim = float(line.split(potim_trigger)[0]) + potim = float(line.split(potim_trigger)[1].strip().split()[0]) break return potim * self.get_steps(filename) diff --git a/tests/vasp/test_outcar.py b/tests/vasp/test_outcar.py index 260a83560..f275d0118 100644 --- a/tests/vasp/test_outcar.py +++ b/tests/vasp/test_outcar.py @@ -566,6 +566,7 @@ def test_get_memory_used(self): 8: 0.0, 9: 787952.0, 10: 1036056.0, + 11: 39113.0 } for filename in self.file_list: self.assertEqual( @@ -822,7 +823,7 @@ def test_get_steps(self): def naive_parse(filename): with open(filename) as f: nblock = 1 - steps = 1 + steps = 0 for l in f: if "NBLOCK" in l: nblock = int(l.split(";")[0].split("=")[1]) @@ -1173,7 +1174,7 @@ def test_get_kpoints_irreducible_reciprocal(self): self.assertEqual(output_all.__str__(), output.__str__()) def test_get_nelect(self): - n_elect_list = [40.0, 16.0, 16.0, 16.0, 16.0, 16.0, 224.0, 358.0, 8, 1] + n_elect_list = [40.0, 16.0, 16.0, 16.0, 16.0, 16.0, 224.0, 358.0, 8, 1, 324.0] for filename in self.file_list: i = int(filename.split("_")[-1]) - 1 self.assertEqual(n_elect_list[i], self.outcar_parser.get_nelect(filename)) From 643064d7caf33adbc109f6b6034570719a21f9b6 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 25 May 2023 18:44:38 -0600 Subject: [PATCH 66/74] Correct spelling issues --- pyiron_atomistics/project.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyiron_atomistics/project.py b/pyiron_atomistics/project.py index fe29c20cb..80db0b5fa 100644 --- a/pyiron_atomistics/project.py +++ b/pyiron_atomistics/project.py @@ -450,7 +450,7 @@ def create_pipeline(self, job, step_lst, delete_existing_job=False): # Deprecated methods - @deprecate("Use create.struture.bulk instead") + @deprecate("Use create.structure.bulk instead") def create_ase_bulk( self, name, @@ -491,7 +491,7 @@ def create_ase_bulk( cubic=cubic, ) - @deprecate("Use create.struture.* methods instead") + @deprecate("Use create.structure.* methods instead") def create_structure(self, element, bravais_basis, lattice_constant): """ Create a crystal structure using pyiron's native crystal structure generator @@ -515,7 +515,7 @@ def create_structure(self, element, bravais_basis, lattice_constant): lattice_constant=lattice_constant, ) - @deprecate("Use create.struture.surface instead") + @deprecate("Use create.structure.surface instead") def create_surface( self, element, @@ -559,7 +559,7 @@ def create_surface( **kwargs ) - @deprecate("Use create.struture.atoms instead") + @deprecate("Use create.structure.atoms instead") def create_atoms( self, symbols=None, @@ -638,7 +638,7 @@ def create_atoms( **qwargs ) - @deprecate("Use create.struture.element instead") + @deprecate("Use create.structure.element instead") def create_element( self, parent_element, new_element_name=None, spin=None, potential_file=None ): From 74fadf2283fe2e5b6513613951f10ca2e137d9bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 08:57:39 +0000 Subject: [PATCH 67/74] Bump pandas from 2.0.1 to 2.0.2 Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.0.1 to 2.0.2. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Commits](https://github.com/pandas-dev/pandas/compare/v2.0.1...v2.0.2) --- updated-dependencies: - dependency-name: pandas dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2c0cbe388..b7bbf3500 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ 'mendeleev==0.13.1', 'mp-api==0.33.3', 'numpy==1.24.3', - 'pandas==2.0.1', + 'pandas==2.0.2', 'phonopy==2.19.1', 'pint==0.21', 'pyiron_base==0.5.39', From 78a0da58a5d56e425f155dcde0872508c39765e2 Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 29 May 2023 08:58:34 +0000 Subject: [PATCH 68/74] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index ab601f77e..a9d625b3c 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -11,7 +11,7 @@ dependencies: - mendeleev =0.13.1 - mp-api =0.33.3 - numpy =1.24.3 -- pandas =2.0.1 +- pandas =2.0.2 - phonopy =2.19.1 - pint =0.21 - pyiron_base =0.5.39 From cbf69293c911bb5623ffe73be36e7e617570e9ab Mon Sep 17 00:00:00 2001 From: samwaseda Date: Tue, 30 May 2023 06:42:52 +0000 Subject: [PATCH 69/74] forgot to remove tests --- tests/lammps/test_potentials.py | 167 -------------------------------- 1 file changed, 167 deletions(-) delete mode 100644 tests/lammps/test_potentials.py diff --git a/tests/lammps/test_potentials.py b/tests/lammps/test_potentials.py deleted file mode 100644 index 158f96ee1..000000000 --- a/tests/lammps/test_potentials.py +++ /dev/null @@ -1,167 +0,0 @@ -# coding: utf-8 -# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department -# Distributed under the terms of "New BSD License", see the LICENSE file. - -from pyiron_atomistics.lammps.potentials import Library, Morse, CustomPotential, LammpsPotentials -import unittest -import pandas as pd -import numpy as np - - -class TestPotentials(unittest.TestCase): - def test_harmonize_species(self): - pot = LammpsPotentials() - self.assertEqual(pot._harmonize_species(("Al",)), ["Al", "Al"]) - for i in [2, 3, 4]: - self.assertEqual(pot._harmonize_species(i * ("Al",)), i * ["Al"]) - self.assertRaises(ValueError, pot._harmonize_species, tuple()) - - def test_set_df(self): - pot = LammpsPotentials() - self.assertEqual(pot.df, None) - required_keys = [ - "pair_style", - "interacting_species", - "pair_coeff", - "preset_species", - ] - arg_dict = {k: [] for k in required_keys} - pot.set_df(pd.DataFrame(arg_dict)) - self.assertIsInstance(pot.df, pd.DataFrame) - for key in required_keys: - arg_dict = {k: [] for k in required_keys if k != key} - self.assertRaises(ValueError, pot.set_df, pd.DataFrame(arg_dict)) - - def test_initialize_df(self): - pot = LammpsPotentials() - pot._initialize_df( - pair_style=["some_potential"], - interacting_species=[["Al", "Al"]], - pair_coeff=["something"], - ) - self.assertIsInstance(pot.df, pd.DataFrame) - self.assertEqual(len(pot.df), 1) - self.assertEqual(pot.df.iloc[0].pair_style, "some_potential") - self.assertEqual(pot.df.iloc[0].interacting_species, ["Al", "Al"]) - self.assertEqual(pot.df.iloc[0].pair_coeff, "something") - self.assertEqual(pot.df.iloc[0].preset_species, []) - self.assertEqual(pot.df.iloc[0].cutoff, 0) - self.assertEqual(pot.df.iloc[0].model, "some_potential") - self.assertEqual(pot.df.iloc[0].citations, []) - self.assertEqual(pot.df.iloc[0].filename, "") - self.assertEqual(pot.df.iloc[0].potential_name, "some_potential") - with self.assertRaises(ValueError): - pot._initialize_df( - pair_style=["some_potential", "one_too_many"], - interacting_species=[["Al", "Al"]], - pair_coeff=["something"], - ) - - def test_custom_potential(self): - pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) - self.assertEqual(pot.df.iloc[0].pair_coeff, "0.5 1 3") - - def test_copy(self): - pot_1 = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) - pot_2 = pot_1.copy() - self.assertTrue(np.all(pot_1.df == pot_2.df)) - pot_2.df.cutoff = 1 - self.assertFalse(np.all(pot_1.df == pot_2.df)) - - def test_model(self): - pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) - self.assertEqual(pot.model, "lj/cut") - pot = LammpsPotentials() - pot._initialize_df( - pair_style=["a", "b"], - interacting_species=[["Al"], ["Ni"]], - pair_coeff=["one", "two"], - model=["first", "second"] - ) - self.assertEqual(pot.model, "first_and_second") - - def test_potential_name(self): - pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) - self.assertEqual(pot.potential_name, "lj/cut") - pot = LammpsPotentials() - pot._initialize_df( - pair_style=["a", "b"], - interacting_species=[["Al"], ["Ni"]], - pair_coeff=["one", "two"], - potential_name=["first", "second"] - ) - self.assertEqual(pot.potential_name, "first_and_second") - - def test_is_scaled(self): - pot = CustomPotential("lj/cut", "Al", "Ni", epsilon=0.5, sigma=1, cutoff=3) - self.assertFalse(pot.is_scaled) - - def test_unique(self): - pot = LammpsPotentials() - self.assertEqual(pot._unique([1, 0, 2, 1, 3]).tolist(), [1, 0, 2, 3]) - - def test_pair_style(self): - pot = LammpsPotentials() - pot._initialize_df( - pair_style=["a"], - interacting_species=[["Al"]], - pair_coeff=["one"], - potential_name=["first"] - ) - self.assertEqual(pot.pair_style, "pair_style a\n") - pot._initialize_df( - pair_style=["a", "b"], - interacting_species=[["Al"], ["Ni"]], - pair_coeff=["one", "two"], - potential_name=["first", "second"] - ) - self.assertEqual(pot.pair_style, "pair_style hybrid a b\n") - pot._initialize_df( - pair_style=["a", "b"], - interacting_species=[["Al"], ["Ni"]], - pair_coeff=["one", "two"], - potential_name=["first", "second"], - cutoff=[1, 1], - ) - self.assertEqual(pot.pair_style, "pair_style hybrid a 1 b 1\n") - pot._initialize_df( - pair_style=["a", "b"], - interacting_species=[["Al"], ["Ni"]], - pair_coeff=["one", "two"], - potential_name=["first", "second"], - scale=[1, 1], - cutoff=[1, 1], - ) - self.assertEqual(pot.pair_style, "pair_style hybrid/overlay a 1 b 1\n") - pot._initialize_df( - pair_style=["a", "b"], - interacting_species=[["Al"], ["Ni"]], - pair_coeff=["one", "two"], - potential_name=["first", "second"], - scale=[1, 0.5], - cutoff=[1, 1], - ) - self.assertEqual(pot.pair_style, "pair_style hybrid/scaled 1.0 a 1 0.5 b 1\n") - pot._initialize_df( - pair_style=["a", "a"], - interacting_species=[["Al"], ["Ni"]], - pair_coeff=["one", "two"], - potential_name=["first", "second"], - ) - self.assertEqual(pot.pair_style, "pair_style a\n") - - def test_PairCoeff(self): - pot = LammpsPotentials() - pc = pot._PairCoeff( - is_hybrid=False, - pair_style=["my_style"], - interacting_species=[["Al", "Fe"]], - pair_coeff=["some arguments"], - species=["Al", "Fe"], - preset_species=[], - ) - self.assertEqual(pc.counter, [""]) - - -if __name__ == "__main__": - unittest.main() From d583ad7dfaa4cf2811eb237da46b495a20ed374a Mon Sep 17 00:00:00 2001 From: Niklas Siemer <70580458+niklassiemer@users.noreply.github.com> Date: Fri, 2 Jun 2023 13:31:55 +0200 Subject: [PATCH 70/74] Fix test for spins --- tests/atomistics/structure/test_atoms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/atomistics/structure/test_atoms.py b/tests/atomistics/structure/test_atoms.py index bd56f78a1..58f78bc6a 100644 --- a/tests/atomistics/structure/test_atoms.py +++ b/tests/atomistics/structure/test_atoms.py @@ -1237,7 +1237,7 @@ def test_get_initial_magnetic_moments(self): cell=2.6 * np.eye(3), ) self.assertTrue( - np.array_equal(basis.get_initial_magnetic_moments(), ["0.5"] * 2) + np.array_equal(basis.get_initial_magnetic_moments(), [0.5] * 2) ) def test_occupy_lattice(self): From 5473e0907ce1beb38c8fbd4e0d59f899d6ddc609 Mon Sep 17 00:00:00 2001 From: Niklas Siemer <70580458+niklassiemer@users.noreply.github.com> Date: Fri, 2 Jun 2023 13:37:34 +0200 Subject: [PATCH 71/74] Give error msg --- tests/atomistics/structure/test_atoms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/atomistics/structure/test_atoms.py b/tests/atomistics/structure/test_atoms.py index 58f78bc6a..28342f094 100644 --- a/tests/atomistics/structure/test_atoms.py +++ b/tests/atomistics/structure/test_atoms.py @@ -1237,7 +1237,8 @@ def test_get_initial_magnetic_moments(self): cell=2.6 * np.eye(3), ) self.assertTrue( - np.array_equal(basis.get_initial_magnetic_moments(), [0.5] * 2) + np.array_equal(basis.get_initial_magnetic_moments(), [0.5] * 2), + msg=f"Expected basis.get_initial_magnetic_moments() to be equal to {[0.5] * 2} but got {basis.get_initial_magnetic_moments()}." ) def test_occupy_lattice(self): From 6e18ca675c7cf9b77a85671f0b0e3e7087d694dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 08:57:03 +0000 Subject: [PATCH 72/74] Bump pymatgen from 2023.5.10 to 2023.5.31 Bumps [pymatgen](https://github.com/materialsproject/pymatgen) from 2023.5.10 to 2023.5.31. - [Release notes](https://github.com/materialsproject/pymatgen/releases) - [Changelog](https://github.com/materialsproject/pymatgen/blob/master/CHANGES.rst) - [Commits](https://github.com/materialsproject/pymatgen/compare/v2023.5.10...v2023.05.31) --- updated-dependencies: - dependency-name: pymatgen dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b7bbf3500..a2adbb9b0 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ 'phonopy==2.19.1', 'pint==0.21', 'pyiron_base==0.5.39', - 'pymatgen==2023.5.10', + 'pymatgen==2023.5.31', 'scipy==1.10.1', 'seekpath==2.1.0', 'scikit-learn==1.2.2', From b993f2da4945ada74f047682197ad3e4dace994a Mon Sep 17 00:00:00 2001 From: pyiron-runner Date: Mon, 5 Jun 2023 08:57:28 +0000 Subject: [PATCH 73/74] [dependabot skip] Update environment --- .ci_support/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index a9d625b3c..12ab2721d 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -15,7 +15,7 @@ dependencies: - phonopy =2.19.1 - pint =0.21 - pyiron_base =0.5.39 -- pymatgen =2023.5.10 +- pymatgen =2023.5.31 - pyscal =2.10.18 - scikit-learn =1.2.2 - scipy =1.10.1 From e3e40cfc6d44cca779cf3ea95c9acb025db9f528 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 5 Jun 2023 11:33:02 -0600 Subject: [PATCH 74/74] Update atoms.py --- pyiron_atomistics/atomistics/structure/atoms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyiron_atomistics/atomistics/structure/atoms.py b/pyiron_atomistics/atomistics/structure/atoms.py index 24e2fa987..b863e121d 100644 --- a/pyiron_atomistics/atomistics/structure/atoms.py +++ b/pyiron_atomistics/atomistics/structure/atoms.py @@ -3312,14 +3312,14 @@ def pyiron_to_pymatgen(pyiron_obj): sel_dyn_list = pyiron_obj.selective_dynamics pyiron_obj_conv.selective_dynamics = [True, True, True] ase_obj = pyiron_to_ase(pyiron_obj_conv) - pymatgen_obj_conv = AseAtomsAdaptor().get_structure(atoms=ase_obj, cls=None) + pymatgen_obj_conv = AseAtomsAdaptor().get_structure(atoms=ase_obj) new_site_properties = pymatgen_obj_conv.site_properties new_site_properties["selective_dynamics"] = sel_dyn_list pymatgen_obj = pymatgen_obj_conv.copy(site_properties=new_site_properties) else: ase_obj = pyiron_to_ase(pyiron_obj_conv) _check_if_simple_atoms(atoms=ase_obj) - pymatgen_obj = AseAtomsAdaptor().get_structure(atoms=ase_obj, cls=None) + pymatgen_obj = AseAtomsAdaptor().get_structure(atoms=ase_obj) return pymatgen_obj