From e38d57ba3c6cfc0b4fa14c01fab9257fe21c546a Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Fri, 19 Jan 2024 11:36:42 +0100 Subject: [PATCH 1/8] clean helpers --- calphy/helpers.py | 167 ---------------------------------------------- calphy/phase.py | 29 -------- 2 files changed, 196 deletions(-) diff --git a/calphy/helpers.py b/calphy/helpers.py index 439a3d9..02ca20d 100644 --- a/calphy/helpers.py +++ b/calphy/helpers.py @@ -154,46 +154,10 @@ def set_potential(lmp, options, ghost_elements=0): return lmp - -def read_dump(lmp, file, species=1): - # Read atoms positions, velocities and box parameters. - lmp.command("lattice fcc 4.0") - lmp.command("region box block 0 2 0 2 0 2") - lmp.command("create_box %d box" % species) - lmp.command( - "read_dump %s 0 x y z vx vy vz scaled no box yes add keep" % file - ) - lmp.command("change_box all triclinic") - return lmp - - def read_data(lmp, file): lmp.command(f"read_data {file}") return lmp - -def convert_to_data_file(inputfile, outputfile, ghost_elements=0): - atoms = read(inputfile, format="lammps-dump-text") - write(outputfile, atoms, format="lammps-data") - - if ghost_elements > 0: - lines = [] - with open(outputfile, "r") as fin: - for line in fin: - raw = line.strip().split() - if (len(raw) == 3) and (raw[2] == "types"): - raw[0] = "%d" % ghost_elements - raw.append("\n") - rline = " ".join(raw) - lines.append(rline) - else: - lines.append(line) - - with open(outputfile, "w") as fout: - for line in lines: - fout.write(line) - - def get_structures(file, species, index=None): traj = Trajectory(file) if index is None: @@ -202,94 +166,6 @@ def get_structures(file, species, index=None): aseobjs = traj[index].to_ase(species=species) return aseobjs - -def set_hybrid_potential(lmp, options, eps, ghost_elements=0): - pc = options.pair_coeff[0] - pcraw = pc.split() - pcnew = " ".join( - [ - *pcraw[:2], - *[ - options._pair_style_names[0], - ], - *pcraw[2:], - ] - ) - - lmp.command( - "pair_style hybrid/overlay %s ufm 7.5" - % options._pair_style_with_options[0] - ) - lmp.command("pair_coeff %s" % pcnew) - lmp.command("pair_coeff * * ufm %f 1.5" % eps) - - lmp = set_mass(lmp, options, ghost_elements=ghost_elements) - - return lmp - - -def set_double_hybrid_potential(lmp, options, ghost_elements=0): - - pc1 = options.pair_coeff[0] - pcraw1 = pc1.split() - - pc2 = options.pair_coeff[1] - pcraw2 = pc2.split() - - if options.pair_style[0] == options.pair_style[1]: - pcnew1 = " ".join( - [ - *pcraw1[:2], - *[ - options._pair_style_names[0], - ], - "1", - *pcraw1[2:], - ] - ) - pcnew2 = " ".join( - [ - *pcraw2[:2], - *[ - options._pair_style_names[1], - ], - "2", - *pcraw2[2:], - ] - ) - else: - pcnew1 = " ".join( - [ - *pcraw1[:2], - *[ - options._pair_style_names[0], - ], - *pcraw1[2:], - ] - ) - pcnew2 = " ".join( - [ - *pcraw2[:2], - *[ - options._pair_style_names[1], - ], - *pcraw2[2:], - ] - ) - - lmp.command( - "pair_style hybrid/overlay %s %s" - % (options._pair_style_with_options[0], options._pair_style_with_options[1]) - ) - - lmp.command("pair_coeff %s" % pcnew1) - lmp.command("pair_coeff %s" % pcnew2) - - lmp = set_mass(lmp, options, ghost_elements=ghost_elements) - - return lmp - - def remap_box(lmp, x, y, z): lmp.command("run 0") lmp.command( @@ -347,49 +223,6 @@ def write_data(lmp, file): lmp.command(f"write_data {file}") return lmp - -def reset_timestep(conf, file="current.data", init_commands=None): - lmp = create_object( - cores=1, - directory=os.path.dirname(file), - timestep=0, - cmdargs=None, - init_commands=init_commands, - ) - lmp = read_data(lmp, file) - lmp = write_data(lmp, conf) - return lmp - - # with open(file, "r") as f: - # with open(conf, "w") as c: - # zero = False - # for l in f: - # if zero: - # c.write("0\n") - # zero = False - # continue - # elif l.startswith("ITEM: TIMESTEP"): - # zero = True - # c.write(l) - - # lmp = create_object( - # cores=1, - # directory = os.path.dirname(file), - # timestep=0, - # cmdargs=None, - # ) - # lmp.command("dump 2 all custom 1 %s id type mass x y z vx vy vz"%(file))+ - # lmp.command("reset_timestep 0") - # lmp.command("run 0") - # lmp.command("undump 2") - - -""" -NOrmal helper routines ---------------------------------------------------------------------- -""" - - def prepare_log(file, screen=False): logger = logging.getLogger(__name__) handler = logging.FileHandler(file) diff --git a/calphy/phase.py b/calphy/phase.py index 7b13ccd..648db88 100644 --- a/calphy/phase.py +++ b/calphy/phase.py @@ -669,35 +669,6 @@ def run_minimal_constrained_pressure_convergence(self, lmp): lmp.command("unfix 1") lmp.command("unfix 2") - def process_traj(self, filename, outfilename): - """ - Process the out trajectory after averaging cycle and - extract a configuration to run integration - - Will be phased out... - - Parameters - ---------- - None - - Returns - ------- - None - - """ - trajfile = os.path.join(self.simfolder, filename) - files = ptp.split_trajectory(trajfile) - conf = os.path.join(self.simfolder, outfilename) - - ph.reset_timestep(conf, os.path.join(self.simfolder, "current.data"), - init_commands=self.calc.md.init_commands, - script_mode=self.calc.script_mode) - - os.remove(trajfile) - for file in files: - os.remove(file) - - def submit_report(self, extra_dict=None): """ Submit final report containing results From 9f93dd5817c2ee94f2724a4d46ed61e2fcce6e12 Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Fri, 19 Jan 2024 11:38:21 +0100 Subject: [PATCH 2/8] clean input --- calphy/input.py | 32 +- calphy/inputbk.py | 862 ---------------------------------------------- 2 files changed, 1 insertion(+), 893 deletions(-) delete mode 100644 calphy/inputbk.py diff --git a/calphy/input.py b/calphy/input.py index d94fef0..e553ab3 100644 --- a/calphy/input.py +++ b/calphy/input.py @@ -58,10 +58,6 @@ def _check_equal(val): return False return True - -def to_list(v: Any) -> List[Any]: - return np.atleast_1d(v) - class CompositionScaling(BaseModel, title='Composition scaling input options'): _input_chemical_composition: PrivateAttr(default=None) output_chemical_composition: Annotated[dict, Field(default=None, required=False)] @@ -517,13 +513,6 @@ def load_job(filename): job = np.load(filename, allow_pickle=True).flatten()[0] return job -def check_dict(indict, key, retval=None): - if key in indict.items(): - return indict[key] - else: - return retval - - def read_inputfile(file): if not os.path.exists(file): raise FileNotFoundError(f'Input file {file} not found.') @@ -645,23 +634,4 @@ def _convert_legacy_inputfile(file, return_calcs=False): warnings.warn(f'Old style input file calphy < v2 found. Converted input in {outfile}. Please check!') with open(outfile, 'w') as fout: yaml.safe_dump(newdata, fout) - return outfile - - -def _to_str(val): - if np.isscalar(val): - return str(val) - else: - return [str(x) for x in val] - -def _to_int(val): - if np.isscalar(val): - return int(val) - else: - return [int(x) for x in val] - -def _to_float(val): - if np.isscalar(val): - return float(val) - else: - return [float(x) for x in val] \ No newline at end of file + return outfile \ No newline at end of file diff --git a/calphy/inputbk.py b/calphy/inputbk.py deleted file mode 100644 index e28e4f7..0000000 --- a/calphy/inputbk.py +++ /dev/null @@ -1,862 +0,0 @@ -""" -calphy: a Python library and command line interface for automated free -energy calculations. - -Copyright 2021 (c) Sarath Menon^1, Yury Lysogorskiy^2, Ralf Drautz^2 -^1: Max Planck Institut für Eisenforschung, Dusseldorf, Germany -^2: Ruhr-University Bochum, Bochum, Germany - -calphy is published and distributed under the Academic Software License v1.0 (ASL). -calphy is distributed in the hope that it will be useful for non-commercial academic research, -but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -calphy API is published and distributed under the BSD 3-Clause "New" or "Revised" License -See the LICENSE FILE for more details. - -More information about the program can be found in: -Menon, Sarath, Yury Lysogorskiy, Jutta Rogal, and Ralf Drautz. -“Automated Free Energy Calculation from Atomistic Simulations.” Physical Review Materials 5(10), 2021 -DOI: 10.1103/PhysRevMaterials.5.103801 - -For more information contact: -sarath.menon@ruhr-uni-bochum.de/yury.lysogorskiy@icams.rub.de -""" - -import os -import yaml -import warnings -import copy -import yaml -import itertools -import shutil -import numpy as np -import datetime - -def read_report(folder): - """ - Read the finished calculation report - """ - repfile = os.path.join(folder, "report.yaml") - if not os.path.exists(repfile): - raise FileNotFoundError(f"file {repfile} not found") - - with open(repfile, 'r') as fin: - data = yaml.safe_load(fin) - return data - -class InputTemplate: - def __init__(self): - pass - - def add_from_dict(self, indict, keys=None): - if keys is None: - for key, val in indict.items(): - setattr(self, key, val) - elif isinstance(keys, list): - for key, val in indict.items(): - if key in keys: - setattr(self, key, val) - - def from_dict(self, indict): - for key, val in indict.items(): - if isinstance(val, dict): - setattr(self, key, InputTemplate()) - getattr(self, key).from_dict(val) - else: - setattr(self, key, val) - - def to_dict(self): - tdict = copy.deepcopy(self.__dict__) - for key, val in tdict.items(): - if isinstance(val, InputTemplate): - tdict[key] = val.to_dict() - return tdict - - def check_and_convert_to_list(self, data, check_none=False): - """ - Check if the given item is a list, if not convert to a single item list - """ - if not isinstance(data, list): - data = [data] - - if check_none: - data = [None if x=="None" else x for x in data] - - return data - - @staticmethod - def convert_to_list(data, check_none=False): - """ - Check if the given item is a list, if not convert to a single item list - """ - if not isinstance(data, list): - data = [data] - - if check_none: - data = [None if x=="None" else x for x in data] - - return data - - def merge_dicts(self, dicts): - """ - Merge dicts from a given list - """ - merged_dict = {} - for d in dicts: - for key, val in d.items(): - merged_dict[key] = val - return merged_dict - -class CompositionScaling(InputTemplate): - def __init__(self): - self._input_chemical_composition = None - self._output_chemical_composition = None - self._restrictions = None - - @property - def input_chemical_composition(self): - return self._input_chemical_composition - - @input_chemical_composition.setter - def input_chemical_composition(self, val): - if isinstance(val, list): - val = self.merge_dicts(val) - self._input_chemical_composition = val - - @property - def output_chemical_composition(self): - return self._output_chemical_composition - - @output_chemical_composition.setter - def output_chemical_composition(self, val): - if isinstance(val, list): - val = self.merge_dicts(val) - self._output_chemical_composition = val - - @property - def restrictions(self): - return self._restrictions - - @restrictions.setter - def restrictions(self, val): - self._restrictions = self.check_and_convert_to_list(val) - - -class Calculation(InputTemplate): - def __init__(self): - super(InputTemplate, self).__init__() - self._element = None - self._n_elements = 0 - self._mass = 1.0 - self._mode = None - self._lattice = None - self._pressure = 0 - self._pressure_stop = 0 - self._pressure_input = None - self._temperature = None - self._temperature_stop = None - self._temperature_high = None - self._temperature_input = None - self._iso = False - self._fix_lattice = False - self._melting_cycle = True - self._pair_style = None - self._pair_style_options = None - self._pair_coeff = None - self._potential_file = None - self._fix_potential_path = True - self._reference_phase = None - self._lattice_constant = 0 - self._repeat = [1, 1, 1] - self._script_mode = False - self._lammps_executable = None - self._mpi_executable = None - self._npt = True - self._n_equilibration_steps = 25000 - self._n_switching_steps = 50000 - self._n_sweep_steps = 50000 - self._n_print_steps = 0 - self._n_iterations = 1 - self._equilibration_control = None - self._folder_prefix = None - - #add second level options; for example spring constants - self._spring_constants = None - self._ghost_element_count = 0 - - self.md = InputTemplate() - self.md.timestep = 0.001 - self.md.n_small_steps = 10000 - self.md.n_every_steps = 10 - self.md.n_repeat_steps = 10 - self.md.n_cycles = 100 - self.md.thermostat_damping = 0.1 - self.md.barostat_damping = 0.1 - self.md.cmdargs = None - self.md.init_commands = None - - self.nose_hoover = InputTemplate() - self.nose_hoover.thermostat_damping = 0.1 - self.nose_hoover.barostat_damping = 0.1 - - self.berendsen = InputTemplate() - self.berendsen.thermostat_damping = 100.0 - self.berendsen.barostat_damping = 100.0 - - self.queue = InputTemplate() - self.queue.scheduler = "local" - self.queue.cores = 1 - self.queue.jobname = "calphy" - self.queue.walltime = None - self.queue.queuename = None - self.queue.memory = "3GB" - self.queue.commands = None - self.queue.options = None - self.queue.modules = None - - self.tolerance = InputTemplate() - self.tolerance.lattice_constant = 0.0002 - self.tolerance.spring_constant = 0.1 - self.tolerance.solid_fraction = 0.7 - self.tolerance.liquid_fraction = 0.05 - self.tolerance.pressure = 0.5 - - #specific input options - self.melting_temperature = InputTemplate() - self.melting_temperature.guess = None - self.melting_temperature.step = 200 - self.melting_temperature.attempts = 5 - - #new mode for composition trf - self.composition_scaling = CompositionScaling() - - def __repr__(self): - """ - String of the class - """ - data = "%s system with T=%s, P=%s in %s lattice for mode %s"%(self.to_string(self.reference_phase), - self.to_string(self._temperature), self.to_string(self._pressure), self.to_string(self.lattice), self.to_string(self.mode)) - return data - - def _repr_json_(self): - return self.to_dict() - - def to_string(self, val): - if val is None: - return "None" - else: - return str(val) - - def to_bool(self, val): - if val in ["True", "true", 1, "1", True]: - return True - elif val in ["False", "false", 0, "0", False]: - return False - else: - raise ValueError(f'Unknown bool input of type {type(val)} with value {val}') - - @property - def element(self): - return self._element - - @element.setter - def element(self, val): - val = self.check_and_convert_to_list(val) - self._n_elements = len(val) - self._element = val - - @property - def n_elements(self): - return self._n_elements - - @property - def mass(self): - return self._mass - - @mass.setter - def mass(self, val): - val = self.check_and_convert_to_list(val) - if self.element is not None: - if not len(self.element) == len(val): - raise ValueError("Elements and mass must have same length") - self._mass = val - - @property - def mode(self): - return self._mode - - @mode.setter - def mode(self, val): - #add check - self._mode = val - if val == "melting_temperature": - self.temperature = 0 - self.pressure = 0 - - @property - def lattice(self): - return self._lattice - - @lattice.setter - def lattice(self, val): - self._lattice = val - - @property - def pressure(self): - return self._pressure_input - - @pressure.setter - def pressure(self, val): - """ - Pressure input can be of many diff types: - Input iso fix_lattice Mode add. constraints - 1. None True True any No - 2. scalar True False any No - 3. list of scalar len 1 True False any No - 4. list of scalar len 2 True False ps No - 5. list of scalar len 3 False False any All terms equal - 6. list of list len 1x3 False False any Each inner term equal - 7. list of list len 2x3 False False ps Each inner term equal - """ - def _check_equal(val): - if not (val[0]==val[1]==val[2]): - return False - return True - - self._pressure_input = val - - error_message = "Available pressure types are of shape: None, 1, 3, (1x1), (2x1), (3x1), (2x3)" - # Case: 1 - if val is None: - self._iso = True - self._fix_lattice = True - self._pressure = None - self._pressure_stop = None - - # Cases: 3,4,5,6,7 - elif isinstance(val, list): - shape = np.shape(val) - indx = shape[0] - indy = shape[1] if len(shape)==2 else None - - if indx == 1: - if indy is None: - # Case: 3 - self._iso = True - self._fix_lattice = False - self._pressure = val[0] - self._pressure_stop = val[0] - elif indy == 3: - # Case: 6 - if _check_equal(val[0]): - self._iso = False - self._fix_lattice = False - self._pressure = val[0][0] - self._pressure_stop = val[0][0] - else: - raise NotImplementedError("Pressure should have px=py=pz") - else: - raise NotImplementedError(error_message) - elif indx == 2: - if indy is None: - # Case: 4 - self._iso = True - self._fix_lattice = False - self._pressure = val[0] - self._pressure_stop = val[1] - elif indy == 3: - # Case: 7 - self._iso = False - self._fix_lattice = False - if _check_equal(val[0]) and _check_equal(val[1]): - self._pressure = val[0][0] - self._pressure_stop = val[1][0] - else: - raise NotImplementedError("Pressure should have px=py=pz") - else: - raise NotImplementedError(error_message) - - elif indx == 3: - if indy is None: - # Case: 5 - if _check_equal(val): - self._iso = False - self._fix_lattice = False - self._pressure = val[0] - self._pressure_stop = val[0] - else: - raise NotImplementedError("Pressure should have px=py=pz") - else: - raise NotImplementedError(error_message) - else: - raise NotImplementedError() - - # Case: 2 - else: - self._pressure = val - self._pressure_stop = val - self._iso = True - self._fix_lattice = False - - @property - def temperature(self): - return self._temperature_input - - @temperature.setter - def temperature(self, val): - self._temperature_input = val - if val is None: - self._temperature = val - self._temperature_stop = val - self._temperature_high = val - elif isinstance(val, list): - if len(val) == 2: - self._temperature = val[0] - self._temperature_stop = val[1] - if self._temperature_high is None: - self._temperature_high = 2*val[1] - else: - raise ValueError("Temperature cannot have len>2") - else: - self._temperature = val - self._temperature_stop = val - if self._temperature_high is None: - self._temperature_high = 2*val - - @property - def temperature_high(self): - return self._temperature_high - - @temperature_high.setter - def temperature_high(self, val): - self._temperature_high = val - - @property - def pair_style(self): - return self._pair_style - - @pair_style.setter - def pair_style(self, val): - val = self.check_and_convert_to_list(val) - ps_lst = [] - ps_options_lst = [] - for ps in val: - ps_split = ps.split() - ps_lst.append(ps_split[0]) - if len(ps) > 1: - ps_options_lst.append(" ".join(ps_split[1:])) - else: - ps_options_lst.append("") - - #val = self.fix_paths(val) - self._pair_style = ps_lst - - #only set if its None - if self.pair_style_options is None: - self.pair_style_options = ps_options_lst - - - @property - def pair_style_options(self): - return self._pair_style_options - - @pair_style_options.setter - def pair_style_options(self, val): - val = self.check_and_convert_to_list(val) - self._pair_style_options = val - - @property - def pair_style_with_options(self): - #ignore options if lengths do not match - if len(self.pair_style) != len(self.pair_style_options): - self.pair_style_options = [self.pair_style_options[0] for x in range(len(self.pair_style))] - return [" ".join([self.pair_style[i], self.pair_style_options[i]]) for i in range(len(self.pair_style))] - - @property - def pair_coeff(self): - return self._pair_coeff - - @pair_coeff.setter - def pair_coeff(self, val): - val = self.check_and_convert_to_list(val) - if self._fix_potential_path: - val = self.fix_paths(val) - self._pair_coeff = val - - @property - def potential_file(self): - return self._potential_file - - @potential_file.setter - def potential_file(self, val): - if os.path.exists(val): - self._potential_file = val - else: - raise FileNotFoundError("File %s not found"%val) - - @property - def reference_phase(self): - return self._reference_phase - - @reference_phase.setter - def reference_phase(self, val): - self._reference_phase = val - - @property - def lattice_constant(self): - return self._lattice_constant - - @lattice_constant.setter - def lattice_constant(self, val): - self._lattice_constant = val - - @property - def repeat(self): - return self._repeat - - @repeat.setter - def repeat(self, val): - if isinstance(val, list): - if not len(val) == 3: - raise ValueError("repeat should be three") - else: - val = [val, val, val] - self._repeat = val - - @property - def npt(self): - return self._npt - - @npt.setter - def npt(self, val): - val = self.to_bool(val) - if isinstance(val, bool): - self._npt = val - else: - raise TypeError("NPT should be either True/False") - - @property - def script_mode(self): - return self._script_mode - - @script_mode.setter - def script_mode(self, val): - val = self.to_bool(val) - if isinstance(val, bool): - self._script_mode = val - else: - raise TypeError("script mode should be either True/False") - - @property - def lammps_executable(self): - return self._lammps_executable - - @lammps_executable.setter - def lammps_executable(self, val): - self._lammps_executable = val - - @property - def mpi_executable(self): - return self._mpi_executable - - @mpi_executable.setter - def mpi_executable(self, val): - self._mpi_executable = val - - @property - def n_equilibration_steps(self): - return self._n_equilibration_steps - - @n_equilibration_steps.setter - def n_equilibration_steps(self, val): - self._n_equilibration_steps = val - - @property - def n_switching_steps(self): - return self._n_switching_steps - - @n_switching_steps.setter - def n_switching_steps(self, val): - if isinstance(val, list): - if len(val) == 2: - self._n_switching_steps = val[0] - self._n_sweep_steps = val[1] - else: - raise TypeError("n_switching_steps should be len 1 or 2") - else: - self._n_switching_steps = val - self._n_sweep_steps = val - - @property - def n_print_steps(self): - return self._n_print_steps - - @n_print_steps.setter - def n_print_steps(self, val): - self._n_print_steps = val - - @property - def n_iterations(self): - return self._n_iterations - - @n_iterations.setter - def n_iterations(self, val): - self._n_iterations = val - - @property - def equilibration_control(self): - return self._equilibration_control - - @equilibration_control.setter - def equilibration_control(self, val): - if val not in ["berendsen", "nose-hoover", None]: - raise ValueError("Equilibration control should be either berendsen or nose-hoover or None") - self._equilibration_control = val - - @property - def melting_cycle(self): - return self._melting_cycle - - @melting_cycle.setter - def melting_cycle(self, val): - val = self.to_bool(val) - if isinstance(val, bool): - self._melting_cycle = val - else: - raise TypeError("Melting cycle should be either True/False") - - @property - def spring_constants(self): - return self._spring_constants - - @spring_constants.setter - def spring_constants(self, val): - val = self.check_and_convert_to_list(val, check_none=True) - self._spring_constants = val - - @property - def folder_prefix(self): - return self._folder_prefix - - @folder_prefix.setter - def folder_prefix(self, val): - self._folder_prefix = val - - def fix_paths(self, potlist): - """ - Fix paths for potential files to complete ones - """ - fixedpots = [] - for pot in potlist: - pcraw = pot.split() - if len(pcraw) >= 3: - filename = pcraw[2] - filename = os.path.abspath(filename) - pcnew = " ".join([*pcraw[:2], filename, *pcraw[3:]]) - fixedpots.append(pcnew) - else: - fixedpots.append(pot) - return fixedpots - - def create_identifier(self): - """ - Generate an identifier - - Parameters - ---------- - calc: dict - a calculation dict - - Returns - ------- - identistring: string - unique identification string - """ - #lattice processed - prefix = self.mode - if prefix == 'melting_temperature': - ts = int(0) - ps = int(0) - l = 'tm' - else: - ts = int(self._temperature) - if self._pressure is None: - ps = "None" - else: - ps = "%d"%(int(self._pressure)) - l = self.lattice - l = l.split('/') - l = l[-1] - - if self.folder_prefix is None: - identistring = "-".join([prefix, l, str(ts), str(ps)]) - else: - identistring = "-".join([self.folder_prefix, prefix, l, str(ts), str(ps)]) - return identistring - - def get_folder_name(self): - identistring = self.create_identifier() - simfolder = os.path.join(os.getcwd(), identistring) - return simfolder - - def create_folders(self): - """ - Create the necessary folder for calculation - - Parameters - ---------- - calc : dict - calculation block - - Returns - ------- - folder : string - create folder - """ - simfolder = self.get_folder_name() - - #if folder exists, delete it -> then create - try: - if os.path.exists(simfolder): - shutil.rmtree(simfolder) - except OSError: - newstr = '-'.join(str(datetime.datetime.now()).split()) - newstr = '-'.join([simfolder, newstr]) - shutil.move(simfolder, newstr) - - os.mkdir(simfolder) - return simfolder - - @classmethod - def generate(cls, indata): - if not isinstance(indata, dict): - if os.path.exists(indata): - with open(indata) as file: - indata = yaml.load(file, Loader=yaml.FullLoader) - if isinstance(indata, dict): - calc = cls() - calc.element = indata["element"] - calc.mass = indata["mass"] - - calc.script_mode = check_dict(indata, "script_mode", retval=False) - calc.lammps_executable = check_dict(indata, "lammps_executable") - calc.mpi_executable = check_dict(indata, "mpi_executable") - - if "md" in indata.keys(): - calc.md.add_from_dict(indata["md"]) - if "queue" in indata.keys(): - calc.queue.add_from_dict(indata["queue"]) - if "tolerance" in indata.keys(): - calc.tolerance.add_from_dict(indata["tolerance"]) - if "melting_temperature" in indata.keys(): - calc.melting_temperature.add_from_dict(indata["melting_temperature"]) - if "nose_hoover" in indata.keys(): - calc.nose_hoover.add_from_dict(indata["nose_hoover"]) - if "berendsen" in indata.keys(): - calc.berendsen.add_from_dict(indata["berendsen"]) - if "composition_scaling" in indata.keys(): - calc.composition_scaling.add_from_dict(indata["composition_scaling"]) - #if temperature_high is present, set it - if "temperature_high" in indata.keys(): - calc.temperature_high = indata["temperature_high"] - return calc - else: - raise FileNotFoundError('%s input file not found'% indata) - - @staticmethod - def read_file(file): - if os.path.exists(file): - with open(file) as file: - indata = yaml.load(file, Loader=yaml.FullLoader) - else: - raise FileNotFoundError('%s input file not found'% file) - return indata - - @property - def savefile(self): - simfolder = self.get_folder_name() - return os.path.join(simfolder, 'job.npy') - - -def read_inputfile(file): - """ - Read calphy inputfile - - Parameters - ---------- - file : string - input file - - Returns - ------- - options : dict - dictionary containing input options - """ - indata = Calculation.read_file(file) - calculations = [] - for ci in indata["calculations"]: - #get main variables - mode = ci["mode"] - if mode == "melting_temperature": - calc = Calculation.generate(indata) - calc.add_from_dict(ci, keys=["mode", "pair_style", "pair_coeff", "repeat", "n_equilibration_steps", - "n_switching_steps", "n_print_steps", "n_iterations", "spring_constants", "equilibration_control", - "folder_prefix"]) - calc.pressure = Calculation.convert_to_list(ci["pressure"], check_none=True) if "pressure" in ci.keys() else 0 - calc.temperature = Calculation.convert_to_list(ci["temperature"]) if "temperature" in ci.keys() else None - calc.lattice = Calculation.convert_to_list(ci["lattice"]) if "lattice" in ci.keys() else None - calc.reference_phase = Calculation.convert_to_list(ci["reference_phase"]) if "reference_phase" in ci.keys() else None - calc.lattice_constant = Calculation.convert_to_list(ci["lattice_constant"]) if "lattice_constant" in ci.keys() else 0 - calculations.append(calc) - else: - pressure = Calculation.convert_to_list(ci["pressure"], check_none=True) if "pressure" in ci.keys() else [] - temperature = Calculation.convert_to_list(ci["temperature"]) if "temperature" in ci.keys() else [] - lattice = Calculation.convert_to_list(ci["lattice"]) - reference_phase = Calculation.convert_to_list(ci["reference_phase"]) - if "lattice_constant" in ci.keys(): - lattice_constant = Calculation.convert_to_list(ci["lattice_constant"]) - else: - lattice_constant = [0 for x in range(len(lattice))] - if not len(lattice_constant)==len(reference_phase)==len(lattice): - raise ValueError("lattice constant, reference phase and lattice should have same length") - lat_props = [{"lattice": lattice[x], "lattice_constant":lattice_constant[x], "reference_phase":reference_phase[x]} for x in range(len(lattice))] - if (mode == "fe") or (mode == "alchemy") or (mode == "composition_scaling"): - combos = itertools.product(lat_props, pressure, temperature) - elif mode == "ts" or mode == "tscale" or mode == "mts": - if not len(temperature) == 2: - raise ValueError("ts/tscale mode needs 2 temperature values") - temperature = [temperature] - combos = itertools.product(lat_props, pressure, temperature) - elif mode == "pscale": - if not len(pressure) == 2: - raise ValueError("pscale mode needs 2 pressure values") - pressure = [pressure] - combos = itertools.product(lat_props, pressure, temperature) - #create calculations - for combo in combos: - calc = Calculation.generate(indata) - calc.add_from_dict(ci, keys=["mode", "pair_style", "pair_coeff", "pair_style_options", "npt", - "repeat", "n_equilibration_steps", - "n_switching_steps", "n_print_steps", "n_iterations", "potential_file", "spring_constants", - "melting_cycle", "equilibration_control", "folder_prefix", "temperature_high"]) - calc.lattice = combo[0]["lattice"] - calc.lattice_constant = combo[0]["lattice_constant"] - calc.reference_phase = combo[0]["reference_phase"] - calc.pressure = combo[1] - calc.temperature = combo[2] - calculations.append(calc) - return calculations - - -def save_job(job): - filename = os.path.join(job.simfolder, 'job.npy') - np.save(filename, job) - -def load_job(filename): - job = np.load(filename, allow_pickle=True).flatten()[0] - return job - -def check_dict(indict, key, retval=None): - if key in indict.items(): - return indict[key] - else: - return retval From 4f50c02cbaec3545e55226e563fa4600504813fa Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Fri, 19 Jan 2024 11:42:51 +0100 Subject: [PATCH 3/8] remove lattice module --- calphy/lattice.py | 178 ---------------------------------------------- calphy/phase.py | 36 ---------- 2 files changed, 214 deletions(-) delete mode 100644 calphy/lattice.py diff --git a/calphy/lattice.py b/calphy/lattice.py deleted file mode 100644 index 4b3b250..0000000 --- a/calphy/lattice.py +++ /dev/null @@ -1,178 +0,0 @@ -""" -calphy: a Python library and command line interface for automated free -energy calculations. - -Copyright 2021 (c) Sarath Menon^1, Yury Lysogorskiy^2, Ralf Drautz^2 -^1: Max Planck Institut für Eisenforschung, Dusseldorf, Germany -^2: Ruhr-University Bochum, Bochum, Germany - -calphy is published and distributed under the Academic Software License v1.0 (ASL). -calphy is distributed in the hope that it will be useful for non-commercial academic research, -but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -calphy API is published and distributed under the BSD 3-Clause "New" or "Revised" License -See the LICENSE FILE for more details. - -More information about the program can be found in: -Menon, Sarath, Yury Lysogorskiy, Jutta Rogal, and Ralf Drautz. -“Automated Free Energy Calculation from Atomistic Simulations.” Physical Review Materials 5(10), 2021 -DOI: 10.1103/PhysRevMaterials.5.103801 - -For more information contact: -sarath.menon@ruhr-uni-bochum.de/yury.lysogorskiy@icams.rub.de -""" - -from mendeleev import element -import os -from pylammpsmpi import LammpsLibrary -import numpy as np -import pyscal3.core as pc -from ase.io import read - -""" -Conversion factors for creating initial lattices -""" -latticedict = { - "BCC" :{"LQD": 1.00000, "BCC":1.00000, "FCC":0.79370, "HCP":1.12246, "DIA":0.62996, "SC":1.25992, "N":2}, - "FCC" :{"LQD": 1.00000, "BCC":1.25992, "FCC":1.00000, "HCP":1.78179, "DIA":0.79370, "SC":1.58740, "N":4}, - "HCP" :{"LQD": 1.00000, "BCC":0.89090, "FCC":0.79370, "HCP":1.00000, "DIA":0.62996, "SC":0.89089, "N":4}, - "DIA" :{"LQD": 1.00000, "BCC":1.58740, "FCC":0.79370, "HCP":1.25992, "DIA":1.00000, "SC":2.00000, "N":8}, - "SC" :{"LQD": 1.00000, "BCC":0.79370, "FCC":0.62996, "HCP":1.12247, "DIA":0.50000, "SC":1.00000, "N":1}, -} - -def get_lattice(symbol, lat): - """ - Find lattice constants of an element - - Parameters - ---------- - symbol : string - symbol of chemical element - - lattice_list : list of strings - list of lattices - - Returns - ------- - lattice_constants : list of floats - list of lattice constant values - - atoms_per_cell : list of ints - number of atoms per cell - - lammps_lattice : list of strings - the main lattice to be used in lammps - """ - - chem = element(symbol) - - mainlat = chem.lattice_structure - - if mainlat == "HEX": - mainlat = "HCP" - - mainalat = chem.lattice_constant - - #print(mainlat, lat) - newlat = latticedict[mainlat][lat]*mainalat - lattice_constant = newlat - - if lat == "LQD": - atoms_per_cell = latticedict[mainlat]["N"] - lammps_lattice = mainlat.lower() - else: - atoms_per_cell = latticedict[lat]["N"] - lammps_lattice = lat.lower() - - return lattice_constant, atoms_per_cell, lammps_lattice - -def check_dump_file(infile): - try: - #now use pyscal to read it in, - sys = pc.System(infile) - atoms = sys.atoms - natoms = len(atoms) - types = [atom.type for atom in atoms] - xx, xxcounts = np.unique(types, return_counts=True) - conc = xxcounts/np.sum(xxcounts) - return (natoms, conc) - except: - return None - -def check_data_file(infile, script_mode=False): - try: - if not script_mode: - lmp = LammpsLibrary(cores=1, - working_directory=os.getcwd()) - lmp.units("metal") - lmp.boundary("p p p") - lmp.atom_style("atomic") - lmp.timestep(0.001) - lmp.read_data(infile) - natoms = lmp.natoms - #now we convert to a dump file and read the concentration - trajfile = ".".join([infile, "dump"]) - lmp.command("mass * 1.0") - lmp.dump("2 all custom", 1, trajfile,"id type x y z") - lmp.run(0) - lmp.undump(2) - lmp.close() - format = 'lammps-dump' - else: - trajfile = read(infile, format='lammps-data', style='atomic') - format = 'ase' - #now use pyscal to read it in, - sys = pc.System(trajfile, format=format) - atoms = sys.atoms - types = [atom.type for atom in atoms] - xx, xxcounts = np.unique(types, return_counts=True) - conc = xxcounts/np.sum(xxcounts) - return (natoms, conc) - except: - return None - -def prepare_lattice(calc): - #process lattice - lattice = calc.lattice.upper() - dumpfile = False - - if lattice in ["BCC", "FCC", "HCP", "DIA", "SC", "LQD"]: - #process lattice - #throw error for multicomponent - if calc.n_elements > 1: - raise ValueError("Only files supported for multicomponent") - - alat, apc, l = get_lattice(calc.element[0], calc.lattice) - - #replace lattice constant - if calc.lattice_constant != 0: - alat = calc.lattice_constant - - conc = [1,] - - elif os.path.exists(calc.lattice): - calc.lattice = os.path.abspath(calc.lattice) - - res = check_dump_file(calc.lattice) - dumpfile = True - - if res is None: - res = check_data_file(calc.lattice) - dumpfile = False - - if res is not None: - natoms = res[0] - conc = res[1] - l = "file" - alat = 1.00 - apc = natoms - else: - raise ValueError("An input file was provided but it was neither data or dump file") - - else: - raise ValueError("Unknown lattice found. Allowed options are BCC, FCC, HCP, DIA, SC or LQD; or an input file.") - - if l == "dia": - l = "diamond" - - return l, alat, apc, conc, dumpfile - diff --git a/calphy/phase.py b/calphy/phase.py index 648db88..2f3c6f3 100644 --- a/calphy/phase.py +++ b/calphy/phase.py @@ -134,9 +134,6 @@ def __init__(self, calculation=None, simfolder=None, log_to_screen=False): self.l = self.calc.lattice self.alat = self.calc.lattice_constant self.vol = None - #self.concentration = self.calc._composition - #self.dumpfile = False - self.prepare_lattice() #other properties self.cores = self.calc.queue.cores @@ -196,39 +193,6 @@ def _from_dict(self, org_dict, indict): self._from_dict(org_dict[key], val) else: org_dict[key] = val - - def prepare_lattice(self): - """ - Prepare the lattice for the simulation - - Parameters - ---------- - None - - Returns - ------- - None - - Notes - ----- - Calculates the lattic, lattice constant, number of atoms per unit cell - and concentration of the input system. - """ - #l, alat, apc, conc, dumpfile = pl.prepare_lattice(self.calc) - #self.l = l - #self.alat = alat - #self.apc = apc - #self.concentration = self.calc.composition - #self.dumpfile = dumpfile - #self.logger.info("Lattice: %s with a=%f"%(self.l, self.alat)) - #self.logger.info("%d atoms in the unit cell"%self.apc) - #self.logger.info("concentration:") - #self.logger.info(self.concentration) - #if self.l == "file": - # if self.dumpfile: - # self.logger.info("Input structure is read in from a LAMMPS dump file") - # else: - # self.logger.info("Input structure is read in from a LAMMPS data file") def dump_current_snapshot(self, lmp, filename): """ From a0c28574ddad2c23fa702bc87b4a4900d561b424 Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Fri, 19 Jan 2024 11:44:02 +0100 Subject: [PATCH 4/8] remove lattice imports --- calphy/alchemy.py | 1 - calphy/helpers.py | 1 - calphy/liquid.py | 1 - calphy/phase.py | 1 - calphy/solid.py | 1 - 5 files changed, 5 deletions(-) diff --git a/calphy/alchemy.py b/calphy/alchemy.py index d5b5e94..8edd685 100644 --- a/calphy/alchemy.py +++ b/calphy/alchemy.py @@ -25,7 +25,6 @@ import yaml from calphy.integrators import * -import calphy.lattice as pl import calphy.helpers as ph import calphy.phase as cph diff --git a/calphy/helpers.py b/calphy/helpers.py index 02ca20d..630f0ee 100644 --- a/calphy/helpers.py +++ b/calphy/helpers.py @@ -31,7 +31,6 @@ from lammps import lammps from ase.io import read, write -import calphy.lattice as pl import pyscal3.core as pc from pyscal3.trajectory import Trajectory diff --git a/calphy/liquid.py b/calphy/liquid.py index 4801636..00a37c0 100644 --- a/calphy/liquid.py +++ b/calphy/liquid.py @@ -25,7 +25,6 @@ import yaml from calphy.integrators import * -import calphy.lattice as pl import calphy.helpers as ph import calphy.phase as cph from calphy.errors import * diff --git a/calphy/phase.py b/calphy/phase.py index 2f3c6f3..358f6d9 100644 --- a/calphy/phase.py +++ b/calphy/phase.py @@ -31,7 +31,6 @@ import pyscal3.traj_process as ptp from calphy.integrators import * -import calphy.lattice as pl import calphy.helpers as ph from calphy.errors import * diff --git a/calphy/solid.py b/calphy/solid.py index 5bcbb49..969382e 100644 --- a/calphy/solid.py +++ b/calphy/solid.py @@ -27,7 +27,6 @@ import sys from calphy.integrators import * -import calphy.lattice as pl import calphy.helpers as ph import calphy.phase as cph from calphy.errors import * From f9b08b174b551ca5153e2652404c07590b8a384c Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Fri, 19 Jan 2024 11:48:00 +0100 Subject: [PATCH 5/8] bring back utility methods --- calphy/input.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/calphy/input.py b/calphy/input.py index e553ab3..6c7a29f 100644 --- a/calphy/input.py +++ b/calphy/input.py @@ -58,6 +58,27 @@ def _check_equal(val): return False return True +def to_list(v: Any) -> List[Any]: + return np.atleast_1d(v) + +def _to_str(val): + if np.isscalar(val): + return str(val) + else: + return [str(x) for x in val] + +def _to_int(val): + if np.isscalar(val): + return int(val) + else: + return [int(x) for x in val] + +def _to_float(val): + if np.isscalar(val): + return float(val) + else: + return [float(x) for x in val] + class CompositionScaling(BaseModel, title='Composition scaling input options'): _input_chemical_composition: PrivateAttr(default=None) output_chemical_composition: Annotated[dict, Field(default=None, required=False)] From d8a8c2a8708d24fac5d489adec0371d924cfa55f Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Fri, 19 Jan 2024 11:58:36 +0100 Subject: [PATCH 6/8] update tests --- examples/complete_test.sh | 0 tests/test_solid_methods.py | 1 - 2 files changed, 1 deletion(-) create mode 100644 examples/complete_test.sh diff --git a/examples/complete_test.sh b/examples/complete_test.sh new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solid_methods.py b/tests/test_solid_methods.py index 3399a26..03113a1 100644 --- a/tests/test_solid_methods.py +++ b/tests/test_solid_methods.py @@ -1,6 +1,5 @@ import pytest from calphy.input import read_inputfile -import calphy.lattice as pl from calphy.solid import Solid from calphy.liquid import Liquid import os From 551417838444691aa35d5cd219d2bafe49fc361e Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Fri, 19 Jan 2024 13:32:37 +0100 Subject: [PATCH 7/8] fix clustering --- calphy/helpers.py | 2 +- examples/complete_test.sh | 39 ++++++++++++++++++++++++++++++++++ examples/example_03/input.yaml | 5 ++--- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/calphy/helpers.py b/calphy/helpers.py index 630f0ee..afbabd2 100644 --- a/calphy/helpers.py +++ b/calphy/helpers.py @@ -213,7 +213,7 @@ def find_solid_fraction(file): sys.find.neighbors( method="cutoff", cutoff=5.0 ) # Maybe add value as convergence param? - sys.find.solids() + sys.find.solids(cluster=False) solids = np.sum(sys.atoms.solid) return solids diff --git a/examples/complete_test.sh b/examples/complete_test.sh index e69de29..10813ad 100644 --- a/examples/complete_test.sh +++ b/examples/complete_test.sh @@ -0,0 +1,39 @@ + +cd example_03 +echo "testing TS solid" +rm -rf ts_* +calphy_kernel -i input.yaml -k 0 -s True +echo "testing TS liquid" +calphy_kernel -i input.yaml -k 1 -s True +cd .. + +cd example_04 +rm -rf ts-* +echo "testing Automated Tm" +calphy_kernel -i input.1.yaml -k 0 -s True +cd .. + +cd example_07 +rm -rf alchemy-* +echo "testing alchemy" +calphy_kernel -i input.1.yaml -k 0 -s True +cd .. + +cd example_08 +rm -rf tscale-* +echo "testing tscale" +calphy_kernel -i input.1.yaml -k 1 -s True +cd .. + +cd example_09 +rm -rf pscale-* +echo "testing pscale" +calphy_kernel -i input2.yaml -k 0 -s True +cd .. + +cd example_10 +rm -rf composition-* +echo "testing composition scaling" +calphy_kernel -i input-composition.yaml -k 0 -s True +cd .. + diff --git a/examples/example_03/input.yaml b/examples/example_03/input.yaml index 0b39dc5..b3b3f17 100644 --- a/examples/example_03/input.yaml +++ b/examples/example_03/input.yaml @@ -3,9 +3,8 @@ calculations: lattice: fcc lattice_constant: 3.61 mass: 63.546 + equilibration_control: berendsen md: - barostat_damping: 0.1 - thermostat_damping: 0.1 timestep: 0.001 mode: ts n_equilibration_steps: 10000 @@ -54,4 +53,4 @@ calculations: - 5 temperature: - 1200.0 - - 1400.0 \ No newline at end of file + - 1400.0 From e704d83f80f2810044aabb27c57a2d03e419baf4 Mon Sep 17 00:00:00 2001 From: Sarath Menon Date: Fri, 19 Jan 2024 15:00:16 +0100 Subject: [PATCH 8/8] fix bug in pair style anems --- calphy/routines.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/calphy/routines.py b/calphy/routines.py index 789ad0e..4c8219f 100644 --- a/calphy/routines.py +++ b/calphy/routines.py @@ -443,6 +443,11 @@ def routine_composition_scaling(job): job.logger.info("Update pair coefficients") job.logger.info(f"pair coeff 1: {job.calc.pair_coeff[0]}") job.logger.info(f"pair coeff 2: {job.calc.pair_coeff[1]}") + job.calc._pair_style_names.append(job.calc._pair_style_names[0]) + job.logger.info("Update pair styles") + job.logger.info(f"pair style 1: {job.calc._pair_style_names[0]}") + job.logger.info(f"pair style 2: {job.calc._pair_style_names[1]}") + backup_element = job.calc.element.copy() job.calc.element = comp.pair_list_old #job.calc._ghost_element_count = len(comp.new_atomtype) - len()