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 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