def load(self, style, *args, **kwargs): """ Wrapper around atomman.load() for loading files that also saves the file loading options as class attributes. Any parameters not given will use the values already set to the object. """ # Load ucell self.__ucell = am.load(style, *args, **kwargs) self.ucell.wrap() # Check if first variable positional argument is a file try: load_file = Path(args[0]) except: self.load_file = None else: if load_file.is_file(): self.load_file = load_file else: self.load_file = None # Set load style if self.load_file is None: self.load_style = style else: self.load_style = 'system_model'
def load_model(self, model): """Loads subset attributes from an existing model.""" sub = model[self.modelroot] d = {} d['family'] = sub['family'] if 'artifact' in sub: if 'initial-atomic-system' in sub: ValueError('found both load file and embedded content for the initial system') d['load_style'] = sub['artifact']['format'] d['load_file'] = sub['artifact']['file'] load_options = sub['artifact'].get('load_options', None) elif 'initial-atomic-system' in sub: d['ucell'] = am.load('system_model', sub, key='initial-atomic-system') else: ValueError('neither load file nor embedded content found for the initial system') d['symbols'] = sub['symbol'] d['composition'] = sub.get('composition', None) if load_options is not None: d['load_options'] = {} load_options_keys = ['key', 'index', 'data_set', 'pbc', 'atom_style', 'units', 'prop_info'] d['load_options'] = termtodict(load_options, load_options_keys) if 'index' in d['load_options']: d['load_options']['index'] = int(d['load_options']['index']) self.set_values(**d)
def stacking_fault_generation(structure, sizemults, a1, a2, hkl, a1vect_uvw, a2vect_uvw): ''' Generate stacking fault struture using Atomman. Please note the below about Atomman: One layer means one 'ucell' in the defect.StackingFault, along the 'hkl' direction. So 'sizemults[2]=6' means 6 ucell above the stacking fault slide plane, and 6 layers below. i.e. totally 12 ucell/layers in the surface system Args: structure: pymatgen structure sizemults: how to extend the struture when building surface a1: fraction of the slide movement along a1vect_uvu a2: fraction of the slide movement along a2vect_uvu hkl: the stacking fault slide plane a1vect_uvw: the stacking fault moving direction a2vect_uvw: the stacking fault moving direction Return: pymatgen structure containing stacking fault ''' # generate stakcing fault object using atomman, and then change to pymatgen for random occupuation stru_atomman = atomman.load('pymatgen_Structure', structure) stack_fault = atomman.defect.StackingFault(hkl=hkl, ucell=stru_atomman, a1vect_uvw=a1vect_uvw, a2vect_uvw=a2vect_uvw) # it is a bit wired that surface_sys is not used in the rest of the code, # but that is how atomman is designed surface_sys = stack_fault.surface(shift=stack_fault.shifts[0], even=True, sizemults=sizemults, vacuumwidth=15) fault_sys = stack_fault.fault(a1=a1, a2=a2) fault_sys_pymatgen = Structure.from_str(fault_sys.dump('poscar'), fmt='poscar') return fault_sys_pymatgen
def spg_ucell(self): """atomman.System: The unit cell identified following the space-group analysis""" if self.__spg_ucell is None: raise ValueError('No results yet!') if not isinstance(self.__spg_ucell, am.System): self.__spg_ucell = am.load('system_model', self.__spg_ucell) return self.__spg_ucell
def load_system_defect(self): if self.__system_defect is None: fname = self.dumpfile_defect tar = self.database.get_tar(record=self) f = tar.extractfile(fname) self.__system_defect = am.load('atom_dump', f, symbols=self.symbols_defect)
def todict(self, full=True, flat=False): """ Converts the structured content to a simpler dictionary. Parameters ---------- full : bool, optional Flag used by the calculation records. A True value will include terms for both the calculation's input and results, while a value of False will only include input terms (Default is True). flat : bool, optional Flag affecting the format of the dictionary terms. If True, the dictionary terms are limited to having only str, int, and float values, which is useful for comparisons. If False, the term values can be of any data type, which is convenient for analysis. (Default is False). Returns ------- dict A dictionary representation of the record's content. """ # Fetch universal record params params = super().todict(full=full, flat=flat) crystal = self.content[self.contentroot] params['method'] = crystal['method'] params['standing'] = crystal.get('standing', 'good') # Extract potential info subset('lammps_potential').todict(crystal, params, full=full, flat=flat) params['family'] = crystal['system-info']['family'] params['parent_key'] = crystal['system-info']['parent_key'] ucell = am.load('system_model', self.content, key='atomic-system') params['composition'] = ucell.composition #print('potential-energy' in crystal) if 'potential-energy' in crystal: params['E_pot'] = uc.value_unit(crystal['potential-energy']) params['E_coh'] = uc.value_unit(crystal['cohesive-energy']) else: params['E_coh'] = params['E_pot'] = uc.value_unit(crystal['cohesive-energy']) if flat is True: params['symbols'] = list(ucell.symbols) params['a'] = ucell.box.a params['b'] = ucell.box.b params['c'] = ucell.box.c params['alpha'] = ucell.box.alpha params['beta'] = ucell.box.beta params['gamma'] = ucell.box.gamma params['natoms'] = ucell.natoms else: params['ucell'] = ucell return params
def load(self, style, input, **kwargs): """Load a System.""" system, symbols = am.load(style, input, **kwargs) self.__box = system.box self.__atoms = system.atoms self.__pbc = system.pbc self.__prop = system.prop return symbols
def min_cells(self): """list : atomman.Systems for the dimensions scanned with local energy minima.""" if self.__min_cells is None: raise ValueError('No results yet!') for i in range(len(self.__min_cells)): if not isinstance(self.__min_cells[i], am.System): self.__min_cells[i] = am.load('system_model', self.__min_cells[i]) return self.__min_cells
def todict(self, full=True, flat=False): """ Converts the structured content to a simpler dictionary. Parameters ---------- full : bool, optional Flag used by the calculation records. A True value will include terms for both the calculation's input and results, while a value of False will only include input terms (Default is True). flat : bool, optional Flag affecting the format of the dictionary terms. If True, the dictionary terms are limited to having only str, int, and float values, which is useful for comparisons. If False, the term values can be of any data type, which is convenient for analysis. (Default is False). Returns ------- dict A dictionary representation of the record's content. """ # Fetch universal record params params = super().todict(full=full, flat=flat) proto = self.content[self.contentroot] params['name'] = proto['name'] params['prototype'] = proto['prototype'] params['Pearson_symbol'] = proto['Pearson-symbol'] params['Strukturbericht'] = proto['Strukturbericht'] params['sg_number'] = proto['space-group']['number'] params['sg_HG'] = proto['space-group']['Hermann-Maguin'] params['sg_Schoen'] = proto['space-group']['Schoenflies'] ucell = am.load('system_model', self.content, key='atomic-system') params['crystal_family'] = am.tools.identifyfamily(ucell.box) params['natypes'] = ucell.natypes if flat is True: params['a'] = ucell.box.a params['b'] = ucell.box.b params['c'] = ucell.box.c params['alpha'] = ucell.box.alpha params['beta'] = ucell.box.beta params['gamma'] = ucell.box.gamma params['natoms'] = ucell.natoms else: params['ucell'] = ucell return params
def assign_composition(df, database, lib_directory=None): """ Assigns compositions to calculations. """ # Build counts for available prototypes prototypes = database.get_records(style='crystal_prototype') counts = {} for prototype in prototypes: counts[prototype.name] = np.unique( prototype.content.finds('component'), return_counts=True)[1] # Set default lib_directory (for ref structures) if lib_directory is None: lib_directory = Settings().library_directory # Identify compositions compositions = [] for i in range(len(df)): series = df.iloc[i] # Use ucell system if available (crystal_space_group) if 'ucell' in series: composition = series.ucell.composition if composition is not None: compositions.append(composition) else: compositions.append(np.nan) # Use symbols and family info if available (E_vs_r_scan, relax_*) elif 'symbols' in series and 'family' in series: # If family is a prototype if series.family in counts: compositions.append( am.tools.compositionstr(series.symbols, counts[series.family])) # If family is a ref else: fname = Path(lib_directory, 'reference_crystal', series.family + '.json') try: ucell = am.load('system_model', fname) except: compositions.append(np.nan) else: count = np.unique(ucell.atoms.atype, return_counts=True)[1] compositions.append( am.tools.compositionstr(ucell.symbols, count)) else: compositions.append(np.nan) df['composition'] = compositions
def todict(self, full=True, flat=False): """ Converts the structured content to a simpler dictionary. Parameters ---------- full : bool, optional Flag used by the calculation records. A True value will include terms for both the calculation's input and results, while a value of False will only include input terms (Default is True). flat : bool, optional Flag affecting the format of the dictionary terms. If True, the dictionary terms are limited to having only str, int, and float values, which is useful for comparisons. If False, the term values can be of any data type, which is convenient for analysis. (Default is False). Returns ------- dict A dictionary representation of the record's content. """ # Fetch universal record params params = super().todict(full=full, flat=flat) crystal = self.content[self.contentroot] params['sourcename'] = crystal['source']['name'] params['sourcelink'] = crystal['source']['link'] ucell = am.load('system_model', self.content, key='atomic-system') params['composition'] = ucell.composition params['natypes'] = ucell.natypes if flat is True: params['symbols'] = list(ucell.symbols) params['a'] = ucell.box.a params['b'] = ucell.box.b params['c'] = ucell.box.c params['alpha'] = ucell.box.alpha params['beta'] = ucell.box.beta params['gamma'] = ucell.box.gamma params['natoms'] = ucell.natoms else: params['ucell'] = ucell return params
def load_dump(self, system1, atom_style=None, units=None, potential=None): """ Utility function that dumps, loads, and dumps, then asserts two dumps are equivalent """ # dump system1 to content1 content1 = system1.dump('atom_data', atom_style=atom_style, units=units, potential=potential, return_info=False) # load content1 to system2 system2 = am.load('atom_data', content1, pbc=system1.pbc, symbols=system1.symbols, atom_style=atom_style, units=units, potential=potential) # dump system2 to content2 content2 = system2.dump('atom_data', atom_style=atom_style, units=units, potential=potential, return_info=False) # Check that the two dumps are equivalent assert content1 == content2
def test_badfile_no_box(self): """Raise FileFormatError if any *lo *hl lines are missing""" # missing xlo xhi content = '\n'.join(self.data_lines[:3] + self.data_lines[4:]) with pytest.raises(FileFormatError): am.load('atom_data', content) # missing ylo yhi content = '\n'.join(self.data_lines[:4] + self.data_lines[5:]) with pytest.raises(FileFormatError): am.load('atom_data', content) # missing zlo zhi content = '\n'.join(self.data_lines[:5] + self.data_lines[6:]) with pytest.raises(FileFormatError): am.load('atom_data', content)
def relax_static(lammps_command, system, potential, mpi_command=None, p_xx=0.0, p_yy=0.0, p_zz=0.0, p_xy=0.0, p_xz=0.0, p_yz=0.0, dispmult=0.0, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom'), maxcycles=100, ctol=1e-10): """ 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: - **'relaxed_system'** (*float*) - The relaxed system. - **'E_coh'** (*float*) - The cohesive energy of the relaxed system. - **'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. """ # 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): old_system = deepcopy(system) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='init.dat', units=potential.units, atom_style=potential.atom_style) lammps_variables['atomman_system_info'] = system_info lammps_variables['atomman_pair_info'] = potential.pair_info( system.symbols) 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_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 template_file = 'minbox.template' lammps_script = 'minbox.in' with open(template_file) as f: template = f.read() with open(lammps_script, 'w') as f: f.write( iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS and extract thermo data logfile = 'log-' + str(cycle) + '.lammps' output = lmp.run(lammps_command, lammps_script, mpi_command, logfile=logfile) thermo = output.simulations[0]['thermo'] # Clean up dump files os.remove('0.dump') 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_coh'] = 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 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 get_oqmd_structures(elements, lib_directory=None): """ Accesses the Materials Project and downloads all structures for a list of elements as poscar files. Parameters ---------- elements : list A list of element symbols. lib_directory : str Path to the lib_directory to save the poscar files to. Default uses the iprPy library/dft_structures directory. """ # Function-specific imports import requests # Get default lib_directory if lib_directory is None: lib_directory = os.path.join(os.path.dirname(rootdir), 'library', 'ref') lib_directory = os.path.abspath(lib_directory) # Set comp_directory elements.sort() have = [] for subelements in subsets(elements): elements_string = '-'.join(subelements) comp_directory = os.path.join(lib_directory, elements_string) if not os.path.isdir(comp_directory): os.makedirs(comp_directory) # Build list of downloaded entries for fname in glob.iglob(os.path.join(comp_directory, 'oqmd-*.poscar')): have.append(os.path.splitext(os.path.basename(fname))[0]) #print('Have', len(have), 'records') # Build list of missing OQMD entries elements_string = '-'.join(elements) composition_r = requests.get('http://oqmd.org/materials/composition/' + elements_string) composition_html = composition_r.text missing = [] count = 0 while True: count += 1 try: start = composition_html.index('href="/materials/entry/') + len('href="/materials/entry/') except: break else: end = start + composition_html[start:].index('">') entry_number = composition_html[start:end] composition_html = composition_html[end+2:] entry_id = 'oqmd-'+entry_number if entry_id not in have and entry_id not in missing: missing.append(entry_id) if count > 100: raise ValueError('Loop likely infinite') #print('Missing', len(missing), 'records') # Download missing entries for entry_id in missing: entry_number = entry_id.replace('oqmd-', '') entry_r = requests.get('http://oqmd.org/materials/entry/' + entry_number) entry_html = entry_r.text start = entry_html.index('href="/materials/structure/') + len('href="/materials/structure/') end = start + entry_html[start:].index('">') structure_number = entry_html[start:end] try: structure_url = 'http://oqmd.org/materials/export/conventional/poscar/' + structure_number structure_r = requests.get(structure_url) structure_r.raise_for_status() except: try: structure_url = 'http://oqmd.org/materials/export/primitive/poscar/' + structure_number structure_r = requests.get(structure_url) structure_r.raise_for_status() except: continue # Save poscar poscar = structure_r.text system = am.load('poscar', poscar) system = system.normalize() elements_string = '-'.join(system.symbols) structure_file = os.path.join(lib_directory, elements_string, entry_id + '.poscar') with open(structure_file, 'w') as f: f.write(poscar) print('Added', entry_id)
def atomman_systemload(input_dict, build=True, **kwargs): """ Interprets calculation parameters associated with building a ucell system. The input_dict keys used by this function (which can be renamed using the function's keyword arguments): - **'load_file'** file to load system information from. - **'load_style'** file format of load_file. - **'load_content'** alternate file or content to load instead of specified load_file. This is used by prepare functions. - **'load_options'** any additional options associated with loading the load file as an atomman.System. - **'symbols'** the list of atomic symbols associated with ucell's atom types. Optional if this information is in load/load_content. - **'box_parameters'** the string of box parameters to scale the system by. Optional if the load file already is properly scaled. - **'family'** if input_dict or the load file contains a family term, then it is passed on. Otherwise, a new family is created based on the load file's name. - **'ucell'** this is where the resulting system is saved. Parameters ---------- input_dict : dict Dictionary containing input parameter key-value pairs. build : bool If False, parameters will be interpreted, but objects won't be built from them (Default is True). load_file : str Replacement parameter key name for 'load_file'. load_style : str Replacement parameter key name for 'load_style'. load_options : str Replacement parameter key name for 'load_options'. symbols : str Replacement parameter key name for 'symbols'. box_parameters : str Replacement parameter key name for 'box_parameters'. family : str Replacement parameter key name for 'family'. ucell : str Replacement parameter key name for 'ucell'. """ # Set default keynames keynames = [ 'load_file', 'load_style', 'load_options', 'load_content', 'box_parameters', 'family', 'ucell', 'symbols' ] for keyname in keynames: kwargs[keyname] = kwargs.get(keyname, keyname) # Extract input values and assign default values load_file = input_dict[kwargs['load_file']] load_style = input_dict.get(kwargs['load_style'], 'system_model') load_options = input_dict.get(kwargs['load_options'], None) load_content = input_dict.get(kwargs['load_content'], None) family = input_dict.get(kwargs['family'], None) symbols = input_dict.get(kwargs['symbols'], None) box_parameters = input_dict.get(kwargs['box_parameters'], None) # Use load_content instead of load_file if given if load_content is not None: load_file = load_content # Separate load_options terms load_options_kwargs = {} if load_options is not None: load_options_keys = [ 'key', 'index', 'data_set', 'pbc', 'atom_style', 'units', 'prop_info' ] load_options_kwargs = termtodict(load_options, load_options_keys) if 'index' in load_options_kwargs: load_options_kwargs['index'] = int(load_options_kwargs['index']) # Build ucell if build is True: # Load ucell ucell = am.load(load_style, load_file, **load_options_kwargs) # Replace symbols if given if symbols is not None: ucell.symbols = symbols.split() symbols = list(ucell.symbols) # Scale ucell by box_parameters if box_parameters is not None: box_params = box_parameters.split() # len of 4 or 7 indicates that last term is a length unit if len(box_params) == 4 or len(box_params) == 7: unit = box_params[-1] box_params = box_params[:-1] # Use calculation's length_unit if unit not given in box_parameters else: unit = input_dict['length_unit'] # Convert to the specified units box_params = np.array(box_params, dtype=float) box_params[:3] = uc.set_in_units(box_params[:3], unit) # Three box_parameters means a, b, c if len(box_params) == 3: ucell.box_set(a=box_params[0], b=box_params[1], c=box_params[2], scale=True) # Six box_parameters means a, b, c, alpha, beta, gamma elif len(box_params) == 6: ucell.box_set(a=box_params[0], b=box_params[1], c=box_params[2], alpha=box_params[3], beta=box_params[4], gamma=box_params[5], scale=True) # Other options are invalid else: ValueError('Invalid box_parameters command') # Don't build else: # Try to get symbols by loading file if symbols is None: try: ucell = am.load(load_style, load_file, **load_options_kwargs) except: pass else: symbols = list(ucell.symbols) ucell = None # Extract system_family (and possibly symbols) from system_model if load_style == 'system_model': model = DM(load_file) # Check if family in model if family is None: try: family = model.find('system-info')['family'] except: family = None # Extract symbols if needed if symbols is None or symbols[0] is None: try: symbols = model.find('system-info')['symbol'] except: pass else: if ucell is not None: ucell.symbols = symbols symbols = list(ucell.symbols) # Pull single symbols out of the list if len(symbols) == 1: symbols = symbols[0] # If no family given/found, use load_file's basename if family is None: family = os.path.splitext( os.path.basename(input_dict[kwargs['load_file']]))[0] # Save processed terms input_dict[kwargs['load_style']] = load_style input_dict[kwargs['load_options']] = load_options input_dict[kwargs['box_parameters']] = box_parameters input_dict[kwargs['family']] = family input_dict[kwargs['ucell']] = ucell input_dict[kwargs['symbols']] = symbols
def todict(self, full=True, flat=False): """ Converts the structured content to a simpler dictionary. Parameters ---------- full : bool, optional Flag used by the calculation records. A True value will include terms for both the calculation's input and results, while a value of False will only include input terms (Default is True). flat : bool, optional Flag affecting the format of the dictionary terms. If True, the dictionary terms are limited to having only str, int, and float values, which is useful for comparisons. If False, the term values can be of any data type, which is convenient for analysis. (Default is False). Returns ------- dict A dictionary representation of the record's content. """ calc = self.content[self.contentroot] params = {} params['key'] = calc['key'] params['script'] = calc['calculation']['script'] params['iprPy_version'] = calc['calculation']['iprPy-version'] params['symmetryprecision'] = calc['calculation']['run-parameter']['symmetryprecision'] params['primitivecell'] = calc['calculation']['run-parameter']['primitivecell'] params['idealcell'] = calc['calculation']['run-parameter']['idealcell'] params['load_file'] = calc['system-info']['artifact']['file'] params['load_style'] = calc['system-info']['artifact']['format'] params['load_options'] = calc['system-info']['artifact']['load_options'] params['family'] = calc['system-info']['family'] symbols = aslist(calc['system-info']['symbol']) if flat is True: try: params['symbols'] = ' '.join(symbols) except: params['symbols'] = np.nan else: params['symbols'] = symbols params['status'] = calc.get('status', 'finished') params['error'] = calc.get('error', np.nan) if full is True and params['status'] == 'finished': ucell = am.load('system_model', self.content, key='unit-cell-atomic-system') params['pearson_symbol'] = calc['Pearson-symbol'] params['spacegroup_number'] = calc['space-group']['number'] params['spacegroup_international'] = calc['space-group']['Hermann-Maguin'] params['spacegroup_Schoenflies'] = calc['space-group']['Schoenflies'] params['wykoff_letters'] = ' '.join(calc['space-group'].finds('letter')) if flat is True: params['a'] = ucell.box.a params['b'] = ucell.box.b params['c'] = ucell.box.c params['alpha'] = ucell.box.alpha params['beta'] = ucell.box.beta params['gamma'] = ucell.box.gamma params['natoms'] = ucell.natoms else: params['ucell'] = ucell return params
def dislocation_monopole(lammps_command: str, ucell: am.System, potential: lmp.Potential, C: am.ElasticConstants, burgers: Union[list, np.ndarray], ξ_uvw: Union[list, np.ndarray], slip_hkl: Union[list, np.ndarray], mpi_command: Optional[str] = None, m: Union[list, np.ndarray] = [0, 1, 0], n: Union[list, np.ndarray] = [0, 0, 1], sizemults=None, amin: float = None, bmin: float = None, cmin: float = None, shift: Union[list, np.ndarray, None] = None, shiftscale: bool = False, shiftindex: int = None, tol: float = 1e-8, etol: float = 0.0, ftol: float = 0.0, maxiter: int = 10000, maxeval: int = 100000, dmax: float = uc.set_in_units(0.01, 'angstrom'), annealtemp: float = 0.0, annealsteps: Optional[int] = None, randomseed: Optional[int] = None, boundaryshape: str = 'cylinder', boundarywidth: float = 0.0, boundaryscale: bool = False) -> dict: """ Creates and relaxes a dislocation monopole system. Parameters ---------- lammps_command :str Command for running LAMMPS. ucell : atomman.System The unit cell to use as the seed for generating the dislocation monopole system. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. C : atomman.ElasticConstants The elastic constants associated with the bulk crystal structure for ucell. burgers : array-like object The dislocation's Burgers vector given as a Miller or Miller-Bravais vector relative to ucell. ξ_uvw : array-like object The dislocation's line direction given as a Miller or Miller-Bravais vector relative to ucell. slip_hkl : array-like object The dislocation's slip plane given as a Miller or Miller-Bravais plane relative to ucell. mpi_command : str or None, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. m : array-like object, optional The m unit vector for the dislocation solution. m, n, and ξ (dislocation line) should be right-hand orthogonal. Default value is [0,1,0] (y-axis). n : array-like object, optional The n unit vector for the dislocation solution. m, n, and ξ (dislocation line) should be right-hand orthogonal. Default value is [0,0,1] (z-axis). n is normal to the dislocation slip plane. sizemults : tuple, optional The size multipliers to use when generating the system. Values are limited to being positive integers. The multipliers for the two non-periodic directions must be even. If not given, the default multipliers will be 2 for the non-periodic directions and 1 for the periodic direction. amin : float, optional A minimum thickness to use for the a box vector direction of the final system. Default value is 0.0. For the non-periodic directions, the resulting vector multiplier will be even. If both amin and sizemults is given, then the larger multiplier for the two will be used. bmin : float, optional A minimum thickness to use for the b box vector direction of the final system. Default value is 0.0. For the non-periodic directions, the resulting vector multiplier will be even. If both bmin and sizemults is given, then the larger multiplier for the two will be used. cmin : float, optional A minimum thickness to use for the c box vector direction of the final system. Default value is 0.0. For the non-periodic directions, the resulting vector multiplier will be even. If both cmin and sizemults is given, then the larger multiplier for the two will be used. shift : float, optional A rigid body shift to apply to the rotated cell prior to inserting the dislocation. Should be selected such that the ideal slip plane does not correspond to any atomic planes. Is taken as absolute if shiftscale is False, or relative to the rotated cell's box vectors if shiftscale is True. Cannot be given with shiftindex. If neither shift nor shiftindex is given then shiftindex = 0 is used. shiftindex : float, optional The index of the identified optimum shifts based on the rotated cell to use. Different values allow for the selection of different atomic planes neighboring the slip plane. Note that shiftindex values only apply shifts normal to the slip plane; best shifts for non-planar dislocations (like bcc screw) may also need a shift in the slip plane. Cannot be given with shiftindex. If neither shift nor shiftindex is given then shiftindex = 0 is used. shiftscale : bool, optional If False (default), a given shift value will be taken as absolute Cartesian. If True, a given shift will be taken relative to the rotated cell's box vectors. tol : float A cutoff tolerance used with obtaining the dislocation solution. Only needs to be changed if there are issues with obtaining a solution. 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. 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. boundaryshape : str, optional Indicates the shape of the boundary region to use. Options are 'cylinder' (default) and 'box'. For 'cylinder', the non-boundary region is defined by a cylinder with axis along the dislocation line and a radius that ensures the boundary is at least boundarywidth thick. For 'box', the boundary region will be exactly boundarywidth thick all around. boundarywidth : float, optional The width of the boundary region to apply. Default value is 0.0, i.e. no boundary region. All atoms in the boundary region will have their atype values changed. boundaryscale : bool, optional If False (Default), the boundarywidth will be taken as absolute. If True, the boundarywidth will be taken relative to the magnitude of the unit cell's a box vector. Returns ------- dict Dictionary of results consisting of keys: - **'dumpfile_base'** (*str*) - The filename of the LAMMPS dump file for the relaxed base system. - **'symbols_base'** (*list of str*) - The list of element-model symbols for the Potential that correspond to the base system's atypes. - **'dumpfile_disl'** (*str*) - The filename of the LAMMPS dump file for the relaxed dislocation monopole system. - **'symbols_disl'** (*list of str*) - The list of element-model symbols for the Potential that correspond to the dislocation monopole system's atypes. - **'dislocation'** (*atomman.defect.Dislocation*) - The Dislocation object used to generate the monopole system. - **'E_total_disl'** (*float*) - The total potential energy of the dislocation monopole system. """ # Construct dislocation configuration generator dislocation = am.defect.Dislocation(ucell, C, burgers, ξ_uvw, slip_hkl, m=m, n=n, shift=shift, shiftindex=shiftindex, shiftscale=shiftscale, tol=tol) # Generate the base and dislocation systems base_system, disl_system = dislocation.monopole( sizemults=sizemults, amin=amin, bmin=bmin, cmin=cmin, shift=shift, shiftindex=shiftindex, shiftscale=shiftscale, boundaryshape=boundaryshape, boundarywidth=boundarywidth, boundaryscale=boundaryscale, return_base_system=True) # Initialize results dict results_dict = {} # Save initial perfect system base_system.dump('atom_dump', f='base.dump') results_dict['dumpfile_base'] = 'base.dump' results_dict['symbols_base'] = base_system.symbols # Save dislocation generator results_dict['dislocation'] = dislocation # Relax system relaxed = disl_relax(lammps_command, disl_system, potential, mpi_command=mpi_command, annealtemp=annealtemp, annealsteps=annealsteps, randomseed=randomseed, etol=etol, ftol=ftol, maxiter=maxiter, maxeval=maxeval, dmax=dmax) # Save relaxed dislocation system with original box vects system_disl = am.load('atom_dump', relaxed['dumpfile'], symbols=disl_system.symbols) system_disl.box_set(vects=disl_system.box.vects, origin=disl_system.box.origin) system_disl.dump('atom_dump', f='disl.dump') results_dict['dumpfile_disl'] = 'disl.dump' results_dict['symbols_disl'] = system_disl.symbols results_dict['E_total_disl'] = relaxed['E_total'] # Cleanup files Path('0.dump').unlink() Path(relaxed['dumpfile']).unlink() for dumpjsonfile in Path('.').glob('*.dump.json'): dumpjsonfile.unlink() return results_dict
def plot_vitek(dislo, bulk, show=True, save_file=None, alat=3.16, plot_axes=None, xyscale=10): """Plots vitek map from ase configurations. Parameters ---------- dislo : ase.Atoms Dislocation configuration. bulk : ase.Atoms Corresponding bulk configuration for calculation of displacements. show : bool Show the figure after plotting. Default is True. save_file : str, optional If given then the plot will be saved to a file with this name. alat : float Lattice parameter for calculation of neghbour list cutoff. plot_axes : matplotlib.Axes.axes object Existing axes to plot on, allows to pass existing matplotlib axes have full control of the graph outside the function. Makes possible to plot multiple differential displacement maps using subplots. Default is None, then new graph is created by plt.subplots() Description of parameter `plot_axes`. xyscale : float xyscale of the graph Returns ------- None """ lengthB = 0.5*np.sqrt(3.)*alat burgers = np.array([0.0, 0.0, lengthB]) base_system = am.load("ase_Atoms", bulk) disl_system = am.load("ase_Atoms", dislo) neighborListCutoff = 0.95 * alat # plot window is +-10 angstroms in x,y directions, and one Burgerx vector # thickness along z direction plot_range = np.array([[-xyscale, xyscale], [-xyscale, xyscale], [-0.1, alat * 3.**(0.5) / 2.]]) # This scales arrows such that b/2 corresponds to the # distance between atoms on the plot plot_scale = 1.885618083 am.defect.differential_displacement(base_system, disl_system, burgers, cutoff=neighborListCutoff, xlim=plot_range[0], ylim=plot_range[1], zlim=plot_range[2], plot_scale=plot_scale, show=show, save_file=save_file, plot_axes=plot_axes) return None
def pointdefect(lammps_command, system, potential, point_kwargs, mpi_command=None, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom')): """ 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 simuation 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_coh'** (*float*) - The cohesive 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. - **'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', units=potential.units, atom_style=potential.atom_style) lammps_variables['atomman_system_info'] = system_info lammps_variables['atomman_pair_info'] = potential.pair_info(system.symbols) 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"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script template_file = 'min.template' lammps_script = 'min.in' with open(template_file) as f: template = f.read() with open(lammps_script, 'w') as f: f.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) # Run lammps to relax perfect.dat output = lmp.run(lammps_command, lammps_script, 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_coh = E_total_base / system.natoms # 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', units = potential.units, atom_style = potential.atom_style) lammps_variables['atomman_system_info'] = system_info # Write lammps input script with open(lammps_script, 'w') as f: f.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) # Run lammps output = lmp.run(lammps_command, lammps_script, 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']) # 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_coh * system_ptd.natoms # Cleanup files for fname in glob.iglob('atom.*'): os.remove(fname) for dumpjsonfile in glob.iglob('*.dump.json'): os.remove(dumpjsonfile) # Return results results_dict = {} results_dict['E_coh'] = E_coh 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['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 phonon(lammps_command, ucell, potential, mpi_command=None, a_mult=3, b_mult=3, c_mult=3, distance=0.01, symprec=1e-5): try: # Get script's location if __file__ exists script_dir = Path(__file__).parent except: # Use cwd otherwise script_dir = Path.cwd() # Get lammps units lammps_units = lmp.style.unit(potential.units) # Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Generate pair_info pair_info = potential.pair_info(ucell.symbols) # Use spglib to find primitive unit cell of ucell convcell = ucell.dump('spglib_cell') primcell = spglib.find_primitive(convcell, symprec=symprec) primucell = am.load('spglib_cell', primcell, symbols=ucell.symbols).normalize() # Initialize Phonopy object phonon = phonopy.Phonopy(primucell.dump('phonopy_Atoms'), [[a_mult, 0, 0], [0, b_mult, 0], [0, 0, c_mult]]) phonon.generate_displacements(distance=distance) # Loop over displaced supercells to compute forces forcearrays = [] for supercell in phonon.supercells_with_displacements: # Save to LAMMPS data file system = am.load('phonopy_Atoms', supercell) system_info = system.dump('atom_data', f='disp.dat') # Define lammps variables lammps_variables = {} lammps_variables['atomman_system_info'] = system_info lammps_variables['atomman_pair_info'] = pair_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 template_file = Path(script_dir, 'phonon.template') lammps_script = 'phonon.in' with open(template_file) as f: template = f.read() with open(lammps_script, 'w') as f: f.write( iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS lmp.run(lammps_command, 'phonon.in', mpi_command=mpi_command) # Extract forces from dump file results = am.load('atom_dump', 'forces.dump') forces = uc.set_in_units(results.atoms.force, lammps_units['force']) forcearrays.append(forces) # Set computed forces phonon.set_forces(forcearrays) # Save to yaml file phonon.save('phonopy_params.yaml') # Compute band structure phonon.produce_force_constants() phonon.auto_band_structure(plot=True) plt.savefig(Path('.', 'band.png'), dpi=400) plt.close() # Compute total density of states phonon.auto_total_dos(plot=True) plt.savefig('total_dos.png', dpi=400) plt.close() # Compute partial density of states phonon.auto_projected_dos(plot=True) plt.savefig('projected_dos.png', dpi=400) plt.close() # Compute thermal properties phonon.run_thermal_properties() phonon.plot_thermal_properties() plt.savefig('thermal.png', dpi=400) plt.close() return {}
def interpret(self, input_dict, build=True): """ Interprets calculation parameters. Parameters ---------- input_dict : dict Dictionary containing input parameter key-value pairs. """ # Set default keynames keymap = self.keymap # Extract input values and assign default values load_file = input_dict[keymap['load_file']] load_style = input_dict.get(keymap['load_style'], 'system_model') load_options = input_dict.get(keymap['load_options'], None) load_content = input_dict.get(keymap['load_content'], None) family = input_dict.get(keymap['family'], None) symbols = input_dict.get(keymap['symbols'], None) box_parameters = input_dict.get(keymap['box_parameters'], None) elastic_file = input_dict.get(keymap['elasticconstants_content'], None) # Use load_content instead of load_file if given if load_content is not None: load_file = load_content # Separate load_options terms load_options_kwargs = {} if load_options is not None: load_options_keys = [ 'key', 'index', 'data_set', 'pbc', 'atom_style', 'units', 'prop_info' ] load_options_kwargs = termtodict(load_options, load_options_keys) if 'index' in load_options_kwargs: load_options_kwargs['index'] = int( load_options_kwargs['index']) # Build ucell if build is True: # Load ucell ucell = am.load(load_style, load_file, **load_options_kwargs) # Replace symbols if given if symbols is not None: ucell.symbols = symbols.split() symbols = list(ucell.symbols) # Scale ucell by box_parameters if box_parameters is not None: box_params = box_parameters.split() # len of 4 or 7 indicates that last term is a length unit if len(box_params) == 4 or len(box_params) == 7: unit = box_params[-1] box_params = box_params[:-1] # Use calculation's length_unit if unit not given in box_parameters else: unit = input_dict['length_unit'] # Convert to the specified units box_params = np.array(box_params, dtype=float) box_params[:3] = uc.set_in_units(box_params[:3], unit) # Three box_parameters means a, b, c if len(box_params) == 3: ucell.box_set(a=box_params[0], b=box_params[1], c=box_params[2], scale=True) # Six box_parameters means a, b, c, alpha, beta, gamma elif len(box_params) == 6: ucell.box_set(a=box_params[0], b=box_params[1], c=box_params[2], alpha=box_params[3], beta=box_params[4], gamma=box_params[5], scale=True) # Other options are invalid else: ValueError('Invalid box_parameters command') # Add model-specific charges if needed if (keymap['potential'] in input_dict and 'charge' not in ucell.atoms_prop()): potential = input_dict[keymap['potential']] ucell.atoms.prop_atype('charge', potential.charges(ucell.symbols)) # Don't build else: # Try to get symbols by loading file if symbols is None: try: ucell = am.load(load_style, load_file, **load_options_kwargs) except: pass else: symbols = list(ucell.symbols) ucell = None # Extract system_family (and possibly symbols) from parent model if elastic_file is not None: model = DM(elastic_file) elif load_style == 'system_model': model = DM(load_file) else: model = None if model is not None: # Check if family in model if family is None: try: family = model.find('system-info')['family'] except: family = None # Extract symbols if needed if symbols is None or symbols[0] is None: try: symbols = model.find('system-info')['symbol'] except: pass else: if ucell is not None: ucell.symbols = symbols symbols = list(ucell.symbols) # Pull single symbols out of the list if len(symbols) == 1: symbols = symbols[0] # If no family given/found, use load_file's stem if family is None: family = Path(input_dict[keymap['load_file']]).stem # Save processed terms input_dict[keymap['load_style']] = load_style input_dict[keymap['load_options']] = load_options input_dict[keymap['box_parameters']] = box_parameters input_dict[keymap['family']] = family input_dict[keymap['ucell']] = ucell input_dict[keymap['symbols']] = symbols
def stackingfaultpoint(lammps_command, system, potential, mpi_command=None, sim_directory=None, cutboxvector='c', faultpos=0.5, faultshift=[0.0, 0.0, 0.0], etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom'), lammps_date=None): """ Perform a stacking fault relaxation simulation for a single faultshift. 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. sim_directory : str, optional The path to the directory to perform the simuation 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). cutboxvector : str, optional Indicates which of the three system box vectors, 'a', 'b', or 'c', to cut with a non-periodic boundary (default is 'c'). faultpos : float, optional The fractional position along the cutboxvector where the stacking fault plane will be placed (default is 0.5). faultshift : list of float, optional The vector shift to apply to all atoms above the fault plane defined by faultpos (default is [0,0,0], i.e. no shift applied). 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. - **'A_fault'** (*float*) - The area of the fault surface. - **'E_total'** (*float*) - The total potential energy of the relaxed system. - **'disp'** (*float*) - The center of mass difference between atoms above and below the fault plane in the cutboxvector direction. Raises ------ ValueError For invalid cutboxvectors. """ # Set options based on cutboxvector if cutboxvector == 'a': # Assert system is compatible with planeaxis value if system.box.xy != 0.0 or system.box.xz != 0.0: raise ValueError("box tilts xy and xz must be 0 for cutboxvector='a'") # Specify cutindex cutindex = 0 # Identify atoms above fault faultpos = system.box.xlo + system.box.lx * faultpos abovefault = system.atoms.pos[:, cutindex] > (faultpos) # Compute fault area faultarea = np.linalg.norm(np.cross(system.box.bvect, system.box.cvect)) # Give correct LAMMPS fix setforce command fix_cut_setforce = 'fix cut all setforce NULL 0 0' elif cutboxvector == 'b': # Assert system is compatible with planeaxis value if system.box.yz != 0.0: raise ValueError("box tilt yz must be 0 for cutboxvector='b'") # Specify cutindex cutindex = 1 # Identify atoms above fault faultpos = system.box.ylo + system.box.ly * faultpos abovefault = system.atoms.pos[:, cutindex] > (faultpos) # Compute fault area faultarea = np.linalg.norm(np.cross(system.box.avect, system.box.cvect)) # Give correct LAMMPS fix setforce command fix_cut_setforce = 'fix cut all setforce 0 NULL 0' elif cutboxvector == 'c': # Specify cutindex cutindex = 2 # Identify atoms above fault faultpos = system.box.zlo + system.box.lz * faultpos abovefault = system.atoms.pos[:, cutindex] > (faultpos) # Compute fault area faultarea = np.linalg.norm(np.cross(system.box.avect, system.box.bvect)) # Give correct LAMMPS fix setforce command fix_cut_setforce = 'fix cut all setforce 0 0 NULL' else: raise ValueError('Invalid cutboxvector') # Assert faultshift is in cut plane if faultshift[cutindex] != 0.0: raise ValueError('faultshift must be in cut plane') # Generate stacking fault system by shifting atoms above the fault sfsystem = deepcopy(system) sfsystem.pbc = [True, True, True] sfsystem.pbc[cutindex] = False sfsystem.atoms.pos[abovefault] += faultshift sfsystem.wrap() if sim_directory is not None: # Create sim_directory if it doesn't exist if not os.path.isdir(sim_directory): os.mkdir(sim_directory) # Add '/' to end of sim_directory string if needed if sim_directory[-1] != '/': sim_directory = sim_directory + '/' 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 = sfsystem.dump('atom_data', f=os.path.join(sim_directory, 'system.dat'), units=potential.units, atom_style=potential.atom_style) lammps_variables['atomman_system_info'] = system_info lammps_variables['atomman_pair_info'] = potential.pair_info(sfsystem.symbols) 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 template_file = 'sfmin.template' lammps_script = os.path.join(sim_directory, 'sfmin.in') with open(template_file) as f: template = f.read() with open(lammps_script, 'w') as f: f.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS output = lmp.run(lammps_command, lammps_script, mpi_command, logfile=os.path.join(sim_directory, 'log.lammps')) # Extract output values thermo = output.simulations[-1]['thermo'] logfile = os.path.join(sim_directory, 'log.lammps') dumpfile = os.path.join(sim_directory, '%i.dump' % thermo.Step.values[-1]) E_total = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) #Load relaxed system sfsystem = am.load('atom_dump', dumpfile, symbols=sfsystem.symbols) # Find center of mass difference in top/bottom planes disp = (sfsystem.atoms.pos[abovefault, cutindex].mean() - sfsystem.atoms.pos[~abovefault, cutindex].mean()) # Return results results_dict = {} results_dict['logfile'] = logfile results_dict['dumpfile'] = dumpfile results_dict['system'] = sfsystem results_dict['A_fault'] = faultarea results_dict['E_total'] = E_total results_dict['disp'] = disp return results_dict
def crystal_space_group(system, symprec=1e-5, to_primitive=False, no_idealize=False): """ Uses spglib to evaluate space group information for a given system. Parameters ---------- system : atomman.System The system to analyze. symprec : float Absolute length tolerance to use in identifying symmetry of atomic sites and system boundaries. to_primitive : bool Indicates if the returned unit cell is conventional (False) or primitive (True). Default value is False. no_idealize : bool 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 Results dictionary containing space group information and an associated unit cell system. """ # Identify the standardized unit cell representation 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() # 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' natoms = str(ucell.natoms) pearson = crystalclass + latticetype + natoms # 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['pearson'] = pearson return results_dict
def get_mp_structures(elements, api_key=None, lib_directory=None): """ Accesses the Materials Project and downloads all structures for a list of elements as poscar files. Parameters ---------- elements : list A list of element symbols. api_key : str, optional The user's Materials Project API key. If not given, will use "MAPI_KEY" environment variable lib_directory : str Path to the lib_directory to save the poscar files to. Default uses the iprPy library/dft_structures directory. """ # Function-specific imports import pymatgen as pmg from pymatgen.ext.matproj import MPRester # Handle lib_directory if lib_directory is None: lib_directory = os.path.join(os.path.dirname(rootdir), 'library', 'ref') lib_directory = os.path.abspath(lib_directory) elements.sort() # Open connection to Materials Project with MPRester(api_key) as m: # Loop over subsets of elements for subelements in subsets(elements): # Set comp_directory elements_string = '-'.join(subelements) comp_directory = os.path.join(lib_directory, elements_string) if not os.path.isdir(comp_directory): os.makedirs(comp_directory) # Build list of downloaded entries have = [] for fname in glob.iglob(os.path.join(comp_directory, 'mp-*.poscar')): have.append(os.path.splitext(os.path.basename(fname))[0]) #print('Have', len(have), elements_string, 'records') # Query MP for all entries corresponding to the elements entries = m.query({"elements": subelements}, ["material_id"]) # Add entries to the list if not there missing = [] for entry in entries: if entry['material_id'] not in have and entry['material_id'] not in missing: missing.append(entry['material_id']) #print('Missing', len(missing), elements_string, 'records') # Download missing entries entries = m.query({"material_id": {"$in": missing}}, ['material_id', 'cif']) # Convert cif to poscar and save for entry in entries: struct = pmg.Structure.from_str(entry['cif'], fmt='cif') struct = pmg.symmetry.analyzer.SpacegroupAnalyzer(struct).get_conventional_standard_structure() system = am.load('pymatgen_Structure', struct) system = system.normalize() structure_file = os.path.join(comp_directory, entry['material_id']+'.poscar') system.dump('poscar', f=structure_file) print('Added', entry['material_id'])
def crystal_space_group(system, symprec=1e-5, to_primitive=False, no_idealize=False): """ Uses spglib to evaluate space group information for a given system. Parameters ---------- system : atomman.System The system to analyze. symprec : float Absolute length tolerance to use in identifying symmetry of atomic sites and system boundaries. to_primitive : bool Indicates if the returned unit cell is conventional (False) or primitive (True). Default value is False. no_idealize : bool 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 Results dictionary containing space group information and an associated unit cell system. """ # 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() # 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' natoms = str(ucell.natoms) pearson = crystalclass + latticetype + 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
def stackingfaultpoint(lammps_command, system, potential, mpi_command=None, sim_directory=None, cutboxvector='c', faultpos=0.5, faultshift=[0.0, 0.0, 0.0], etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom'), lammps_date=None): """ Perform a stacking fault relaxation simulation for a single faultshift. 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. sim_directory : str, optional The path to the directory to perform the simuation 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). cutboxvector : str, optional Indicates which of the three system box vectors, 'a', 'b', or 'c', to cut with a non-periodic boundary (default is 'c'). faultpos : float, optional The fractional position along the cutboxvector where the stacking fault plane will be placed (default is 0.5). faultshift : list of float, optional The vector shift to apply to all atoms above the fault plane defined by faultpos (default is [0,0,0], i.e. no shift applied). 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. - **'A_fault'** (*float*) - The area of the fault surface. - **'E_total'** (*float*) - The total potential energy of the relaxed system. - **'disp'** (*float*) - The center of mass difference between atoms above and below the fault plane in the cutboxvector direction. Raises ------ ValueError For invalid cutboxvectors. """ # Set options based on cutboxvector if cutboxvector == 'a': # Assert system is compatible with planeaxis value if system.box.xy != 0.0 or system.box.xz != 0.0: raise ValueError( "box tilts xy and xz must be 0 for cutboxvector='a'") # Specify cutindex cutindex = 0 # Identify atoms above fault faultpos = system.box.xlo + system.box.lx * faultpos abovefault = system.atoms.pos[:, cutindex] > (faultpos) # Compute fault area faultarea = np.linalg.norm(np.cross(system.box.bvect, system.box.cvect)) # Give correct LAMMPS fix setforce command fix_cut_setforce = 'fix cut all setforce NULL 0 0' elif cutboxvector == 'b': # Assert system is compatible with planeaxis value if system.box.yz != 0.0: raise ValueError("box tilt yz must be 0 for cutboxvector='b'") # Specify cutindex cutindex = 1 # Identify atoms above fault faultpos = system.box.ylo + system.box.ly * faultpos abovefault = system.atoms.pos[:, cutindex] > (faultpos) # Compute fault area faultarea = np.linalg.norm(np.cross(system.box.avect, system.box.cvect)) # Give correct LAMMPS fix setforce command fix_cut_setforce = 'fix cut all setforce 0 NULL 0' elif cutboxvector == 'c': # Specify cutindex cutindex = 2 # Identify atoms above fault faultpos = system.box.zlo + system.box.lz * faultpos abovefault = system.atoms.pos[:, cutindex] > (faultpos) # Compute fault area faultarea = np.linalg.norm(np.cross(system.box.avect, system.box.bvect)) # Give correct LAMMPS fix setforce command fix_cut_setforce = 'fix cut all setforce 0 0 NULL' else: raise ValueError('Invalid cutboxvector') # Assert faultshift is in cut plane if faultshift[cutindex] != 0.0: raise ValueError('faultshift must be in cut plane') # Generate stacking fault system by shifting atoms above the fault sfsystem = deepcopy(system) sfsystem.pbc = [True, True, True] sfsystem.pbc[cutindex] = False sfsystem.atoms.pos[abovefault] += faultshift sfsystem.wrap() if sim_directory is not None: # Create sim_directory if it doesn't exist if not os.path.isdir(sim_directory): os.mkdir(sim_directory) # Add '/' to end of sim_directory string if needed if sim_directory[-1] != '/': sim_directory = sim_directory + '/' 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 = sfsystem.dump('atom_data', f=os.path.join(sim_directory, 'system.dat'), units=potential.units, atom_style=potential.atom_style) lammps_variables['atomman_system_info'] = system_info lammps_variables['atomman_pair_info'] = potential.pair_info( sfsystem.symbols) 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 template_file = 'sfmin.template' lammps_script = os.path.join(sim_directory, 'sfmin.in') with open(template_file) as f: template = f.read() with open(lammps_script, 'w') as f: f.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS output = lmp.run(lammps_command, lammps_script, mpi_command, logfile=os.path.join(sim_directory, 'log.lammps')) # Extract output values thermo = output.simulations[-1]['thermo'] logfile = os.path.join(sim_directory, 'log.lammps') dumpfile = os.path.join(sim_directory, '%i.dump' % thermo.Step.values[-1]) E_total = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) # Load relaxed system sfsystem = am.load('atom_dump', dumpfile, symbols=sfsystem.symbols) # Find center of mass difference in top/bottom planes disp = (sfsystem.atoms.pos[abovefault, cutindex].mean() - sfsystem.atoms.pos[~abovefault, cutindex].mean()) # Return results results_dict = {} results_dict['logfile'] = logfile results_dict['dumpfile'] = dumpfile results_dict['system'] = sfsystem results_dict['A_fault'] = faultarea results_dict['E_total'] = E_total results_dict['disp'] = disp return results_dict
def get_mp_structures(elements, api_key=None, lib_directory=None): """ Accesses the Materials Project and downloads all structures for a list of elements as poscar files. Parameters ---------- elements : list A list of element symbols. api_key : str, optional The user's Materials Project API key. If not given, will use "MAPI_KEY" environment variable lib_directory : str Path to the lib_directory to save the poscar files to. Default uses the iprPy library/reference_crystal directory. """ # Function-specific imports import pymatgen as pmg from pymatgen.ext.matproj import MPRester # Set source name and link sourcename = "Materials Project" sourcelink = "https://materialsproject.org/" # Handle lib_directory if lib_directory is None: lib_directory = Path(libdir, 'reference_crystal') if not lib_directory.is_dir(): lib_directory.mkdir() elements.sort() # Build list of downloaded entries have = [] for fname in lib_directory.glob('*.json'): have.append(fname.stem) # Open connection to Materials Project with MPRester(api_key) as m: # Loop over subsets of elements for subelements in subsets(elements): # Query MP for all entries corresponding to the elements entries = m.query({"elements": subelements}, ["material_id"]) # Add entries to the list if not there missing = [] for entry in entries: if entry['material_id'] not in have and entry[ 'material_id'] not in missing: missing.append(entry['material_id']) # Download missing entries try: entries = m.query({"material_id": { "$in": missing }}, ['material_id', 'cif']) except: pass else: # Convert cif to model and save for entry in entries: name = entry['material_id'] struct = pmg.Structure.from_str(entry['cif'], fmt='cif') struct = pmg.symmetry.analyzer.SpacegroupAnalyzer( struct).get_conventional_standard_structure() ucell = am.load('pymatgen_Structure', struct).normalize() model = build_reference_crystal_model( name, ucell, sourcename, sourcelink) with open(Path(lib_directory, name + '.json'), 'w') as f: model.json(fp=f, indent=4) print('Added', entry['material_id'])
def crystal_space_group(system, symprec=1e-5, to_primitive=False, no_idealize=False): """ Uses spglib to evaluate space group information for a given system. Parameters ---------- system : atomman.System The system to analyze. symprec : float Absolute length tolerance to use in identifying symmetry of atomic sites and system boundaries. to_primitive : bool Indicates if the returned unit cell is conventional (False) or primitive (True). Default value is False. no_idealize : bool 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 Results dictionary containing space group information and an associated unit cell system. """ # Identify the standardized unit cell representation 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() # 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' natoms = str(ucell.natoms) pearson = crystalclass + latticetype + natoms # 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['pearson'] = pearson return results_dict
def phononcalc(lammps_command, ucell, potential, mpi_command=None, a_mult=3, b_mult=3, c_mult=3, distance=0.01, symprec=1e-5, savefile='phonopy_params.yaml', plot=True, lammps_date=None): """ Uses phonopy to compute the phonons for a unit cell structure using a LAMMPS interatomic potential. 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. savefile: str, optional The name of the phonopy yaml backup file. Default value is 'phonopy_params.yaml'. plot : bool, optional Flag indicating if band structure and DOS figures are to be generated. Default value is True. lammps_date : datetime.date, optional The version date associated with lammps_command. If not given, the version will be identified. """ # 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'] # Use spglib to find primitive unit cell of ucell convcell = ucell.dump('spglib_cell') primcell = spglib.find_primitive(convcell, symprec=symprec) primucell = am.load('spglib_cell', primcell, symbols=ucell.symbols).normalize() # Initialize Phonopy object phonon = phonopy.Phonopy(primucell.dump('phonopy_Atoms', symbols=potential.elements( primucell.symbols)), [[a_mult, 0, 0], [0, b_mult, 0], [0, 0, c_mult]], factor=phonopy.units.VaspToTHz) phonon.generate_displacements(distance=distance) # Loop over displaced supercells to compute forces forcearrays = [] for supercell in phonon.supercells_with_displacements: # Save to LAMMPS data file system = am.load('phonopy_Atoms', supercell, symbols=primucell.symbols) system_info = system.dump('atom_data', f='disp.dat', potential=potential) # Define lammps variables lammps_variables = {} 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 lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) # Extract forces from dump file forcestructure = am.load('atom_dump', 'forces.dump') forces = uc.set_in_units(forcestructure.atoms.force, lammps_units['force']) forcearrays.append(forces) results = {} # Set computed forces phonon.set_forces(forcearrays) # Save to yaml file phonon.save(savefile) # Compute band structure phonon.produce_force_constants() phonon.auto_band_structure(plot=plot) results['band_structure'] = phonon.get_band_structure_dict() if plot: plt.ylabel('Frequency (THz)') plt.savefig(Path('.', 'band.png'), dpi=400) plt.close() # Compute density of states phonon.auto_total_dos(plot=False) phonon.auto_projected_dos(plot=False) dos = phonon.get_total_dos_dict() dos['frequency'] = uc.set_in_units(dos.pop('frequency_points'), 'THz') dos['projected_dos'] = phonon.get_projected_dos_dict()['projected_dos'] results['dos'] = dos # Compute thermal properties phonon.run_thermal_properties() results['thermal_properties'] = phonon.get_thermal_properties_dict() results['phonon'] = phonon return results
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 dislocationmonopole(lammps_command, system, potential, burgers, C, mpi_command=None, axes=None, m=[0,1,0], n=[0,0,1], lineboxvector='a', randomseed=None, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom'), annealtemp=0.0, bshape='circle', bwidth=uc.set_in_units(10, 'angstrom')): """ Creates and relaxes a dislocation monopole system. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The bulk system to add the defect to. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. burgers : list or numpy.array of float The burgers vector for the dislocation being added. C : atomman.ElasticConstants The system's elastic constants. mpi_command : str or None, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. axes : numpy.array of float or None, optional The 3x3 axes used to rotate the system by during creation. If given, will be used to transform burgers and C from the standard crystallographic orientations to the system's Cartesian units. 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). annealtemp : float, optional The temperature to perform a dynamic relaxation at. (Default is 0.0, which will skip the dynamic relaxation.) bshape : str, optional The shape to make the boundary region. Options are 'circle' and 'rect' (default is 'circle'). bwidth : float, optional The minimum thickness of the boundary region (default is 10 Angstroms). Returns ------- dict Dictionary of results consisting of keys: - **'dumpfile_base'** (*str*) - The filename of the LAMMPS dump file for the relaxed base system. - **'symbols_base'** (*list of str*) - The list of element-model symbols for the Potential that correspond to the base system's atypes. - **'Stroh_preln'** (*float*) - The pre-logarithmic factor in the dislocation's self-energy expression. - **'Stroh_K_tensor'** (*numpy.array of float*) - The energy coefficient tensor based on the dislocation's Stroh solution. - **'dumpfile_disl'** (*str*) - The filename of the LAMMPS dump file for the relaxed dislocation monopole system. - **'symbols_disl'** (*list of str*) - The list of element-model symbols for the Potential that correspond to the dislocation monopole system's atypes. - **'E_total_disl'** (*float*) - The total potential energy of the dislocation monopole system. """ # Initialize results dict results_dict = {} # Save initial perfect system system.dump('atom_dump', f='base.dump') results_dict['dumpfile_base'] = 'base.dump' results_dict['symbols_base'] = system.symbols # Solve Stroh method for dislocation stroh = am.defect.Stroh(C, burgers, axes=axes, m=m, n=n) results_dict['Stroh_preln'] = stroh.preln results_dict['Stroh_K_tensor'] = stroh.K_tensor # Generate dislocation system by displacing atoms disp = stroh.displacement(system.atoms.pos) system.atoms.pos += disp # Apply fixed boundary conditions system = disl_boundary_fix(system, bwidth, bshape=bshape, lineboxvector=lineboxvector, m=m, n=n) # Relax system relaxed = disl_relax(lammps_command, system, potential, mpi_command = mpi_command, annealtemp = annealtemp, etol = etol, ftol = ftol, maxiter = maxiter, maxeval = maxeval) # Save relaxed dislocation system with original box vects system_disl = am.load('atom_dump', relaxed['dumpfile'], symbols=system.symbols) system_disl.box_set(vects=system.box.vects, origin=system.box.origin) system_disl.dump('atom_dump', f='disl.dump') results_dict['dumpfile_disl'] = 'disl.dump' results_dict['symbols_disl'] = system_disl.symbols results_dict['E_total_disl'] = relaxed['E_total'] # Cleanup files os.remove('0.dump') os.remove(relaxed['dumpfile']) for dumpjsonfile in glob.iglob('*.dump.json'): os.remove(dumpjsonfile) return results_dict
def relax_static(lammps_command, system, potential, mpi_command=None, p_xx=0.0, p_yy=0.0, p_zz=0.0, p_xy=0.0, p_xz=0.0, p_yz=0.0, dispmult=0.0, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom'), maxcycles=100, ctol=1e-10): """ 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: - **'relaxed_system'** (*float*) - The relaxed system. - **'E_coh'** (*float*) - The cohesive energy of the relaxed system. - **'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. """ # 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): old_system = deepcopy(system) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='init.dat', units=potential.units, atom_style=potential.atom_style) lammps_variables['atomman_system_info'] = system_info lammps_variables['atomman_pair_info'] = potential.pair_info(system.symbols) 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_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 template_file = 'minbox.template' lammps_script = 'minbox.in' with open(template_file) as f: template = f.read() with open(lammps_script, 'w') as f: f.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS and extract thermo data logfile = 'log-' + str(cycle) + '.lammps' output = lmp.run(lammps_command, lammps_script, mpi_command, logfile=logfile) thermo = output.simulations[0]['thermo'] # Clean up dump files os.remove('0.dump') 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_coh'] = 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 todict(self, full=True, flat=False): """ Converts the structured content to a simpler dictionary. Parameters ---------- full : bool, optional Flag used by the calculation records. A True value will include terms for both the calculation's input and results, while a value of False will only include input terms (Default is True). flat : bool, optional Flag affecting the format of the dictionary terms. If True, the dictionary terms are limited to having only str, int, and float values, which is useful for comparisons. If False, the term values can be of any data type, which is convenient for analysis. (Default is False). Returns ------- dict A dictionary representation of the record's content. """ calc = self.content[self.contentroot] params = {} params['key'] = calc['key'] params['script'] = calc['calculation']['script'] params['iprPy_version'] = calc['calculation']['iprPy-version'] params['symmetryprecision'] = calc['calculation']['run-parameter'][ 'symmetryprecision'] params['primitivecell'] = calc['calculation']['run-parameter'][ 'primitivecell'] params['idealcell'] = calc['calculation']['run-parameter']['idealcell'] params['load_file'] = calc['system-info']['artifact']['file'] params['load_style'] = calc['system-info']['artifact']['format'] params['load_options'] = calc['system-info']['artifact'][ 'load_options'] params['family'] = calc['system-info']['family'] symbols = aslist(calc['system-info']['symbol']) if flat is True: try: params['symbols'] = ' '.join(symbols) except: params['symbols'] = np.nan else: params['symbols'] = symbols params['status'] = calc.get('status', 'finished') params['error'] = calc.get('error', np.nan) if full is True and params['status'] == 'finished': ucell = am.load('system_model', self.content, key='unit-cell-atomic-system') params['pearson_symbol'] = calc['Pearson-symbol'] params['spacegroup_number'] = calc['space-group']['number'] params['spacegroup_international'] = calc['space-group'][ 'Hermann-Maguin'] params['spacegroup_Schoenflies'] = calc['space-group'][ 'Schoenflies'] params['wykoff_letters'] = ' '.join( calc['space-group'].finds('letter')) if flat is True: params['a'] = ucell.box.a params['b'] = ucell.box.b params['c'] = ucell.box.c params['alpha'] = ucell.box.alpha params['beta'] = ucell.box.beta params['gamma'] = ucell.box.gamma params['natoms'] = ucell.natoms else: params['ucell'] = ucell return params
def todict(self, full=True, flat=False): """ Converts the structured content to a simpler dictionary. Parameters ---------- full : bool, optional Flag used by the calculation records. A True value will include terms for both the calculation's input and results, while a value of False will only include input terms (Default is True). flat : bool, optional Flag affecting the format of the dictionary terms. If True, the dictionary terms are limited to having only str, int, and float values, which is useful for comparisons. If False, the term values can be of any data type, which is convenient for analysis. (Default is False). Returns ------- dict A dictionary representation of the record's content. """ # Extract universal content params = super().todict(full=full, flat=flat) calc = self.content[self.contentroot] params['symmetryprecision'] = calc['calculation']['run-parameter'][ 'symmetryprecision'] params['primitivecell'] = calc['calculation']['run-parameter'][ 'primitivecell'] params['idealcell'] = calc['calculation']['run-parameter']['idealcell'] # Extract system info subset('atomman_systemload').todict(calc, params, full=full, flat=flat) params['status'] = calc.get('status', 'finished') params['error'] = calc.get('error', np.nan) if full is True and params['status'] == 'finished': ucell = am.load('system_model', self.content, key='unit-cell-atomic-system') params['pearson_symbol'] = calc['Pearson-symbol'] params['spacegroup_number'] = calc['space-group']['number'] params['spacegroup_international'] = calc['space-group'][ 'Hermann-Maguin'] params['spacegroup_Schoenflies'] = calc['space-group'][ 'Schoenflies'] params['wykoff_fingerprint'] = calc['space-group'][ 'Wyckoff-fingerprint'] params['composition'] = ucell.composition if flat is True: params['a'] = ucell.box.a params['b'] = ucell.box.b params['c'] = ucell.box.c params['alpha'] = ucell.box.alpha params['beta'] = ucell.box.beta params['gamma'] = ucell.box.gamma params['natoms'] = ucell.natoms else: params['ucell'] = ucell return params
def load_ucell(self, **kwargs): """ Wrapper around atomman.load() for loading files that also saves the file loading options as class attributes. Any parameters not given will use the values already set to the object. Parameters ---------- load_style : str, optional The style for atomman.load() to use. load_file : str, optional The path to the file to load. symbols : list or None, optional The list of interaction model symbols to associate with the atom types in the load file. A value of None will default to the symbols listed in the load file if the style contains that information. load_options : dict, optional Any other atomman.load() keyword options to use when loading. box_parameters : list or None, optional A list of 3 orthorhombic box parameters or 6 trigonal box length and angle parameters to scale the loaded system by. Setting a value of None will perform no scaling. family : str or None, optional The system's family identifier. If None, then the family will be set according to the family value in the load file if it has one, or as the load file's name otherwise. """ self.set_values(**kwargs) # Check for file and contents if self.load_content is not None: load_file = self.load_content elif self.load_file is not None: load_file = self.load_file else: raise ValueError('load_file not set') # Change load symbols kwarg to None if symbols attribute is empty if self.symbols is None or len(self.symbols) == 0: symbols = None else: symbols = self.symbols # Load ucell self.__ucell = am.load(self.load_style, load_file, symbols=symbols, **self.load_options) self.ucell.wrap() # Update object's symbols and composition self.symbols = self.ucell.symbols self.composition self.scale_ucell() # Add model-specific charges if needed try: potential = self.parent.potential.potential if 'charge' not in self.ucell.atoms_prop(): self.ucell.atoms.prop_atype('charge', potential.charges(self.ucell.symbols)) except: pass
def dislocationmonopole(lammps_command, system, potential, burgers, C, mpi_command=None, axes=None, m=[0, 1, 0], n=[0, 0, 1], lineboxvector='a', randomseed=None, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom'), annealtemp=0.0, bshape='circle', bwidth=uc.set_in_units(10, 'angstrom')): """ Creates and relaxes a dislocation monopole system. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The bulk system to add the defect to. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. burgers : list or numpy.array of float The burgers vector for the dislocation being added. C : atomman.ElasticConstants The system's elastic constants. mpi_command : str or None, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. axes : numpy.array of float or None, optional The 3x3 axes used to rotate the system by during creation. If given, will be used to transform burgers and C from the standard crystallographic orientations to the system's Cartesian units. 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). annealtemp : float, optional The temperature to perform a dynamic relaxation at. (Default is 0.0, which will skip the dynamic relaxation.) bshape : str, optional The shape to make the boundary region. Options are 'circle' and 'rect' (default is 'circle'). bwidth : float, optional The minimum thickness of the boundary region (default is 10 Angstroms). Returns ------- dict Dictionary of results consisting of keys: - **'dumpfile_base'** (*str*) - The filename of the LAMMPS dump file for the relaxed base system. - **'symbols_base'** (*list of str*) - The list of element-model symbols for the Potential that correspond to the base system's atypes. - **'Stroh_preln'** (*float*) - The pre-logarithmic factor in the dislocation's self-energy expression. - **'Stroh_K_tensor'** (*numpy.array of float*) - The energy coefficient tensor based on the dislocation's Stroh solution. - **'dumpfile_disl'** (*str*) - The filename of the LAMMPS dump file for the relaxed dislocation monopole system. - **'symbols_disl'** (*list of str*) - The list of element-model symbols for the Potential that correspond to the dislocation monopole system's atypes. - **'E_total_disl'** (*float*) - The total potential energy of the dislocation monopole system. """ # Initialize results dict results_dict = {} # Save initial perfect system system.dump('atom_dump', f='base.dump') results_dict['dumpfile_base'] = 'base.dump' results_dict['symbols_base'] = system.symbols # Solve Stroh method for dislocation stroh = am.defect.Stroh(C, burgers, axes=axes, m=m, n=n) results_dict['Stroh_preln'] = stroh.preln results_dict['Stroh_K_tensor'] = stroh.K_tensor # Generate dislocation system by displacing atoms disp = stroh.displacement(system.atoms.pos) system.atoms.pos += disp # Apply fixed boundary conditions system = disl_boundary_fix(system, bwidth, bshape=bshape, lineboxvector=lineboxvector, m=m, n=n) # Relax system relaxed = disl_relax(lammps_command, system, potential, mpi_command=mpi_command, annealtemp=annealtemp, etol=etol, ftol=ftol, maxiter=maxiter, maxeval=maxeval) # Save relaxed dislocation system with original box vects system_disl = am.load('atom_dump', relaxed['dumpfile'], symbols=system.symbols) system_disl.box_set(vects=system.box.vects, origin=system.box.origin) system_disl.dump('atom_dump', f='disl.dump') results_dict['dumpfile_disl'] = 'disl.dump' results_dict['symbols_disl'] = system_disl.symbols results_dict['E_total_disl'] = relaxed['E_total'] # Cleanup files os.remove('0.dump') os.remove(relaxed['dumpfile']) for dumpjsonfile in glob.iglob('*.dump.json'): os.remove(dumpjsonfile) return results_dict
def relax_dynamic(lammps_command, system, potential, mpi_command=None, p_xx=0.0, p_yy=0.0, p_zz=0.0, p_xy=0.0, p_xz=0.0, p_yz=0.0, temperature=0.0, integrator=None, runsteps=220000, thermosteps=100, dumpsteps=None, equilsteps=20000, randomseed=None): """ 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). 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: - **'relaxed_system'** (*float*) - The relaxed system. - **'E_coh'** (*float*) - The mean measured cohesive 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_coh_std'** (*float*) - The standard deviation in the measured cohesive 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 # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='init.dat', units=potential.units, atom_style=potential.atom_style) lammps_variables['atomman_system_info'] = system_info lammps_variables['atomman_pair_info'] = potential.pair_info(system.symbols) 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_variables['integrator_info'] = integ_info lammps_variables['thermosteps'] = thermosteps lammps_variables['runsteps'] = runsteps lammps_variables['dumpsteps'] = dumpsteps # 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_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 template_file = 'full_relax.template' lammps_script = 'full_relax.in' with open(template_file) as f: template = f.read() with open(lammps_script, 'w') as f: f.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) # Run lammps output = lmp.run(lammps_command, lammps_script, mpi_command) # Extract LAMMPS thermo data. results = {} thermo = output.simulations[0]['thermo'] results['dumpfile_initial'] = '0.dump' results['symbols_initial'] = system.symbols # Load relaxed system from dump file last_dump_file = str(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_coh'] = uc.set_in_units(thermo.PotEng.mean() / natoms, lammps_units['energy']) results['E_coh_std'] = uc.set_in_units(thermo.PotEng.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