def energy_check(lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None) -> dict: """ Performs a quick run 0 calculation to evaluate the potential energy of a configuration. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The atomic configuration to evaluate. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. Returns ------- dict Dictionary of results consisting of keys: - **'E_pot'** (*float*) - The per-atom potential energy of the system. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='init.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info # Fill in lammps input script template = read_calc_file('iprPy.calculation.energy_check', 'run0.template') script = filltemplate(template, lammps_variables, '<', '>') # Run LAMMPS output = lmp.run(lammps_command, script=script, mpi_command=mpi_command, logfile=None) # Extract output values thermo = output.simulations[-1]['thermo'] results = {} results['E_pot'] = uc.set_in_units(thermo.v_peatom.values[-1], lammps_units['energy']) return results
def e_vs_r_scan(lammps_command: str, system: am.System, potential: am.lammps.Potential, mpi_command: Optional[str] = None, ucell: Optional[am.System] = None, rmin: float = uc.set_in_units(2.0, 'angstrom'), rmax: float = uc.set_in_units(6.0, 'angstrom'), rsteps: int = 200) -> dict: """ Performs a cohesive energy scan over a range of interatomic spaces, r. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. ucell : atomman.System, optional The fundamental unit cell correspodning to system. This is used to convert system dimensions to cell dimensions. If not given, ucell will be taken as system. rmin : float, optional The minimum r spacing to use (default value is 2.0 angstroms). rmax : float, optional The maximum r spacing to use (default value is 6.0 angstroms). rsteps : int, optional The number of r spacing steps to evaluate (default value is 200). Returns ------- dict Dictionary of results consisting of keys: - **'r_values'** (*numpy.array of float*) - All interatomic spacings, r, explored. - **'a_values'** (*numpy.array of float*) - All unit cell a lattice constants corresponding to the values explored. - **'Ecoh_values'** (*numpy.array of float*) - The computed cohesive energies for each r value. - **'min_cell'** (*list of atomman.System*) - Systems corresponding to the minima identified in the Ecoh_values. """ # Make system a deepcopy of itself (protect original from changes) system = deepcopy(system) # Set ucell = system if ucell not given if ucell is None: ucell = system # Calculate the r/a ratio for the unit cell r_a = ucell.r0() / ucell.box.a # Get ratios of lx, ly, and lz of system relative to a of ucell lx_a = system.box.a / ucell.box.a ly_a = system.box.b / ucell.box.a lz_a = system.box.c / ucell.box.a alpha = system.box.alpha beta = system.box.beta gamma = system.box.gamma # Build lists of values r_values = np.linspace(rmin, rmax, rsteps) a_values = r_values / r_a Ecoh_values = np.empty(rsteps) # Loop over values for i in range(rsteps): # Rescale system's box a = a_values[i] system.box_set(a=a * lx_a, b=a * ly_a, c=a * lz_a, alpha=alpha, beta=beta, gamma=gamma, scale=True) # Get lammps units lammps_units = lmp.style.unit(potential.units) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='atom.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info # Write lammps input script lammps_script = 'run0.in' template = read_calc_file('iprPy.calculation.E_vs_r_scan', 'run0.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run lammps and extract data try: output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) except: Ecoh_values[i] = np.nan else: thermo = output.simulations[0]['thermo'] if output.lammps_date < datetime.date(2016, 8, 1): Ecoh_values[i] = uc.set_in_units(thermo.peatom.values[-1], lammps_units['energy']) else: Ecoh_values[i] = uc.set_in_units(thermo.v_peatom.values[-1], lammps_units['energy']) # Rename log.lammps try: shutil.move('log.lammps', 'run0-' + str(i) + '-log.lammps') except: pass if len(Ecoh_values[np.isfinite(Ecoh_values)]) == 0: raise ValueError( 'All LAMMPS runs failed. Potential likely invalid or incompatible.' ) # Find unit cell systems at the energy minimums min_cells = [] for i in range(1, rsteps - 1): if (Ecoh_values[i] < Ecoh_values[i - 1] and Ecoh_values[i] < Ecoh_values[i + 1]): a = a_values[i] cell = deepcopy(ucell) cell.box_set(a=a, b=a * ucell.box.b / ucell.box.a, c=a * ucell.box.c / ucell.box.a, alpha=alpha, beta=beta, gamma=gamma, scale=True) min_cells.append(cell) # Collect results results_dict = {} results_dict['r_values'] = r_values results_dict['a_values'] = a_values results_dict['Ecoh_values'] = Ecoh_values results_dict['min_cell'] = min_cells return results_dict
def relax_dynamic(lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None, p_xx: float = 0.0, p_yy: float = 0.0, p_zz: float = 0.0, p_xy: float = 0.0, p_xz: float = 0.0, p_yz: float = 0.0, temperature: float = 0.0, integrator: Optional[str] = None, runsteps: int = 220000, thermosteps: int = 100, dumpsteps: Optional[int] = None, restartsteps: Optional[int] = None, equilsteps: int = 20000, randomseed: Optional[int] = None) -> dict: """ Performs a full dynamic relax on a given system at the given temperature to the specified pressure state. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. symbols : list of str The list of element-model symbols for the Potential that correspond to system's atypes. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. p_xx : float, optional The value to relax the x tensile pressure component to (default is 0.0). p_yy : float, optional The value to relax the y tensile pressure component to (default is 0.0). p_zz : float, optional The value to relax the z tensile pressure component to (default is 0.0). temperature : float, optional The temperature to relax at (default is 0.0). runsteps : int, optional The number of integration steps to perform (default is 220000). integrator : str or None, optional The integration method to use. Options are 'npt', 'nvt', 'nph', 'nve', 'nve+l', 'nph+l'. The +l options use Langevin thermostat. (Default is None, which will use 'nph+l' for temperature == 0, and 'npt' otherwise.) thermosteps : int, optional Thermo values will be reported every this many steps (default is 100). dumpsteps : int or None, optional Dump files will be saved every this many steps (default is None, which sets dumpsteps equal to runsteps). restartsteps : int or None, optional Restart files will be saved every this many steps (default is None, which sets restartsteps equal to runsteps). equilsteps : int, optional The number of timesteps at the beginning of the simulation to exclude when computing average values (default is 20000). randomseed : int or None, optional Random number seed used by LAMMPS in creating velocities and with the Langevin thermostat. (Default is None which will select a random int between 1 and 900000000.) Returns ------- dict Dictionary of results consisting of keys: - **'dumpfile_initial'** (*str*) - The name of the initial dump file created. - **'symbols_initial'** (*list*) - The symbols associated with the initial dump file. - **'dumpfile_final'** (*str*) - The name of the final dump file created. - **'symbols_final'** (*list*) - The symbols associated with the final dump file. - **'nsamples'** (*int*) - The number of thermodynamic samples included in the mean and standard deviation estimates. Can also be used to estimate standard error values assuming that the thermo step size is large enough (typically >= 100) to assume the samples to be independent. - **'E_pot'** (*float*) - The mean measured potential energy. - **'measured_pxx'** (*float*) - The measured x tensile pressure of the relaxed system. - **'measured_pyy'** (*float*) - The measured y tensile pressure of the relaxed system. - **'measured_pzz'** (*float*) - The measured z tensile pressure of the relaxed system. - **'measured_pxy'** (*float*) - The measured xy shear pressure of the relaxed system. - **'measured_pxz'** (*float*) - The measured xz shear pressure of the relaxed system. - **'measured_pyz'** (*float*) - The measured yz shear pressure of the relaxed system. - **'temp'** (*float*) - The mean measured temperature. - **'E_pot_std'** (*float*) - The standard deviation in the measured potential energy values. - **'measured_pxx_std'** (*float*) - The standard deviation in the measured x tensile pressure of the relaxed system. - **'measured_pyy_std'** (*float*) - The standard deviation in the measured y tensile pressure of the relaxed system. - **'measured_pzz_std'** (*float*) - The standard deviation in the measured z tensile pressure of the relaxed system. - **'measured_pxy_std'** (*float*) - The standard deviation in the measured xy shear pressure of the relaxed system. - **'measured_pxz_std'** (*float*) - The standard deviation in the measured xz shear pressure of the relaxed system. - **'measured_pyz_std'** (*float*) - The standard deviation in the measured yz shear pressure of the relaxed system. - **'temp_std'** (*float*) - The standard deviation in the measured temperature values. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Handle default values if dumpsteps is None: dumpsteps = runsteps if restartsteps is None: restartsteps = runsteps # Define lammps variables lammps_variables = {} # Dump initial system as data and build LAMMPS inputs system_info = system.dump('atom_data', f='init.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info # Generate LAMMPS inputs for restarting system_info2 = potential.pair_restart_info('*.restart', system.symbols) lammps_variables['atomman_pair_restart_info'] = system_info2 # Integrator lines for main run integ_info = integrator_info(integrator=integrator, p_xx=p_xx, p_yy=p_yy, p_zz=p_zz, p_xy=p_xy, p_xz=p_xz, p_yz=p_yz, temperature=temperature, randomseed=randomseed, units=potential.units, lammps_date=lammps_date) lammps_variables['integrator_info'] = integ_info # Integrator lines for restarts integ_info2 = integrator_info(integrator=integrator, p_xx=p_xx, p_yy=p_yy, p_zz=p_zz, p_xy=p_xy, p_xz=p_xz, p_yz=p_yz, temperature=temperature, velocity_temperature=0.0, randomseed=randomseed, units=potential.units, lammps_date=lammps_date) lammps_variables['integrator_restart_info'] = integ_info2 # Other run settings lammps_variables['thermosteps'] = thermosteps lammps_variables['runsteps'] = runsteps lammps_variables['dumpsteps'] = dumpsteps lammps_variables['restartsteps'] = restartsteps # Set compute stress/atom based on LAMMPS version if lammps_date < datetime.date(2014, 2, 12): lammps_variables['stressterm'] = '' else: lammps_variables['stressterm'] = 'NULL' # Set dump_keys based on atom_style if potential.atom_style in ['charge']: lammps_variables['dump_keys'] = 'id type q xu yu zu c_pe c_ke &\n' lammps_variables[ 'dump_keys'] += 'c_stress[1] c_stress[2] c_stress[3] c_stress[4] c_stress[5] c_stress[6]' else: lammps_variables['dump_keys'] = 'id type xu yu zu c_pe c_ke &\n' lammps_variables[ 'dump_keys'] += 'c_stress[1] c_stress[2] c_stress[3] c_stress[4] c_stress[5] c_stress[6]' # Set dump_modify_format based on lammps_date if lammps_date < datetime.date(2016, 8, 3): if potential.atom_style in ['charge']: lammps_variables['dump_modify_format'] = f'"%d %d{12 * " %.13e"}"' else: lammps_variables['dump_modify_format'] = f'"%d %d{11 * " %.13e"}"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script lammps_script = 'full_relax.in' template = read_calc_file('iprPy.calculation.relax_dynamic', 'full_relax.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Write lammps restart input script restart_script = 'full_relax_restart.in' template = read_calc_file('iprPy.calculation.relax_dynamic', 'full_relax_restart.template') with open(restart_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run lammps output = lmp.run(lammps_command, script_name=lammps_script, restart_script_name=restart_script, mpi_command=mpi_command, screen=False) # Extract LAMMPS thermo data. results = {} thermo = output.flatten()['thermo'] results['dumpfile_initial'] = '0.dump' results['symbols_initial'] = system.symbols # Load relaxed system from dump file last_dump_file = f'{thermo.Step.values[-1]}.dump' results['dumpfile_final'] = last_dump_file system = am.load('atom_dump', last_dump_file, symbols=system.symbols) results['symbols_final'] = system.symbols # Only consider values where Step >= equilsteps thermo = thermo[thermo.Step >= equilsteps] results['nsamples'] = len(thermo) # Get cohesive energy estimates natoms = system.natoms results['E_pot'] = uc.set_in_units(thermo.PotEng.mean() / natoms, lammps_units['energy']) results['E_pot_std'] = uc.set_in_units(thermo.PotEng.std() / natoms, lammps_units['energy']) results['E_total'] = uc.set_in_units(thermo.TotEng.mean() / natoms, lammps_units['energy']) results['E_total_std'] = uc.set_in_units(thermo.TotEng.std() / natoms, lammps_units['energy']) results['lx'] = uc.set_in_units(thermo.Lx.mean(), lammps_units['length']) results['lx_std'] = uc.set_in_units(thermo.Lx.std(), lammps_units['length']) results['ly'] = uc.set_in_units(thermo.Ly.mean(), lammps_units['length']) results['ly_std'] = uc.set_in_units(thermo.Ly.std(), lammps_units['length']) results['lz'] = uc.set_in_units(thermo.Lz.mean(), lammps_units['length']) results['lz_std'] = uc.set_in_units(thermo.Lz.std(), lammps_units['length']) results['xy'] = uc.set_in_units(thermo.Xy.mean(), lammps_units['length']) results['xy_std'] = uc.set_in_units(thermo.Xy.std(), lammps_units['length']) results['xz'] = uc.set_in_units(thermo.Xz.mean(), lammps_units['length']) results['xz_std'] = uc.set_in_units(thermo.Xz.std(), lammps_units['length']) results['yz'] = uc.set_in_units(thermo.Yz.mean(), lammps_units['length']) results['yz_std'] = uc.set_in_units(thermo.Yz.std(), lammps_units['length']) results['measured_pxx'] = uc.set_in_units(thermo.Pxx.mean(), lammps_units['pressure']) results['measured_pxx_std'] = uc.set_in_units(thermo.Pxx.std(), lammps_units['pressure']) results['measured_pyy'] = uc.set_in_units(thermo.Pyy.mean(), lammps_units['pressure']) results['measured_pyy_std'] = uc.set_in_units(thermo.Pyy.std(), lammps_units['pressure']) results['measured_pzz'] = uc.set_in_units(thermo.Pzz.mean(), lammps_units['pressure']) results['measured_pzz_std'] = uc.set_in_units(thermo.Pzz.std(), lammps_units['pressure']) results['measured_pxy'] = uc.set_in_units(thermo.Pxy.mean(), lammps_units['pressure']) results['measured_pxy_std'] = uc.set_in_units(thermo.Pxy.std(), lammps_units['pressure']) results['measured_pxz'] = uc.set_in_units(thermo.Pxz.mean(), lammps_units['pressure']) results['measured_pxz_std'] = uc.set_in_units(thermo.Pxz.std(), lammps_units['pressure']) results['measured_pyz'] = uc.set_in_units(thermo.Pyz.mean(), lammps_units['pressure']) results['measured_pyz_std'] = uc.set_in_units(thermo.Pyz.std(), lammps_units['pressure']) results['temp'] = thermo.Temp.mean() results['temp_std'] = thermo.Temp.std() return results
def disl_relax( lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None, annealtemp: float = 0.0, annealsteps: Optional[int] = None, randomseed: Optional[int] = None, etol: float = 0.0, ftol: float = 1e-6, maxiter: int = 10000, maxeval: int = 100000, dmax: float = uc.set_in_units(0.01, 'angstrom') ) -> dict: """ Sets up and runs the disl_relax.in LAMMPS script for relaxing a dislocation monopole system. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. annealtemp : float, optional The temperature to perform a dynamic relaxation at. Default is 0.0, which will skip the dynamic relaxation. annealsteps : int, optional The number of time steps to run the dynamic relaxation for. Default is None, which will run for 10000 steps if annealtemp is not 0.0. randomseed : int or None, optional Random number seed used by LAMMPS in creating velocities and with the Langevin thermostat. Default is None which will select a random int between 1 and 900000000. etol : float, optional The energy tolerance for the structure minimization. This value is unitless. Default is 0.0. ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. Default is 0.0. maxiter : int, optional The maximum number of minimization iterations to use default is 10000. maxeval : int, optional The maximum number of minimization evaluations to use default is 100000. dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration default is 0.01 Angstroms. Returns ------- dict Dictionary of results consisting of keys: - **'logfile'** (*str*) - The name of the LAMMPS log file. - **'dumpfile'** (*str*) - The name of the LAMMPS dump file for the relaxed system. - **'E_total'** (*float*) - The total potential energy for the relaxed system. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='system.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info lammps_variables['anneal_info'] = anneal_info(annealtemp, annealsteps, randomseed, potential.units) lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = dmax lammps_variables['group_move'] = ' '.join( np.array(range(1, system.natypes // 2 + 1), dtype=str)) # Set dump_modify format based on dump_modify_version if lammps_date < datetime.date(2016, 8, 3): lammps_variables[ 'dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script lammps_script = 'disl_relax.in' template = read_calc_file('iprPy.calculation.dislocation_monopole', 'disl_relax.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) thermo = output.simulations[-1]['thermo'] # Extract output values results = {} results['logfile'] = 'log.lammps' results['dumpfile'] = '%i.dump' % thermo.Step.values[-1] results['E_total'] = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) return results
def check_ptd_config( system: am.System, point_kwargs: Union[list, dict], cutoff: float, tol: float = uc.set_in_units(1e-5, 'angstrom')) -> dict: """ Evaluates a relaxed system containing a point defect to determine if the defect structure has transformed to a different configuration. Parameters ---------- system : atomman.System The relaxed defect system. point_kwargs : dict or list of dict One or more dictionaries containing the keyword arguments for the atomman.defect.point() function to generate specific point defect configuration(s). cutoff : float Cutoff distance to use in identifying neighbor atoms. tol : float, optional Absolute tolerance to use for identifying if a defect has reconfigured (default is 1e-5 Angstoms). Returns ------- dict Dictionary of results consisting of keys: - **'has_reconfigured'** (*bool*) - Flag indicating if the structure has been identified as relaxing to a different defect configuration. - **'centrosummation'** (*numpy.ndarray of float*) - The centrosummation parameter used for evaluating if the configuration has relaxed. - **'position_shift'** (*numpy.ndarray of float*) - The position_shift parameter used for evaluating if the configuration has relaxed. Only given for interstitial and substitutional-style defects. - **'db_vect_shift'** (*numpy.ndarray of float*) - The db_vect_shift parameter used for evaluating if the configuration has relaxed. Only given for dumbbell-style defects. """ # Check if point_kwargs is a list if not isinstance(point_kwargs, (list, tuple)): pos = point_kwargs['pos'] # If it is a list of 1, use that set elif len(point_kwargs) == 1: point_kwargs = point_kwargs[0] pos = point_kwargs['pos'] # If it is a list of two (divacancy), use the first and average position elif len(point_kwargs) == 2: pos = (np.array(point_kwargs[0]['pos']) + np.array(point_kwargs[1]['pos'])) / 2 point_kwargs = point_kwargs[0] # More than two not supported by this function else: raise ValueError('Invalid point defect parameters') # Initially set has_reconfigured to False has_reconfigured = False # Calculate distance of all atoms from defect position pos_vects = system.dvect(system.atoms.pos, pos) pos_mags = np.linalg.norm(pos_vects, axis=1) # Calculate centrosummation by summing up the positions of the close atoms centrosummation = np.sum(pos_vects[pos_mags < cutoff], axis=0) if not np.allclose(centrosummation, np.zeros(3), atol=tol): has_reconfigured = True # Calculate shift of defect atom's position if interstitial or substitutional if point_kwargs['ptd_type'] == 'i' or point_kwargs['ptd_type'] == 's': position_shift = system.dvect(system.natoms - 1, pos) if not np.allclose(position_shift, np.zeros(3), atol=tol): has_reconfigured = True return { 'has_reconfigured': has_reconfigured, 'centrosummation': centrosummation, 'position_shift': position_shift } # Investigate if dumbbell vector has shifted direction elif point_kwargs['ptd_type'] == 'db': db_vect = point_kwargs['db_vect'] / np.linalg.norm( point_kwargs['db_vect']) new_db_vect = system.dvect(-2, -1) new_db_vect = new_db_vect / np.linalg.norm(new_db_vect) db_vect_shift = db_vect - new_db_vect if not np.allclose(db_vect_shift, np.zeros(3), atol=tol): has_reconfigured = True return { 'has_reconfigured': has_reconfigured, 'centrosummation': centrosummation, 'db_vect_shift': db_vect_shift } else: return { 'has_reconfigured': has_reconfigured, 'centrosummation': centrosummation }
def pointdefect( lammps_command: str, system: am.System, potential: lmp.Potential, point_kwargs: Union[list, dict], mpi_command: Optional[str] = None, etol: float = 0.0, ftol: float = 0.0, maxiter: int = 10000, maxeval: int = 100000, dmax: float = uc.set_in_units(0.01, 'angstrom') ) -> dict: """ Adds one or more point defects to a system and evaluates the defect formation energy. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. point_kwargs : dict or list of dict One or more dictionaries containing the keyword arguments for the atomman.defect.point() function to generate specific point defect configuration(s). mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. sim_directory : str, optional The path to the directory to perform the simulation in. If not given, will use the current working directory. etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). Returns ------- dict Dictionary of results consisting of keys: - **'E_pot'** (*float*) - The per-atom potential energy of the bulk system. - **'E_ptd_f'** (*float*) - The point defect formation energy. - **'E_total_base'** (*float*) - The total potential energy of the relaxed bulk system. - **'E_total_ptd'** (*float*) - The total potential energy of the relaxed defect system. - **'pij_tensor'** (*numpy.ndarray of float*) - The elastic dipole tensor associated with the defect. - **'system_base'** (*atomman.System*) - The relaxed bulk system. - **'system_ptd'** (*atomman.System*) - The relaxed defect system. - **'dumpfile_base'** (*str*) - The filename of the LAMMPS dump file for the relaxed bulk system. - **'dumpfile_ptd'** (*str*) - The filename of the LAMMPS dump file for the relaxed defect system. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='perfect.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = dmax # Set dump_modify_format based on lammps_date if lammps_date < datetime.date(2016, 8, 3): lammps_variables[ 'dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script lammps_script = 'min.in' template = read_calc_file('iprPy.calculation.point_defect_static', 'min.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run lammps to relax perfect.dat output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) # Extract LAMMPS thermo data. thermo = output.simulations[0]['thermo'] E_total_base = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) E_pot = E_total_base / system.natoms pxx = uc.set_in_units(thermo.Pxx.values[-1], lammps_units['pressure']) pyy = uc.set_in_units(thermo.Pyy.values[-1], lammps_units['pressure']) pzz = uc.set_in_units(thermo.Pzz.values[-1], lammps_units['pressure']) pxy = uc.set_in_units(thermo.Pxy.values[-1], lammps_units['pressure']) pxz = uc.set_in_units(thermo.Pxz.values[-1], lammps_units['pressure']) pyz = uc.set_in_units(thermo.Pyz.values[-1], lammps_units['pressure']) pressure_base = np.array([[pxx, pxy, pxz], [pxy, pyy, pyz], [pxz, pyz, pzz]]) # Rename log file shutil.move('log.lammps', 'min-perfect-log.lammps') # Load relaxed system from dump file and copy old box vectors because # dump files crop the values. last_dump_file = 'atom.' + str(thermo.Step.values[-1]) system_base = am.load('atom_dump', last_dump_file, symbols=system.symbols) system_base.box_set(vects=system.box.vects) system_base.dump('atom_dump', f='perfect.dump') # Add defect(s) system_ptd = deepcopy(system_base) if not isinstance(point_kwargs, (list, tuple)): point_kwargs = [point_kwargs] for pkwargs in point_kwargs: system_ptd = am.defect.point(system_ptd, **pkwargs) # Update lammps variables system_info = system_ptd.dump('atom_data', f='defect.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info # Write lammps input script with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run lammps output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) # Extract lammps thermo data thermo = output.simulations[0]['thermo'] E_total_ptd = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) pxx = uc.set_in_units(thermo.Pxx.values[-1], lammps_units['pressure']) pyy = uc.set_in_units(thermo.Pyy.values[-1], lammps_units['pressure']) pzz = uc.set_in_units(thermo.Pzz.values[-1], lammps_units['pressure']) pxy = uc.set_in_units(thermo.Pxy.values[-1], lammps_units['pressure']) pxz = uc.set_in_units(thermo.Pxz.values[-1], lammps_units['pressure']) pyz = uc.set_in_units(thermo.Pyz.values[-1], lammps_units['pressure']) pressure_ptd = np.array([[pxx, pxy, pxz], [pxy, pyy, pyz], [pxz, pyz, pzz]]) # Rename log file shutil.move('log.lammps', 'min-defect-log.lammps') # Load relaxed system from dump file and copy old vects as # the dump files crop the values last_dump_file = 'atom.' + str(thermo.Step.values[-1]) system_ptd = am.load('atom_dump', last_dump_file, symbols=system_ptd.symbols) system_ptd.box_set(vects=system.box.vects) system_ptd.dump('atom_dump', f='defect.dump') # Compute defect formation energy E_ptd_f = E_total_ptd - E_pot * system_ptd.natoms # Compute strain tensor pij = -(pressure_base - pressure_ptd) * system_base.box.volume # Cleanup files for fname in Path.cwd().glob('atom.*'): fname.unlink() for dumpjsonfile in Path.cwd().glob('*.dump.json'): dumpjsonfile.unlink() # Return results results_dict = {} results_dict['E_pot'] = E_pot results_dict['E_ptd_f'] = E_ptd_f results_dict['E_total_base'] = E_total_base results_dict['E_total_ptd'] = E_total_ptd results_dict['pij_tensor'] = pij results_dict['system_base'] = system_base results_dict['system_ptd'] = system_ptd results_dict['dumpfile_base'] = 'perfect.dump' results_dict['dumpfile_ptd'] = 'defect.dump' return results_dict
def elastic_constants_static( lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None, strainrange: float = 1e-6, etol: float = 0.0, ftol: float = 0.0, maxiter: int = 10000, maxeval: int = 100000, dmax: float = uc.set_in_units(0.01, 'angstrom') ) -> dict: """ Repeatedly runs the ELASTIC example distributed with LAMMPS until box dimensions converge within a tolerance. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. strainrange : float, optional The small strain value to apply when calculating the elastic constants (default is 1e-6). etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). Returns ------- dict Dictionary of results consisting of keys: - **'raw_Cij_negative'** (*numpy.ndarray*) - The values of Cij obtained from only the negative strains. - **'raw_Cij_positive'** (*numpy.ndarray*) - The values of Cij obtained from only the positive strains. - **'C'** (*atomman.ElasticConstants*) - The computed elastic constants obtained from averaging the negative and positive strain values. """ # Convert hexagonal cells to orthorhombic to avoid LAMMPS tilt issues if am.tools.ishexagonal(system.box): system = system.rotate([[2, -1, -1, 0], [0, 1, -1, 0], [0, 0, 0, 1]]) # Get lammps units lammps_units = lmp.style.unit(potential.units) # Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='init.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info lammps_variables['restart_commands'] = restart_commands( potential, system.symbols) lammps_variables['strainrange'] = strainrange lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = uc.get_in_units(dmax, lammps_units['length']) # Fill in template files lammps_script = 'cij.in' template = read_calc_file('iprPy.calculation.elastic_constants_static', 'cij.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) # Pull out initial state thermo = output.simulations[0]['thermo'] pxx0 = uc.set_in_units(thermo.Pxx.values[-1], lammps_units['pressure']) pyy0 = uc.set_in_units(thermo.Pyy.values[-1], lammps_units['pressure']) pzz0 = uc.set_in_units(thermo.Pzz.values[-1], lammps_units['pressure']) pyz0 = uc.set_in_units(thermo.Pyz.values[-1], lammps_units['pressure']) pxz0 = uc.set_in_units(thermo.Pxz.values[-1], lammps_units['pressure']) pxy0 = uc.set_in_units(thermo.Pxy.values[-1], lammps_units['pressure']) # Negative strains cij_n = np.empty((6, 6)) for i in range(6): j = 1 + i * 2 # Pull out strained state thermo = output.simulations[j]['thermo'] pxx = uc.set_in_units(thermo.Pxx.values[-1], lammps_units['pressure']) pyy = uc.set_in_units(thermo.Pyy.values[-1], lammps_units['pressure']) pzz = uc.set_in_units(thermo.Pzz.values[-1], lammps_units['pressure']) pyz = uc.set_in_units(thermo.Pyz.values[-1], lammps_units['pressure']) pxz = uc.set_in_units(thermo.Pxz.values[-1], lammps_units['pressure']) pxy = uc.set_in_units(thermo.Pxy.values[-1], lammps_units['pressure']) # Calculate cij_n using stress changes cij_n[i] = np.array([ pxx - pxx0, pyy - pyy0, pzz - pzz0, pyz - pyz0, pxz - pxz0, pxy - pxy0 ]) / strainrange # Positive strains cij_p = np.empty((6, 6)) for i in range(6): j = 2 + i * 2 # Pull out strained state thermo = output.simulations[j]['thermo'] pxx = uc.set_in_units(thermo.Pxx.values[-1], lammps_units['pressure']) pyy = uc.set_in_units(thermo.Pyy.values[-1], lammps_units['pressure']) pzz = uc.set_in_units(thermo.Pzz.values[-1], lammps_units['pressure']) pyz = uc.set_in_units(thermo.Pyz.values[-1], lammps_units['pressure']) pxz = uc.set_in_units(thermo.Pxz.values[-1], lammps_units['pressure']) pxy = uc.set_in_units(thermo.Pxy.values[-1], lammps_units['pressure']) # Calculate cij_p using stress changes cij_p[i] = np.array([ pxx - pxx0, pyy - pyy0, pzz - pzz0, pyz - pyz0, pxz - pxz0, pxy - pxy0 ]) / -strainrange # Average symmetric values cij = (cij_n + cij_p) / 2 for i in range(6): for j in range(i): cij[i, j] = cij[j, i] = (cij[i, j] + cij[j, i]) / 2 # Define results_dict results_dict = {} results_dict['raw_Cij_negative'] = cij_n results_dict['raw_Cij_positive'] = cij_p results_dict['C'] = am.ElasticConstants(Cij=cij) return results_dict
def phonon_quasiharmonic(lammps_command: str, ucell: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None, a_mult: int = 3, b_mult: int = 3, c_mult: int = 3, distance: float = 0.01, symprec: float = 1e-5, strainrange: float = 0.01, numstrains: int = 5) -> dict: """ Function that performs phonon and quasiharmonic approximation calculations using phonopy and LAMMPS. Parameters ---------- lammps_command :str Command for running LAMMPS. ucell : atomman.System The unit cell system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. a_mult : int, optional The a size multiplier to use on ucell before running the phonon calculation. Must be an int and not a tuple. Default value is 3. b_mult : int, optional The b size multiplier to use on ucell before running the phonon calculation. Must be an int and not a tuple. Default value is 3. c_mult : int, optional The c size multiplier to use on ucell before running the phonon calculation. Must be an int and not a tuple. Default value is 3. distance : float, optional The atomic displacement distance used for computing the phonons. Default value is 0.01. symprec : float, optional Absolute length tolerance to use in identifying symmetry of atomic sites and system boundaries. Default value is 1e-5. strainrange : float, optional The range of strains to apply to the unit cell to use with the quasiharmonic calculations. Default value is 0.01. numstrains : int, optional The number of strains to use for the quasiharmonic calculations. Must be an odd integer. If 1, then the quasiharmonic calculations will not be performed. Default value is 5. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) # Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Get original box vectors vects = ucell.box.vects # Generate the range of strains if numstrains == 1: zerostrain = phononcalc(lammps_command, ucell, potential, mpi_command=mpi_command, a_mult=a_mult, b_mult=b_mult, c_mult=c_mult, distance=distance, symprec=symprec, lammps_date=lammps_date) phonons = [zerostrain['phonon']] qha = None elif numstrains % 2 == 0 or numstrains < 5: raise ValueError( 'Invalid number of strains: must be odd and 1 or >= 5') else: strains = np.linspace(-strainrange, strainrange, numstrains) istrains = np.linspace(-(numstrains - 1) / 2, (numstrains - 1) / 2, numstrains, dtype=int) volumes = [] energies = [] phonons = [] temperatures = None free_energy = None heat_capacity = None entropy = None # Loop over all strains for istrain, strain in zip(istrains, strains): # Identify the zero strain run if istrain == 0: zerostrainrun = True savefile = 'phonopy_params.yaml' else: zerostrainrun = False savefile = f'phonopy_params_{istrain}.yaml' # Generate system at the strain newvects = vects * (1 + strain) ucell.box_set(vects=newvects, scale=True) volumes.append(ucell.box.volume) system = ucell.supersize(a_mult, b_mult, c_mult) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='disp.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info # Set dump_modify_format based on lammps_date if lammps_date < datetime.date(2016, 8, 3): lammps_variables[ 'dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script lammps_script = 'phonon.in' template = read_calc_file('iprPy.calculation.phonon', 'phonon.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS output = lmp.run(lammps_command, script_name='phonon.in', mpi_command=mpi_command) # Extract system energy thermo = output.simulations[0]['thermo'] energy = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) # Scale energy by sizemults and append to list energies.append(energy / (a_mult * b_mult * c_mult)) # Compute phonon info for ucell phononinfo = phononcalc(lammps_command, ucell, potential, mpi_command=mpi_command, a_mult=a_mult, b_mult=b_mult, c_mult=c_mult, distance=distance, symprec=symprec, savefile=savefile, plot=zerostrainrun, lammps_date=lammps_date) phonons.append(phononinfo['phonon']) # Extract temperature values from the first run if temperatures is None: temperatures = phononinfo['thermal_properties']['temperatures'] # Initialize QHA input arrays free_energy = np.empty((len(temperatures), len(strains))) heat_capacity = np.empty((len(temperatures), len(strains))) entropy = np.empty((len(temperatures), len(strains))) # Get values for zerostrainrun if zerostrainrun is True: zerostrain = phononinfo # Copy values to qha input arrays free_energy[:, istrain] = phononinfo['thermal_properties'][ 'free_energy'] entropy[:, istrain] = phononinfo['thermal_properties']['entropy'] heat_capacity[:, istrain] = phononinfo['thermal_properties'][ 'heat_capacity'] # Compute qha try: qha = phonopy.PhonopyQHA(volumes=volumes, electronic_energies=energies, temperatures=temperatures, free_energy=free_energy, cv=heat_capacity, entropy=entropy) except: qha = None results = {} # Add phonopy objects results['phonon_objects'] = phonons results['qha_object'] = qha # Extract zerostrain properties results['band_structure'] = zerostrain['band_structure'] results['density_of_states'] = zerostrain['dos'] # Convert units on thermal properties results['thermal_properties'] = zerostrain['thermal_properties'] results['thermal_properties']['temperature'] = results[ 'thermal_properties'].pop('temperatures') results['thermal_properties']['Helmholtz'] = uc.set_in_units( results['thermal_properties'].pop('free_energy'), 'kJ/mol') results['thermal_properties']['entropy'] = uc.set_in_units( results['thermal_properties'].pop('entropy'), 'J/K/mol') results['thermal_properties']['heat_capacity_v'] = uc.set_in_units( results['thermal_properties'].pop('heat_capacity'), 'J/K/mol') if qha is not None: # Create QHA plots qha.plot_bulk_modulus() plt.xlabel('Volume ($Å^3$)', size='large') plt.ylabel('Energy ($eV$)', size='large') plt.savefig('bulk_modulus.png', dpi=400, bbox_inches='tight') plt.close() qha.plot_helmholtz_volume() plt.savefig('helmholtz_volume.png', dpi=400) plt.close() # Package volume vs energy scans results['volume_scan'] = {} results['volume_scan']['volume'] = np.array(volumes) results['volume_scan']['strain'] = strains results['volume_scan']['energy'] = np.array(energies) # Compute and add QHA properties properties = qha.get_bulk_modulus_parameters() results['E0'] = uc.set_in_units(properties[0], 'eV') results['B0'] = uc.set_in_units(properties[1], 'eV/angstrom^3') results['B0prime'] = uc.set_in_units(properties[2], 'eV/angstrom^3') results['V0'] = uc.set_in_units(properties[3], 'angstrom^3') results['thermal_properties']['volume'] = uc.set_in_units( np.hstack([qha.volume_temperature, np.nan]), 'angstrom^3') results['thermal_properties']['thermal_expansion'] = np.hstack( [qha.thermal_expansion, np.nan]) results['thermal_properties']['Gibbs'] = uc.set_in_units( np.hstack([qha.gibbs_temperature, np.nan]), 'eV') results['thermal_properties']['bulk_modulus'] = uc.set_in_units( np.hstack([qha.bulk_modulus_temperature, np.nan]), 'GPa') results['thermal_properties'][ 'heat_capacity_p_numerical'] = uc.set_in_units( np.hstack([qha.heat_capacity_P_numerical, np.nan]), 'J/K/mol') results['thermal_properties'][ 'heat_capacity_p_polyfit'] = uc.set_in_units( np.hstack([qha.heat_capacity_P_polyfit, np.nan]), 'J/K/mol') results['thermal_properties']['gruneisen'] = np.hstack( [qha.gruneisen_temperature, np.nan]) return results
def relax_box(lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None, strainrange: float = 1e-6, p_xx: float = 0.0, p_yy: float = 0.0, p_zz: float = 0.0, p_xy: float = 0.0, p_xz: float = 0.0, p_yz: float = 0.0, tol: float = 1e-10, diverge_scale: float = 3.0) -> dict: """ Quickly refines static orthorhombic system by evaluating the elastic constants and the virial pressure. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. strainrange : float, optional The small strain value to apply when calculating the elastic constants (default is 1e-6). p_xx : float, optional The value to relax the x tensile pressure component to (default is 0.0). p_yy : float, optional The value to relax the y tensile pressure component to (default is 0.0). p_zz : float, optional The value to relax the z tensile pressure component to (default is 0.0). p_xy : float, optional The value to relax the xy shear pressure component to (default is 0.0). p_xz : float, optional The value to relax the xz shear pressure component to (default is 0.0). p_yz : float, optional The value to relax the yz shear pressure component to (default is 0.0). tol : float, optional The relative tolerance used to determine if the lattice constants have converged (default is 1e-10). diverge_scale : float, optional Factor to identify if the system's dimensions have diverged. Divergence is identified if either any current box dimension is greater than the original dimension multiplied by diverge_scale, or if any current box dimension is less than the original dimension divided by diverge_scale. (Default is 3.0). Returns ------- dict Dictionary of results consisting of keys: - **'dumpfile_initial'** (*str*) - The name of the initial dump file created. - **'symbols_initial'** (*list*) - The symbols associated with the initial dump file. - **'dumpfile_final'** (*str*) - The name of the final dump file created. - **'symbols_final'** (*list*) - The symbols associated with the final dump file. - **'lx'** (*float*) - The relaxed lx box length. - **'ly'** (*float*) - The relaxed ly box length. - **'lz'** (*float*) - The relaxed lz box length. - **'xy'** (*float*) - The relaxed xy box tilt. - **'xz'** (*float*) - The relaxed xz box tilt. - **'yz'** (*float*) - The relaxed yz box tilt. - **'E_pot'** (*float*) - The potential energy per atom for the final configuration. - **'measured_pxx'** (*float*) - The measured x tensile pressure of the relaxed system. - **'measured_pyy'** (*float*) - The measured y tensile pressure of the relaxed system. - **'measured_pzz'** (*float*) - The measured z tensile pressure of the relaxed system. - **'measured_pxy'** (*float*) - The measured xy shear pressure of the relaxed system. - **'measured_pxz'** (*float*) - The measured xz shear pressure of the relaxed system. - **'measured_pyz'** (*float*) - The measured yz shear pressure of the relaxed system. Raises ------ RuntimeError If system diverges or no convergence reached after 100 cycles. """ # Flag for if values have converged converged = False # Define current and old systems system_current = deepcopy(system) system_old = None system.dump('atom_dump', f='initial.dump') for cycle in range(100): # Run LAMMPS and evaluate results based on system_old results = cij_run0(lammps_command, system_current, potential, mpi_command=mpi_command, strainrange=strainrange, cycle=cycle) pij = results['pij'] Cij = results['C'].Cij system_new = update_box(system_current, results['C'], results['pij'], p_xx, p_yy, p_zz, p_xy, p_xz, p_yz, tol) # Compare new and current to test for convergence if np.allclose(system_new.box.vects, system_current.box.vects, rtol=tol, atol=0): converged = True break # Compare old and new to test for double-value convergence elif system_old is not None and np.allclose( system_new.box.vects, system_old.box.vects, rtol=tol, atol=0): # Update current to average of old and new system_current.box_set( a=(system_new.box.a + system_old.box.a) / 2., b=(system_new.box.b + system_old.box.b) / 2., c=(system_new.box.c + system_old.box.c) / 2., scale=True) # Calculate Cij for the averaged system results = cij_run0(lammps_command, system_current, potential, mpi_command=mpi_command, strainrange=strainrange, cycle=cycle) system_new = update_box(system_current, results['C'], results['pij'], p_xx, p_yy, p_zz, p_xy, p_xz, p_yz, tol) converged = True break # Test for divergence elif system_new.box.a < system.box.a / diverge_scale: raise RuntimeError('Divergence of box dimensions') elif system_new.box.a > system.box.a * diverge_scale: raise RuntimeError('Divergence of box dimensions') elif system_new.box.b < system.box.b / diverge_scale: raise RuntimeError('Divergence of box dimensions') elif system_new.box.b > system.box.b * diverge_scale: raise RuntimeError('Divergence of box dimensions') elif system_new.box.c < system.box.c / diverge_scale: raise RuntimeError('Divergence of box dimensions') elif system_new.box.c > system.box.c * diverge_scale: raise RuntimeError('Divergence of box dimensions') elif results['E_pot'] == 0.0: raise RuntimeError('Divergence: potential energy is 0') # If not converged or diverged, current -> old and new -> current else: system_old, system_current = system_current, system_new # Return values when converged if converged: system_new.dump('atom_dump', f='final.dump') # Build results_dict results_dict = {} results_dict['dumpfile_initial'] = 'initial.dump' results_dict['symbols_initial'] = system.symbols results_dict['dumpfile_final'] = 'final.dump' results_dict['symbols_final'] = system.symbols results_dict['lx'] = system_new.box.lx results_dict['ly'] = system_new.box.ly results_dict['lz'] = system_new.box.lz results_dict['xy'] = system_new.box.xy results_dict['xz'] = system_new.box.xz results_dict['yz'] = system_new.box.yz results_dict['E_pot'] = results['E_pot'] results_dict['measured_pxx'] = results['pij'][0, 0] results_dict['measured_pyy'] = results['pij'][1, 1] results_dict['measured_pzz'] = results['pij'][2, 2] results_dict['measured_pxy'] = results['pij'][0, 1] results_dict['measured_pxz'] = results['pij'][0, 2] results_dict['measured_pyz'] = results['pij'][1, 2] return results_dict else: raise RuntimeError('Failed to converge after 100 cycles')
def cij_run0(lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None, strainrange: float = 1e-6, cycle: int = 0) -> dict: """ Runs cij_run0.in LAMMPS script to evaluate the elastic constants, pressure and potential energy of the current system. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. strainrange : float, optional The small strain value to apply when calculating the elastic constants (default is 1e-6). cycle : int, optional Indicates the iteration cycle of quick_a_Cij(). This is used to uniquely save the LAMMPS input and output files. Returns ------- dict Dictionary of results consisting of keys: - **'E_pot'** (*float*) - The potential energy per atom for the supplied system. - **'pressure'** (*numpy.array*) - The measured pressure state of the supplied system. - **'C_elastic'** (*atomman.ElasticConstants*) - The supplied system's elastic constants. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='init.dat', potential=potential) restart_info = potential.pair_restart_info('initial.restart', system.symbols) lammps_variables['pair_data_info'] = system_info lammps_variables['pair_restart_info'] = restart_info lammps_variables['strainrange'] = strainrange # Write lammps input script lammps_script = 'cij_run0.in' template = read_calc_file('iprPy.calculation.relax_box', 'cij_run0.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run lammps output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command, logfile=f'cij-{cycle}-log.lammps') thermo = output.flatten('all').thermo # Extract LAMMPS thermo data. Each term ranges i=0-12 where i=0 is undeformed # The remaining values are for -/+ strain pairs in the six unique directions lx = uc.set_in_units(thermo.Lx, lammps_units['length']) ly = uc.set_in_units(thermo.Ly, lammps_units['length']) lz = uc.set_in_units(thermo.Lz, lammps_units['length']) xy = uc.set_in_units(thermo.Xy, lammps_units['length']) xz = uc.set_in_units(thermo.Xz, lammps_units['length']) yz = uc.set_in_units(thermo.Yz, lammps_units['length']) pxx = uc.set_in_units(thermo.Pxx, lammps_units['pressure']) pyy = uc.set_in_units(thermo.Pyy, lammps_units['pressure']) pzz = uc.set_in_units(thermo.Pzz, lammps_units['pressure']) pxy = uc.set_in_units(thermo.Pxy, lammps_units['pressure']) pxz = uc.set_in_units(thermo.Pxz, lammps_units['pressure']) pyz = uc.set_in_units(thermo.Pyz, lammps_units['pressure']) pe = uc.set_in_units(thermo.PotEng / system.natoms, lammps_units['energy']) # Extract the pressure tensor pij = np.array([[pxx[0], pxy[0], pxz[0]], [pxy[0], pyy[0], pyz[0]], [pxz[0], pyz[0], pzz[0]]]) # Set the six non-zero strain values strains = np.array([(lx[2] - lx[1]) / lx[0], (ly[4] - ly[3]) / ly[0], (lz[6] - lz[5]) / lz[0], (yz[8] - yz[7]) / lz[0], (xz[10] - xz[9]) / lz[0], (xy[12] - xy[11]) / ly[0]]) # Calculate cij using stress changes associated with each non-zero strain cij = np.empty((6, 6)) for i in range(6): delta_stress = np.array([ pxx[2 * i + 1] - pxx[2 * i + 2], pyy[2 * i + 1] - pyy[2 * i + 2], pzz[2 * i + 1] - pzz[2 * i + 2], pyz[2 * i + 1] - pyz[2 * i + 2], pxz[2 * i + 1] - pxz[2 * i + 2], pxy[2 * i + 1] - pxy[2 * i + 2] ]) cij[i] = delta_stress / strains[i] for i in range(6): for j in range(i): cij[i, j] = cij[j, i] = (cij[i, j] + cij[j, i]) / 2 C = am.ElasticConstants(Cij=cij) results = {} results['E_pot'] = pe[0] results['pij'] = pij results['C'] = C return results
def stackingfaultrelax(lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None, sim_directory: Optional[str] = None, cutboxvector: str = 'c', etol: float = 0.0, ftol: float = 0.0, maxiter: int = 10000, maxeval: int = 100000, dmax: float = uc.set_in_units(0.01, 'angstrom'), lammps_date: Optional[datetime.date] = None) -> dict: """ Perform a stacking fault relaxation simulation for a single faultshift. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system containing a stacking fault. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. sim_directory : str, optional The path to the directory to perform the simulation in. If not given, will use the current working directory. cutboxvector : str, optional Indicates which of the three system box vectors, 'a', 'b', or 'c', has the non-periodic boundary (default is 'c'). Fault plane normal is defined by the cross of the other two box vectors. etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). lammps_date : datetime.date or None, optional The date version of the LAMMPS executable. If None, will be identified from the lammps_command (default is None). Returns ------- dict Dictionary of results consisting of keys: - **'logfile'** (*str*) - The filename of the LAMMPS log file. - **'dumpfile'** (*str*) - The filename of the LAMMPS dump file of the relaxed system. - **'system'** (*atomman.System*) - The relaxed system. - **'E_total'** (*float*) - The total potential energy of the relaxed system. Raises ------ ValueError For invalid cutboxvectors. """ # Give correct LAMMPS fix setforce command if cutboxvector == 'a': fix_cut_setforce = 'fix cut all setforce NULL 0 0' elif cutboxvector == 'b': fix_cut_setforce = 'fix cut all setforce 0 NULL 0' elif cutboxvector == 'c': fix_cut_setforce = 'fix cut all setforce 0 0 NULL' else: raise ValueError('Invalid cutboxvector') if sim_directory is not None: # Create sim_directory if it doesn't exist sim_directory = Path(sim_directory) if not sim_directory.is_dir(): sim_directory.mkdir() sim_directory = sim_directory.as_posix() + '/' else: # Set sim_directory if is None sim_directory = '' # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date if lammps_date is None: lammps_date = lmp.checkversion(lammps_command)['date'] # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f=Path(sim_directory, 'system.dat').as_posix(), potential=potential) lammps_variables['atomman_system_pair_info'] = system_info lammps_variables['fix_cut_setforce'] = fix_cut_setforce lammps_variables['sim_directory'] = sim_directory lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = uc.get_in_units(dmax, lammps_units['length']) # Set dump_modify format based on dump_modify_version if lammps_date < datetime.date(2016, 8, 3): lammps_variables[ 'dump_modify_format'] = '"%i %i %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script lammps_script = Path(sim_directory, 'sfmin.in') template = read_calc_file('iprPy.calculation.stacking_fault_map_2D', 'sfmin.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS output = lmp.run(lammps_command, script_name=lammps_script.as_posix(), mpi_command=mpi_command, logfile=Path(sim_directory, 'log.lammps').as_posix()) # Extract output values thermo = output.simulations[-1]['thermo'] logfile = Path(sim_directory, 'log.lammps').as_posix() dumpfile = Path(sim_directory, '%i.dump' % thermo.Step.values[-1]).as_posix() E_total = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) # Load relaxed system system = am.load('atom_dump', dumpfile, symbols=system.symbols) # Return results results_dict = {} results_dict['logfile'] = logfile results_dict['dumpfile'] = dumpfile results_dict['system'] = system results_dict['E_total'] = E_total return results_dict
def relax_system(lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None, etol: float = 0.0, ftol: float = 0.0, maxiter: int = 10000, maxeval: int = 100000, dmax: float = uc.set_in_units(0.01, 'angstrom'), lammps_date: Optional[datetime.date] = None) -> dict: """ Sets up and runs the min.in LAMMPS script for performing an energy/force minimization to relax a system. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). lammps_date : datetime.date or None, optional The date version of the LAMMPS executable. If None, will be identified from the lammps_command (default is None). Returns ------- dict Dictionary of results consisting of keys: - **'logfile'** (*str*) - The name of the LAMMPS log file. - **'initialdatafile'** (*str*) - The name of the LAMMPS data file used to import an inital configuration. - **'initialdumpfile'** (*str*) - The name of the LAMMPS dump file corresponding to the inital configuration. - **'finaldumpfile'** (*str*) - The name of the LAMMPS dump file corresponding to the relaxed configuration. - **'potentialenergy'** (*float*) - The total potential energy of the relaxed system. """ # Ensure all atoms are within the system's box system.wrap() # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date if lammps_date is None: lammps_date = lmp.checkversion(lammps_command)['date'] # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='system.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = uc.get_in_units(dmax, lammps_units['length']) # Set dump_modify format based on dump_modify_version if lammps_date < datetime.date(2016, 8, 3): lammps_variables[ 'dump_modify_format'] = '"%i %i %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script template_file = 'min.template' lammps_script = 'min.in' template = read_calc_file('iprPy.calculation.surface_energy_static', 'min.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) # Extract output values thermo = output.simulations[-1]['thermo'] results = {} results['logfile'] = 'log.lammps' results['initialdatafile'] = 'system.dat' results['initialdumpfile'] = 'atom.0' results['finaldumpfile'] = 'atom.%i' % thermo.Step.values[-1] results['potentialenergy'] = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) return results
def relax_static(lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None, p_xx: float = 0.0, p_yy: float = 0.0, p_zz: float = 0.0, p_xy: float = 0.0, p_xz: float = 0.0, p_yz: float = 0.0, dispmult: float = 0.0, etol: float = 0.0, ftol: float = 0.0, maxiter: int = 100000, maxeval: int = 1000000, dmax: float = uc.set_in_units(0.01, 'angstrom'), maxcycles: int = 100, ctol: float = 1e-10) -> dict: """ Repeatedly runs the ELASTIC example distributed with LAMMPS until box dimensions converge within a tolerance. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. p_xx : float, optional The value to relax the x tensile pressure component to (default is 0.0). p_yy : float, optional The value to relax the y tensile pressure component to (default is 0.0). p_zz : float, optional The value to relax the z tensile pressure component to (default is 0.0). p_xy : float, optional The value to relax the xy shear pressure component to (default is 0.0). p_xz : float, optional The value to relax the xz shear pressure component to (default is 0.0). p_yz : float, optional The value to relax the yz shear pressure component to (default is 0.0). dispmult : float, optional Multiplier for applying a random displacement to all atomic positions prior to relaxing. Default value is 0.0. etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). pressure_unit : str, optional The unit of pressure to calculate the elastic constants in (default is 'GPa'). maxcycles : int, optional The maximum number of times the minimization algorithm is called. Default value is 100. ctol : float, optional The relative tolerance used to determine if the lattice constants have converged (default is 1e-10). Returns ------- dict Dictionary of results consisting of keys: - **'dumpfile_initial'** (*str*) - The name of the initial dump file created. - **'symbols_initial'** (*list*) - The symbols associated with the initial dump file. - **'dumpfile_final'** (*str*) - The name of the final dump file created. - **'symbols_final'** (*list*) - The symbols associated with the final dump file. - **'lx'** (*float*) - The relaxed lx box length. - **'ly'** (*float*) - The relaxed ly box length. - **'lz'** (*float*) - The relaxed lz box length. - **'xy'** (*float*) - The relaxed xy box tilt. - **'xz'** (*float*) - The relaxed xz box tilt. - **'yz'** (*float*) - The relaxed yz box tilt. - **'E_pot'** (*float*) - The potential energy per atom for the final configuration. - **'measured_pxx'** (*float*) - The measured x tensile pressure component for the final configuration. - **'measured_pyy'** (*float*) - The measured y tensile pressure component for the final configuration. - **'measured_pzz'** (*float*) - The measured z tensile pressure component for the final configuration. - **'measured_pxy'** (*float*) - The measured xy shear pressure component for the final configuration. - **'measured_pxz'** (*float*) - The measured xz shear pressure component for the final configuration. - **'measured_pyz'** (*float*) - The measured yz shear pressure component for the final configuration. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) # Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Save initial configuration as a dump file system.dump('atom_dump', f='initial.dump') # Apply small random distortions to atoms system.atoms.pos += dispmult * np.random.rand( *system.atoms.pos.shape) - dispmult / 2 # Initialize parameters old_vects = system.box.vects converged = False # Run minimizations up to maxcycles times for cycle in range(maxcycles): # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='init.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info lammps_variables['p_xx'] = uc.get_in_units(p_xx, lammps_units['pressure']) lammps_variables['p_yy'] = uc.get_in_units(p_yy, lammps_units['pressure']) lammps_variables['p_zz'] = uc.get_in_units(p_zz, lammps_units['pressure']) lammps_variables['p_xy'] = uc.get_in_units(p_xy, lammps_units['pressure']) lammps_variables['p_xz'] = uc.get_in_units(p_xz, lammps_units['pressure']) lammps_variables['p_yz'] = uc.get_in_units(p_yz, lammps_units['pressure']) lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = uc.get_in_units(dmax, lammps_units['length']) # Set dump_keys based on atom_style if potential.atom_style in ['charge']: lammps_variables['dump_keys'] = 'id type q x y z c_peatom' else: lammps_variables['dump_keys'] = 'id type x y z c_peatom' # Set dump_modify_format based on lammps_date if lammps_date < datetime.date(2016, 8, 3): if potential.atom_style in ['charge']: lammps_variables[ 'dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e %.13e"' else: lammps_variables[ 'dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script lammps_script = 'minbox.in' template = read_calc_file('iprPy.calculation.relax_static', 'minbox.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS and extract thermo data logfile = 'log-' + str(cycle) + '.lammps' output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command, logfile=logfile) thermo = output.simulations[0]['thermo'] # Clean up dump files Path('0.dump').unlink() last_dump_file = str(thermo.Step.values[-1]) + '.dump' renamed_dump_file = 'relax_static-' + str(cycle) + '.dump' shutil.move(last_dump_file, renamed_dump_file) # Load relaxed system system = am.load('atom_dump', renamed_dump_file, symbols=system.symbols) # Test if box dimensions have converged if np.allclose(old_vects, system.box.vects, rtol=ctol, atol=0): converged = True break else: old_vects = system.box.vects # Check for convergence if converged is False: raise RuntimeError('Failed to converge after ' + str(maxcycles) + ' cycles') # Zero out near-zero tilt factors lx = system.box.lx ly = system.box.ly lz = system.box.lz xy = system.box.xy xz = system.box.xz yz = system.box.yz if np.isclose(xy / ly, 0.0, rtol=0.0, atol=1e-10): xy = 0.0 if np.isclose(xz / lz, 0.0, rtol=0.0, atol=1e-10): xz = 0.0 if np.isclose(yz / lz, 0.0, rtol=0.0, atol=1e-10): yz = 0.0 system.box.set(lx=lx, ly=ly, lz=lz, xy=xy, xz=xz, yz=yz) system.wrap() # Build results_dict results_dict = {} results_dict['dumpfile_initial'] = 'initial.dump' results_dict['symbols_initial'] = system.symbols results_dict['dumpfile_final'] = renamed_dump_file results_dict['symbols_final'] = system.symbols results_dict['E_pot'] = uc.set_in_units( thermo.PotEng.values[-1] / system.natoms, lammps_units['energy']) results_dict['lx'] = uc.set_in_units(lx, lammps_units['length']) results_dict['ly'] = uc.set_in_units(ly, lammps_units['length']) results_dict['lz'] = uc.set_in_units(lz, lammps_units['length']) results_dict['xy'] = uc.set_in_units(xy, lammps_units['length']) results_dict['xz'] = uc.set_in_units(xz, lammps_units['length']) results_dict['yz'] = uc.set_in_units(yz, lammps_units['length']) results_dict['measured_pxx'] = uc.set_in_units(thermo.Pxx.values[-1], lammps_units['pressure']) results_dict['measured_pyy'] = uc.set_in_units(thermo.Pyy.values[-1], lammps_units['pressure']) results_dict['measured_pzz'] = uc.set_in_units(thermo.Pzz.values[-1], lammps_units['pressure']) results_dict['measured_pxy'] = uc.set_in_units(thermo.Pxy.values[-1], lammps_units['pressure']) results_dict['measured_pxz'] = uc.set_in_units(thermo.Pxz.values[-1], lammps_units['pressure']) results_dict['measured_pyz'] = uc.set_in_units(thermo.Pyz.values[-1], lammps_units['pressure']) return results_dict
def pointdiffusion(lammps_command: str, system: am.System, potential: lmp.Potential, point_kwargs: Union[list, dict], mpi_command: Optional[str] = None, temperature: float = 300.0, runsteps: int = 200000, thermosteps: Optional[int] = None, dumpsteps: int = 0, equilsteps: int = 20000, randomseed: Optional[int] = None) -> dict: """ Evaluates the diffusion rate of a point defect at a given temperature. This method will run two simulations: an NVT run at the specified temperature to equilibrate the system, then an NVE run to measure the defect's diffusion rate. The diffusion rate is evaluated using the mean squared displacement of all atoms in the system, and using the assumption that diffusion is only due to the added defect(s). Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. point_kwargs : dict or list of dict One or more dictionaries containing the keyword arguments for the atomman.defect.point() function to generate specific point defect configuration(s). mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. temperature : float, optional The temperature to run at (default is 300.0). runsteps : int, optional The number of integration steps to perform (default is 200000). thermosteps : int, optional Thermo values will be reported every this many steps (default is 100). dumpsteps : int or None, optional Dump files will be saved every this many steps (default is 0, which does not output dump files). equilsteps : int, optional The number of timesteps at the beginning of the simulation to exclude when computing average values (default is 20000). randomseed : int or None, optional Random number seed used by LAMMPS in creating velocities and with the Langevin thermostat. (Default is None which will select a random int between 1 and 900000000.) Returns ------- dict Dictionary of results consisting of keys: - **'natoms'** (*int*) - The number of atoms in the system. - **'temp'** (*float*) - The mean measured temperature. - **'pxx'** (*float*) - The mean measured normal xx pressure. - **'pyy'** (*float*) - The mean measured normal yy pressure. - **'pzz'** (*float*) - The mean measured normal zz pressure. - **'Epot'** (*numpy.array*) - The mean measured total potential energy. - **'temp_std'** (*float*) - The standard deviation in the measured temperature values. - **'pxx_std'** (*float*) - The standard deviation in the measured normal xx pressure values. - **'pyy_std'** (*float*) - The standard deviation in the measured normal yy pressure values. - **'pzz_std'** (*float*) - The standard deviation in the measured normal zz pressure values. - **'Epot_std'** (*float*) - The standard deviation in the measured total potential energy values. - **'dx'** (*float*) - The computed diffusion constant along the x-direction. - **'dy'** (*float*) - The computed diffusion constant along the y-direction. - **'dz'** (*float*) - The computed diffusion constant along the y-direction. - **'d'** (*float*) - The total computed diffusion constant. """ # Add defect(s) to the initially perfect system if not isinstance(point_kwargs, (list, tuple)): point_kwargs = [point_kwargs] for pkwargs in point_kwargs: #print(pkwargs) system = am.defect.point(system, **pkwargs) # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Check that temperature is greater than zero if temperature <= 0.0: raise ValueError('Temperature must be greater than zero') # Handle default values if thermosteps is None: thermosteps = runsteps // 1000 if thermosteps == 0: thermosteps = 1 if dumpsteps is None: dumpsteps = runsteps if randomseed is None: randomseed = random.randint(1, 900000000) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='perfect.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info lammps_variables['temperature'] = temperature lammps_variables['runsteps'] = runsteps lammps_variables['equilsteps'] = equilsteps lammps_variables['thermosteps'] = thermosteps lammps_variables['dumpsteps'] = dumpsteps lammps_variables['randomseed'] = randomseed lammps_variables['timestep'] = lmp.style.timestep(potential.units) # Set dump_info if dumpsteps == 0: lammps_variables['dump_info'] = '' else: lammps_variables['dump_info'] = '\n'.join([ '', '# Define dump files', 'dump dumpit all custom ${dumpsteps} *.dump id type x y z c_peatom', 'dump_modify dumpit format <dump_modify_format>', '', ]) # Set dump_modify_format based on lammps_date if lammps_date < datetime.date(2016, 8, 3): lammps_variables['dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script lammps_script = 'diffusion.in' template = read_calc_file('iprPy.calculation.point_defect_diffusion', 'diffusion.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run lammps output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) # Extract LAMMPS thermo data. thermo = output.simulations[1]['thermo'] temps = thermo.Temp.values pxxs = uc.set_in_units(thermo.Pxx.values, lammps_units['pressure']) pyys = uc.set_in_units(thermo.Pyy.values, lammps_units['pressure']) pzzs = uc.set_in_units(thermo.Pzz.values, lammps_units['pressure']) E_pots = uc.set_in_units(thermo.PotEng.values, lammps_units['energy']) E_totals = uc.set_in_units(thermo.TotEng.values, lammps_units['energy']) steps = thermo.Step.values # Read user-defined thermo data if output.lammps_date < datetime.date(2016, 8, 1): msd_x = uc.set_in_units(thermo['msd[1]'].values, lammps_units['length']+'^2') msd_y = uc.set_in_units(thermo['msd[2]'].values, lammps_units['length']+'^2') msd_z = uc.set_in_units(thermo['msd[3]'].values, lammps_units['length']+'^2') msd = uc.set_in_units(thermo['msd[4]'].values, lammps_units['length']+'^2') else: msd_x = uc.set_in_units(thermo['c_msd[1]'].values, lammps_units['length']+'^2') msd_y = uc.set_in_units(thermo['c_msd[2]'].values, lammps_units['length']+'^2') msd_z = uc.set_in_units(thermo['c_msd[3]'].values, lammps_units['length']+'^2') msd = uc.set_in_units(thermo['c_msd[4]'].values, lammps_units['length']+'^2') # Initialize results dict results = {} results['natoms'] = system.natoms results['nsamples'] = len(thermo) # Get mean and std for temperature, pressure, and energy results['temp'] = np.mean(temps) results['temp_std'] = np.std(temps) results['pxx'] = np.mean(pxxs) results['pxx_std'] = np.std(pxxs) results['pyy'] = np.mean(pyys) results['pyy_std'] = np.std(pyys) results['pzz'] = np.mean(pzzs) results['pzz_std'] = np.std(pzzs) results['E_pot'] = np.mean(E_pots) results['E_pot_std'] = np.std(E_pots) results['E_total'] = np.mean(E_totals) results['E_total_std'] = np.std(E_totals) # Convert steps to times times = steps * uc.set_in_units(lammps_variables['timestep'], lammps_units['time']) # Estimate diffusion rates # MSD_ptd = natoms * MSD_atoms (if one defect in system) # MSD = 2 * ndim * D * t --> D = MSD/t / (2 * ndim) mx = np.polyfit(times, system.natoms * msd_x, 1)[0] my = np.polyfit(times, system.natoms * msd_y, 1)[0] mz = np.polyfit(times, system.natoms * msd_z, 1)[0] m = np.polyfit(times, system.natoms * msd, 1)[0] results['dx'] = mx / 2 results['dy'] = my / 2 results['dz'] = mz / 2 results['d'] = m / 6 return results
def crystal_space_group(system: am.System, symprec: float = 1e-5, to_primitive: bool = False, no_idealize: bool = False) -> dict: """ Uses spglib to evaluate space group information for a given system. Parameters ---------- system : atomman.System The system to analyze. symprec : float, optional Absolute length tolerance to use in identifying symmetry of atomic sites and system boundaries. Default value is 1e-5 to_primitive : bool, optional Indicates if the returned unit cell is conventional (False) or primitive (True). Default value is False. no_idealize : bool, optional Indicates if the atom positions in the returned unit cell are averaged (True) or idealized based on the structure (False). Default value is False. Returns ------- dict Dictionary of results consisting of keys: - **'number'** (*int*) The spacegroup number. - **'international_short'** (*str*) The short international spacegroup symbol. - **'international_full'** (*str*) The full international spacegroup symbol. - **'international'** (*str*) The international spacegroup symbol. - **'schoenflies'** (*str*) The schoenflies spacegroup symbol. - **'hall_symbol'** (*str*) The Hall symbol. - **'choice'** (*str*) The setting choice if there is one. - **'pointgroup_international'** (*str*) The international pointgroup symbol. - **'pointgroup_schoenflies'** (*str*) The schoenflies pointgroup symbol. - **'arithmetic_crystal_class_number'** (*int*) The arithmetic crystal class number. - **'arithmetic_crystal_class_symbol'** (*str*) The arithmetic crystal class symbol. - **'ucell'** (*am.System*) The spacegroup-processed unit cell. - **'hall_number'** (*int*) The Hall number. - **'wyckoffs'** (*list*) A list of the spacegroup's Wyckoff symbols where atoms are found. - **'equivalent_atoms'** (*list*) A list of indices indicating which atoms are equivalent to others. - **'pearson'** (*str*) The Pearson symbol. - **'wyckoff_fingerprint'** (*str*) The Wyckoff symbols joined together. """ # Identify the standardized unit cell representation sym_data = spglib.get_symmetry_dataset(system.dump('spglib_cell'), symprec=symprec) ucell = spglib.standardize_cell(system.dump('spglib_cell'), to_primitive=to_primitive, no_idealize=no_idealize, symprec=symprec) # Convert back to atomman systems and normalize ucell = am.load('spglib_cell', ucell, symbols=system.symbols) ucell.atoms.pos -= ucell.atoms.pos[0] ucell = ucell.normalize() # Throw error if natoms > 2000 natoms = ucell.natoms if natoms > 2000: raise RuntimeError('too many positions') # Average extra per-atom properties by mappings to primitive for index in np.unique(sym_data['mapping_to_primitive']): for key in system.atoms.prop(): if key in ['atype', 'pos']: continue value = system.atoms.view[key][sym_data['mapping_to_primitive'] == index].mean() if key not in ucell.atoms.prop(): ucell.atoms.view[key] = np.zeros_like(value) ucell.atoms.view[key][sym_data['std_mapping_to_primitive'] == index] = value # Get space group metadata sym_data = spglib.get_symmetry_dataset(ucell.dump('spglib_cell')) spg_type = spglib.get_spacegroup_type(sym_data['hall_number']) # Generate Pearson symbol if spg_type['number'] <= 2: crystalclass = 'a' elif spg_type['number'] <= 15: crystalclass = 'm' elif spg_type['number'] <= 74: crystalclass = 'o' elif spg_type['number'] <= 142: crystalclass = 't' elif spg_type['number'] <= 194: crystalclass = 'h' else: crystalclass = 'c' latticetype = spg_type['international'][0] if latticetype in ['A', 'B']: latticetype = 'C' pearson = crystalclass + latticetype + str(natoms) # Generate Wyckoff fingerprint fingerprint_dict = {} usites, uindices = np.unique(sym_data['equivalent_atoms'], return_index=True) for usite, uindex in zip(usites, uindices): atype = ucell.atoms.atype[uindex] wykoff = sym_data['wyckoffs'][uindex] if atype not in fingerprint_dict: fingerprint_dict[atype] = [wykoff] else: fingerprint_dict[atype].append(wykoff) fingerprint = [] for atype in sorted(fingerprint_dict.keys()): fingerprint.append(''.join(sorted(fingerprint_dict[atype]))) fingerprint = ' '.join(fingerprint) # Return results results_dict = spg_type results_dict['ucell'] = ucell results_dict['hall_number'] = sym_data['hall_number'] results_dict['wyckoffs'] = sym_data['wyckoffs'] results_dict['equivalent_atoms'] = sym_data['equivalent_atoms'] results_dict['pearson'] = pearson results_dict['wyckoff_fingerprint'] = fingerprint return results_dict