def __init__(self, model=None, name=None, params=None, **kwargs): """Initializes a Calculation object for a given style.""" # Initialize subsets used by the calculation self.__potential = LammpsPotential(self) self.__commands = LammpsCommands(self) self.__units = Units(self) subsets = (self.commands, self.potential, self.units) # Initialize unique calculation attributes self.symbols = None self.number_of_steps_r = 100 self.minimum_r = uc.set_in_units(0.5, 'angstrom') self.maximum_r = uc.set_in_units(6.0, 'angstrom') self.number_of_steps_theta = 100 self.minimum_theta = 1.0 self.maximum_theta = 180.0 self.cluster = None self.results_file = None self.results_length_unit = None self.results_energy_unit = None # Define calc shortcut self.calc = bond_angle_scan # Call parent constructor super().__init__(model=model, name=name, params=params, subsets=subsets, **kwargs)
def __init__(self, model=None, name=None, params=None, **kwargs): """Initializes a Calculation object for a given style.""" # Initialize subsets used by the calculation self.__potential = LammpsPotential(self) self.__commands = LammpsCommands(self) self.__units = Units(self) subsets = (self.commands, self.potential, self.units) # Initialize unique calculation attributes self.symbols = [] self.number_of_steps_r = 300 self.minimum_r = uc.set_in_units(0.02, 'angstrom') self.maximum_r = uc.set_in_units(6.0, 'angstrom') self.r_values = None self.energy_values = None # Define calc shortcut self.calc = diatom_scan # Call parent constructor super().__init__(model=model, name=name, params=params, subsets=subsets, **kwargs)
def lammps_ELASTIC(lammps_command, system, potential, symbols, mpi_command=None, strainrange=1e-6, etol=0.0, ftol=0.0, maxiter=100, maxeval=1000, dmax=0.01, pressure_unit='GPa'): """Sets up and runs the ELASTIC example distributed with LAMMPS""" #Get lammps units lammps_units = lmp.style.unit(potential.units) #Define lammps variables lammps_variables = {} lammps_variables['atomman_system_info'] = lmp.atom_data.dump(system, 'init.dat', units=potential.units, atom_style=potential.atom_style) lammps_variables['atomman_pair_info'] = potential.pair_info(symbols) lammps_variables['strainrange'] = strainrange lammps_variables['pressure_unit_scaling'] = uc.get_in_units(uc.set_in_units(1.0, lammps_units['pressure']), pressure_unit) lammps_variables['pressure_unit'] = pressure_unit lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = uc.get_in_units(dmax, lammps_units['length']) #Fill in mod.template files with open('init.mod.template') as template_file: template = template_file.read() with open('init.mod', 'w') as in_file: in_file.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) with open('potential.mod.template') as template_file: template = template_file.read() with open('potential.mod', 'w') as in_file: in_file.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) output = lmp.run(lammps_command, 'in.elastic', mpi_command) #Extract output values relaxed = output['LAMMPS-log-thermo-data']['simulation'][0] results = {} results['E_coh'] = uc.set_in_units(relaxed['thermo']['PotEng'][-1], lammps_units['energy']) / system.natoms results['lx'] = uc.set_in_units(relaxed['thermo']['Lx'][-1], lammps_units['length']) results['ly'] = uc.set_in_units(relaxed['thermo']['Ly'][-1], lammps_units['length']) results['lz'] = uc.set_in_units(relaxed['thermo']['Lz'][-1], lammps_units['length']) with open('log.lammps') as log_file: log = log_file.read() start = log.find('print "Elastic Constant C11all = ${C11all} ${cunits}"') lines = log[start+54:].split('\n') for line in lines: terms = line.split() if len(terms) > 0 and terms[0] == 'Elastic': c_term = terms[2][:3] c_value = terms[4] results['c_unit'] = terms[5] results[c_term] = c_value return results
def fcc_edge(self): axes = np.array([[1, 0, -1], [1, 1, 1], [1, -2, 1]]) alat = uc.set_in_units(4.0248, 'angstrom') C11 = uc.set_in_units(113.76, 'GPa') C12 = uc.set_in_units(61.71, 'GPa') C44 = uc.set_in_units(31.25, 'GPa') c = am.ElasticConstants(C11=C11, C12=C12, C44=C44) burgers = alat / 2 * np.array([1., 0., -1.]) # initializing a new Stroh object using the data stroh = am.defect.Stroh(c, burgers, axes=axes) pos_test = uc.set_in_units(np.array([12.4, 13.5, -10.6]), 'angstrom') disp = stroh.displacement(pos_test) print("displacement =", uc.get_in_units(disp, 'angstrom'), 'angstrom') # monopole system box = am.Box(a=alat, b=alat, c=alat) atoms = am.Atoms(natoms=4, prop={ 'atype': 1, 'pos': [[0.0, 0.0, 0.0], [0.5, 0.5, 0.0], [0.0, 0.5, 0.5], [0.5, 0.0, 0.5]] }) ucell = am.System(atoms=atoms, box=box, scale=True) system = am.rotate_cubic(ucell, axes) shift = np.array( [0.12500000000000, 0.50000000000000, 0.00000000000000]) new_pos = system.atoms_prop(key='pos', scale=True) + shift system.atoms_prop(key='pos', value=new_pos, scale=True) system.supersize((-7, 7), (-6, 6), (0, 1)) disp = stroh.displacement(system.atoms_prop(key='pos')) system.atoms_prop(key='pos', value=system.atoms_prop(key='pos') + disp) system.pbc = (False, False, True) system.wrap() pos = system.atoms_prop(key='pos') x = uc.get_in_units(pos[:, 0], 'angstrom') y = uc.get_in_units(pos[:, 1], 'angstrom') plt.figure(figsize=(8, 8)) plt.scatter(x, y, s=30) plt.xlim(min(x), max(x)) plt.ylim(min(y), max(y)) plt.xlabel('x-position (Angstrom)', fontsize='large') plt.ylabel('y-position (Angstrom)', fontsize='large') plt.show()
def gb_energy(lammps_command, potential, symbols, alat, axes_1, axes_2, E_coh, mpi_command=None, xshift=0.0, zshift=0.0): """Computes the grain boundary energy using the grain_boundary.in LAMMPS script""" axes_1 = np.asarray(axes_1, dtype=int) axes_2 = np.asarray(axes_2, dtype=int) lx = alat * np.linalg.norm(axes_1[0]) lz = 2 * alat * np.linalg.norm(axes_1[2]) mesh_dir = 'mesh-%.8f-%.8f' %(xshift, zshift) if not os.path.isdir(mesh_dir): os.makedirs(mesh_dir) #Get lammps units lammps_units = lmp.style.unit(potential.units) #Define lammps variables lammps_variables = {} lammps_variables['units'] = potential.units lammps_variables['atom_style'] = potential.atom_style lammps_variables['atomman_pair_info'] = potential.pair_info(symbols) lammps_variables['alat'] = uc.get_in_units(alat, lammps_units['length']) lammps_variables['xsize'] = uc.get_in_units(lx, lammps_units['length']) lammps_variables['zsize'] = uc.get_in_units(lz, lammps_units['length']) lammps_variables['xshift'] = uc.get_in_units(xshift, lammps_units['length']) lammps_variables['zshift'] = uc.get_in_units(zshift, lammps_units['length']) lammps_variables['x_axis_1'] = str(axes_1[0]).strip('[] ') lammps_variables['y_axis_1'] = str(axes_1[1]).strip('[] ') lammps_variables['z_axis_1'] = str(axes_1[2]).strip('[] ') lammps_variables['x_axis_2'] = str(axes_2[0]).strip('[] ') lammps_variables['y_axis_2'] = str(axes_2[1]).strip('[] ') lammps_variables['z_axis_2'] = str(axes_2[2]).strip('[] ') lammps_variables['mesh_dir'] = 'mesh-%.8f-%.8f' %(xshift, zshift) #Fill in mod.template files with open('grain_boundary.template') as template_file: template = template_file.read() lammps_input = os.path.join(mesh_dir, 'grain_boundary.in') with open(lammps_input, 'w') as in_file: in_file.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) output = lmp.run(lammps_command, lammps_input, mpi_command) #Extract output values try: E_total = uc.set_in_units(output.finds('c_eatoms')[-1], lammps_units['energy']) natoms = output.finds('v_natoms')[-1] except: E_total = uc.set_in_units(output.finds('eatoms')[-1], lammps_units['energy']) natoms = output.finds('natoms')[-1] #Compute grain boundary energy E_gb = (E_total - E_coh*natoms) / (lx * lz) return E_gb
def peierlsnabarro(alat, C, axes, burgers, gamma, cutofflongrange=uc.set_in_units(1000, 'angstrom'), tau=np.zeros((3,3)), alpha=[0.0], beta=np.zeros((3,3)), cdiffelastic=False, cdiffsurface=True, cdiffstress=False, fullstress=True, halfwidth=uc.set_in_units(1, 'angstrom'), normalizedisreg=True, xnum=None, xmax=None, xstep=None, min_method='Powell', min_options={}): """ Solves a Peierls-Nabarro dislocation model. """ # Solve Stroh method for dislocation stroh = am.defect.Stroh(C, burgers, axes=axes) Kij = stroh.K_tensor # Transform burgers to axes T = am.tools.axes_check(axes) b = T.dot(burgers) # Scale xmax and xstep by alat if xmax is not None: xmax *= alat if xstep is not None: xstep *= alat # Generate initial disregistry guess x, idisreg = pn_arctan_disregistry(xmax=xmax, xstep=xstep, xnum=xnum, burgers=b, halfwidth=halfwidth, normalize=normalizedisreg) # Minimize disregistry pnsolution = SDVPN(x, idisreg, gamma, axes, Kij, tau=tau, alpha=alpha, beta=beta, cutofflongrange=cutofflongrange, burgers=b, fullstress=fullstress, cdiffelastic=cdiffelastic, cdiffsurface=cdiffsurface, cdiffstress=cdiffstress, min_method=min_method, min_options=min_options) # Initialize results dict results_dict = {} results_dict['SDVPN_solution'] = pnsolution return results_dict
def cal_screw_const(self, tag='intro'): axes = np.array([[1, 1, -2], [-1, 1, 0], [1, 1, 1]]) alat = uc.set_in_units(self.pot['lattice'], 'angstrom') C11 = uc.set_in_units(self.pot['c11'], 'GPa') C12 = uc.set_in_units(self.pot['c12'], 'GPa') C44 = uc.set_in_units(self.pot['c44'], 'GPa') c = am.ElasticConstants(C11=C11, C12=C12, C44=C44) burgers = alat / 2 * np.array([1., 1., 1.]) stroh = am.defect.Stroh(c, burgers, axes=axes) print("K tensor", stroh.K_tensor) print("K (biKijbj)", stroh.K_coeff, "eV/A") print("pre-ln alpha = biKijbj/4pi", stroh.preln, "ev/A")
def __init__(self, model=None, name=None, params=None, **kwargs): """Initializes a Calculation object for a given style.""" # Initialize subsets used by the calculation self.__potential = LammpsPotential(self) self.__commands = LammpsCommands(self) self.__units = Units(self) self.__system = AtommanSystemLoad(self) self.__system_mods = AtommanSystemManipulate(self) subsets = (self.commands, self.potential, self.system, self.system_mods, self.units) # Initialize unique calculation attributes self.strainrange = 0.01 self.displacementdistance = uc.set_in_units(0.01, 'angstrom') self.symmetryprecision = 1e-5 self.numstrains = 5 self.__bandstructure = None self.__dos = None self.__thermal = None self.__volumescan = None self.__E0 = None self.__B0 = None self.__B0prime = None self.__V0 = None self.__phonons = None self.__qha = None # Define calc shortcut self.calc = phonon_quasiharmonic # Call parent constructor super().__init__(model=model, name=name, params=params, subsets=subsets, **kwargs)
def __init__(self, parent, prefix='', templateheader=None, templatedescription=None): """ Initializes a calculation record subset object. Parameters ---------- parent : iprPy.calculation.Calculation The parent calculation object that the subset object is part of. This allows for the subset methods to access parameters set to the calculation itself or other subsets. prefix : str, optional An optional prefix to add to metadata field names to allow for differentiating between multiple subsets of the same style within a single record templateheader : str, optional An alternate header to use in the template file for the subset. templatedescription : str, optional An alternate description of the subset for the templatedoc. """ super().__init__(parent, prefix=prefix, templateheader=templateheader, templatedescription=templatedescription) self.energytolerance = 0.0 self.forcetolerance = 0.0 self.maxiterations = 100000 self.maxevaluations = 1000000 self.maxatommotion = uc.set_in_units(0.01, 'angstrom')
def test_scalar_model(self): unit = 'mJ/s^2' v = 1234.214 value = uc.set_in_units(v, unit) model = uc.model(value, unit) value2 = uc.value_unit(model) assert pytest.approx(value, value2)
def test_tensor_model(self): unit = 'mJ/s^2' v = np.array([[1234.214, 346.23], [109.124, 235.781]]) value = uc.set_in_units(v, unit) model = uc.model(value, unit) value2 = uc.value_unit(model) assert np.allclose(value, value2)
def test_vector_model(self): unit = 'mJ/s^2' v = np.array([1234.214, 346.23]) value = uc.set_in_units(v, unit) model = uc.model(value, unit) value2 = uc.value_unit(model) assert np.allclose(value, value2)
def value(input_dict, key, default_unit=None, default_term=None): """ Converts a string dictionary value into a float with proper unit conversion. The string can either be: a number a number and unit separated by a single space Keyword Arguments: input_dict -- a dictionary key -- the key for the value in input_dict default_unit -- unit for the value if not specified in the string. default_term -- string of the value (and unit) to use if key is not in input_dict. Note that the unit in default_term does not have to correspond to default_unit. This allows for default values to be constant regardless of preferred units. returns the value as a float in atomman's working units. """ term = input_dict.get(key, default_term) try: i = term.strip().index(' ') value = float(term[:i]) unit = term[i + 1:] except: value = float(term) unit = default_unit return uc.set_in_units(value, unit)
def __init__(self, model=None, name=None, params=None, **kwargs): """Initializes a Calculation object for a given style.""" # Initialize subsets used by the calculation self.__units = Units(self) self.__system = AtommanSystemLoad(self) subsets = (self.system, self.units) # Initialize unique calculation attributes self.primitivecell = False self.idealcell = True self.symmetryprecision = uc.set_in_units(0.01, 'angstrom') self.__pearson = None self.__number = None self.__international = None self.__schoenflies = None self.__wyckoffs = None self.__wyckoff_fingerprint = None self.__spg_ucell = None # Define calc shortcut self.calc = crystal_space_group # Call parent constructor super().__init__(model=model, name=name, params=params, subsets=subsets, **kwargs)
def dumbbell(system, atype=None, pos=None, ptd_id=None, db_vect=None, scale=False, atol=None): """ Returns a new System where a dumbbell interstitial point defect has been inserted. Keyword Arguments: system -- base System that the defect is added to. atype -- atom type for the atom in the dumbbell pair being added to the system. pos -- position of the system atom where the dumbbell pair is added. ptd_id -- id of the system atom where the dumbbell pair is added. Alternative to using pos. db_vect -- vector associated with the dumbbell interstitial. scale -- indicates if pos and db_vect are absolute (False) or box-relative (True). Default is False. Adds atom property old_id if it doesn't already exist that tracks the original atom ids. """ pos_list = system.atoms.view['pos'] #if pos is supplied, use isclose and where to identify the id of the atom at pos if pos is not None: if atol is None: atol = uc.set_in_units(1e-3, 'angstrom') if scale: pos = system.unscale(pos) assert ptd_id is None, 'pos and ptd_id cannot both be supplied' ptd_id = np.where(np.isclose(pos_list, pos, atol=atol).all(axis=1)) assert len(ptd_id) == 1 and len(ptd_id[0]) == 1, 'Unique atom at pos not identified' ptd_id = long(ptd_id[0][0]) #test that ptd_id is a valid entry try: pos = pos_list[ptd_id] except: raise TypeError('Invalid ptd_id') assert isinstance(atype, (int, long)) and atype > 0, 'atype must be a positive integer' #unscale db_vect if scale is True if scale: db_vect = system.unscale(db_vect) #create new system and copy values over d_system = am.System(box=system.box, pbc=system.pbc, atoms=am.Atoms(natoms=system.natoms+1)) for prop in system.atoms_prop(): view = system.atoms.view[prop] value = np.asarray(np.vstack(( view[:ptd_id], view[ptd_id+1:], view[ptd_id], np.zeros_like(view[0]) )), dtype=system.atoms.dtype[prop]) d_system.atoms_prop(key=prop, value=value) d_system.atoms_prop(a_id=d_system.natoms-1, key='atype', value=atype) d_system.atoms_prop(a_id=d_system.natoms-2, key='pos', value=pos-db_vect) d_system.atoms_prop(a_id=d_system.natoms-1, key='pos', value=pos+db_vect) #add property old_id with each atom's original id if d_system.atoms_prop(a_id=0, key='old_id') is None: d_system.atoms_prop(key='old_id', value=np.hstack(( np.arange(0, ptd_id), np.arange(ptd_id+1, system.natoms), ptd_id, system.natoms)), dtype='int32') else: old_id = max(system.atoms_prop(key='old_id')) + 1 d_system.atoms_prop(a_id=d_system.natoms-1, key='old_id', value=old_id) return d_system
def __init__(self, model=None, name=None, params=None, **kwargs): """Initializes a Calculation object for a given style.""" # Initialize subsets used by the calculation self.__units = Units(self) self.__system = AtommanSystemLoad(self) self.__defect = Dislocation(self) self.__elastic = AtommanElasticConstants(self) self.__gamma = AtommanGammaSurface(self) subsets = (self.system, self.elastic, self.gamma, self.defect, self.units) # Initialize unique calculation attributes self.xnum = None self.xmax = None self.xstep = None self.xscale = False self.minimize_style = 'Powell' self.minimize_options = {} self.minimize_cycles = 10 self.cutofflongrange = uc.set_in_units(1000, 'angstrom') self.tau = np.zeros((3, 3)) self.alpha = 0.0 self.beta = np.zeros((3, 3)) self.cdiffelastic = False self.cdiffsurface = True self.cdiffstress = False self.halfwidth = uc.set_in_units(1, 'angstrom') self.normalizedisreg = True self.fullstress = True self.__sdvpn_solution = None self.__energies = None self.__disregistries = None # Define calc shortcut self.calc = sdvpn # Call parent constructor super().__init__(model=model, name=name, params=params, subsets=subsets, **kwargs)
def ecoh_vs_r(lammps_exe, ucell, potential, symbols, rmin=2.0, rmax=5.0, rsteps=200): """Measure cohesive energy of a crystal as a function of nearest neighbor distance, r0""" #Initial size setup r_a = r_a_ratio(ucell) amin = rmin / r_a amax = rmax / r_a alat0 = (amax + amin) / 2. delta = (amax-amin)/alat0 #Create unit cell with a = alat0 ucell.box_set(a = alat0, b = alat0 * ucell.box.b / ucell.box.a, c = alat0 * ucell.box.c / ucell.box.a, scale=True) #LAMMPS script setup pair_info = potential.pair_info(symbols) system_info = lmp.sys_gen(units = potential.units, atom_style = potential.atom_style, ucell = ucell, size = np.array([[0,3], [0,3], [0,3]], dtype=np.int)) #Write the LAMMPS input file with open('alat.in','w') as script: script.write(alat_script(system_info, pair_info, delta=delta, steps=rsteps)) #Run LAMMPS data = lmp.run(lammps_exe, 'alat.in') #extract thermo data from log output avalues = np.array(data.finds('Lx')) / 3. rvalues = avalues * r_a evalues = np.array(data.finds('peatom')) #Use potential units and atom_style terms to convert values to appropriate length and energy units lmp_units = lmp.style.unit(potential.units) avalues = uc.set_in_units(avalues, lmp_units['length']) rvalues = uc.set_in_units(rvalues, lmp_units['length']) evalues = uc.set_in_units(evalues, lmp_units['energy']) return rvalues, avalues, evalues
def energy_check(lammps_command: str, system: am.System, potential: lmp.Potential, mpi_command: Optional[str] = None) -> dict: """ Performs a quick run 0 calculation to evaluate the potential energy of a configuration. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The atomic configuration to evaluate. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. Returns ------- dict Dictionary of results consisting of keys: - **'E_pot'** (*float*) - The per-atom potential energy of the system. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='init.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info # Fill in lammps input script template = read_calc_file('iprPy.calculation.energy_check', 'run0.template') script = filltemplate(template, lammps_variables, '<', '>') # Run LAMMPS output = lmp.run(lammps_command, script=script, mpi_command=mpi_command, logfile=None) # Extract output values thermo = output.simulations[-1]['thermo'] results = {} results['E_pot'] = uc.set_in_units(thermo.v_peatom.values[-1], lammps_units['energy']) return results
def vacancy(system, pos=None, ptd_id=None, scale=False, atol=None): """ Returns a new System where a vacancy point defect has been inserted. Keyword Arguments: system -- base System that the defect is added to. pos -- position of the atom to be removed. ptd_id -- id of the atom to be removed. Alternative to using pos. scale -- if pos is given, indicates if pos is absolute (False) or box-relative (True). Default is False. Adds atom property old_id if it doesn't already exist that tracks the original atom ids. """ pos_list = system.atoms.view['pos'] #if pos is supplied, use isclose and where to identify the id of the atom at pos if pos is not None: if atol is None: atol = uc.set_in_units(1e-3, 'angstrom') if scale: pos = system.unscale(pos) assert ptd_id is None, 'pos and ptd_id cannot both be supplied' ptd_id = np.where(np.isclose(pos_list, pos, atol=atol).all(axis=1)) assert len(ptd_id) == 1 and len( ptd_id[0]) == 1, 'Unique atom at pos not identified' ptd_id = long(ptd_id[0][0]) #test that ptd_id is a valid entry try: pos = pos_list[ptd_id] except: raise TypeError('Invalid ptd_id') #create new system and copy values over d_system = am.System(box=system.box, pbc=system.pbc, atoms=am.Atoms(natoms=system.natoms - 1)) for prop in system.atoms_prop(): view = system.atoms.view[prop] value = np.asarray(np.vstack((view[:ptd_id], view[ptd_id + 1:])), dtype=system.atoms.dtype[prop]) d_system.atoms_prop(key=prop, value=value) #add property old_id with each atom's original id if d_system.atoms_prop(key='old_id') is None: d_system.atoms_prop(key='old_id', value=np.hstack((np.arange(0, ptd_id), np.arange(ptd_id + 1, system.natoms))), dtype='int32') return d_system
def interstitial(system, atype=None, pos=None, scale=False, atol=None): """ Returns a new System where a positional interstitial point defect has been inserted. Keyword Arguments: system -- base System that the defect is added to. atype -- atom type for the interstitial atom. pos -- position for adding the interstitial atom. scale -- if pos is given, indicates if pos is absolute (False) or box-relative (True). Default is False. Adds atom property old_id if it doesn't already exist that tracks the original atom ids. """ pos_list = system.atoms.view['pos'] if atol is None: atol = uc.set_in_units(1e-3, 'angstrom') if scale: pos = system.unscale(pos) #Use isclose and where to check that no atoms are already at pos ptd_id = np.where(np.isclose(pos_list, pos, atol=atol).all(axis=1)) assert len(ptd_id) == 1 and len(ptd_id[0]) == 0, 'atom already at pos' assert isinstance( atype, (int, long)) and atype > 0, 'atype must be a positive integer' #create new system and copy values over d_system = am.System(box=system.box, pbc=system.pbc, atoms=am.Atoms(natoms=system.natoms + 1)) for prop in system.atoms_prop(): view = system.atoms.view[prop] value = np.asarray(np.vstack((view, np.zeros_like(view[0]))), dtype=system.atoms.dtype[prop]) d_system.atoms_prop(key=prop, value=value) d_system.atoms_prop(a_id=d_system.natoms - 1, key='atype', value=atype) d_system.atoms_prop(a_id=d_system.natoms - 1, key='pos', value=pos) #add property old_id with each atom's original id if d_system.atoms_prop(key='old_id') is None: d_system.atoms_prop(key='old_id', value=np.arange(d_system.natoms), dtype='int32') else: old_id = max(system.atoms_prop(key='old_id')) + 1 d_system.atoms_prop(a_id=d_system.natoms - 1, key='old_id', value=old_id) return d_system
def stackingfaultworker(lammps_command, system, potential, shiftvector1, shiftvector2, shiftfraction1, shiftfraction2, mpi_command=None, cutboxvector=None, faultpos=0.5, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom'), lammps_date=None): """ A wrapper function around stackingfaultpoint. Converts shiftfractions and shiftvectors to a faultshift, runs stackingfaultpoint, and adds keys 'shift1' and 'shift2' to the returned dictionary corresponding to the shiftfractions. """ # Compute the faultshift faultshift = shiftfraction1 * shiftvector1 + shiftfraction2 * shiftvector2 # Name the simulation directory based on shiftfractions sim_directory = 'a%.10f-b%.10f' % (shiftfraction1, shiftfraction2) # Evaluate the system at the shift sf = stackingfaultpoint(lammps_command, system, potential, mpi_command=mpi_command, cutboxvector=cutboxvector, faultpos=faultpos, etol=etol, ftol=ftol, maxiter=maxiter, maxeval=maxeval, dmax=dmax, faultshift=faultshift, sim_directory=sim_directory, lammps_date=lammps_date) # Add shiftfractions to sf results sf['shift1'] = shiftfraction1 sf['shift2'] = shiftfraction2 return sf
def value(input_dict: dict, key: str, default_unit: Optional[str] = None, default_term: Optional[str] = None) -> float: """ Interprets a calculation parameter by converting it from a string to a float in working units. The parameter being converted is a str with one of two formats: - '<number>' - '<number> <unit>' Parameters ---------- input_dict : dict Dictionary containing input parameter key-value pairs. key : str The key of input_dict to evaluate. default_unit : str, optional Default unit to use if not specified in the parameter value. If not given, then no unit conversion will be done on unitless parameter values. default_term : str, optional Default str parameter value to use if key not in input_dict. Can be specified as '<value> <unit>' to ensure that the default value is always the same regardless of the working units or default_unit. If not given, then key must be in input_dict. Returns ------- float The interpreted value of the input parameter's str value in the working units. """ term = input_dict.get(key, default_term) try: i = term.strip().index(' ') value = float(term[:i]) unit = term[i + 1:] except: value = float(term) unit = default_unit return uc.set_in_units(value, unit)
def vacancy(system, pos=None, ptd_id=None, scale=False, atol=None): """ Returns a new System where a vacancy point defect has been inserted. Keyword Arguments: system -- base System that the defect is added to. pos -- position of the atom to be removed. ptd_id -- id of the atom to be removed. Alternative to using pos. scale -- if pos is given, indicates if pos is absolute (False) or box-relative (True). Default is False. Adds atom property old_id if it doesn't already exist that tracks the original atom ids. """ pos_list = system.atoms.view['pos'] #if pos is supplied, use isclose and where to identify the id of the atom at pos if pos is not None: if atol is None: atol = uc.set_in_units(1e-3, 'angstrom') if scale: pos = system.unscale(pos) assert ptd_id is None, 'pos and ptd_id cannot both be supplied' ptd_id = np.where(np.isclose(pos_list, pos, atol=atol).all(axis=1)) assert len(ptd_id) == 1 and len(ptd_id[0]) == 1, 'Unique atom at pos not identified' ptd_id = long(ptd_id[0][0]) #test that ptd_id is a valid entry try: pos = pos_list[ptd_id] except: raise TypeError('Invalid ptd_id') #create new system and copy values over d_system = am.System(box=system.box, pbc=system.pbc, atoms=am.Atoms(natoms=system.natoms-1)) for prop in system.atoms_prop(): view = system.atoms.view[prop] value = np.asarray(np.vstack(( view[:ptd_id], view[ptd_id+1:] )), dtype=system.atoms.dtype[prop]) d_system.atoms_prop(key=prop, value=value) #add property old_id with each atom's original id if d_system.atoms_prop(key='old_id') is None: d_system.atoms_prop(key='old_id', value=np.hstack(( np.arange(0, ptd_id), np.arange(ptd_id+1, system.natoms) )), dtype='int32') return d_system
def interstitial(system, atype=None, pos=None, scale=False, atol=None): """ Returns a new System where a positional interstitial point defect has been inserted. Keyword Arguments: system -- base System that the defect is added to. atype -- atom type for the interstitial atom. pos -- position for adding the interstitial atom. scale -- if pos is given, indicates if pos is absolute (False) or box-relative (True). Default is False. Adds atom property old_id if it doesn't already exist that tracks the original atom ids. """ pos_list = system.atoms.view['pos'] if atol is None: atol = uc.set_in_units(1e-3, 'angstrom') if scale: pos = system.unscale(pos) #Use isclose and where to check that no atoms are already at pos ptd_id = np.where(np.isclose(pos_list, pos, atol=atol).all(axis=1)) assert len(ptd_id) == 1 and len(ptd_id[0]) == 0, 'atom already at pos' assert isinstance(atype, (int, long)) and atype > 0, 'atype must be a positive integer' #create new system and copy values over d_system = am.System(box=system.box, pbc=system.pbc, atoms=am.Atoms(natoms=system.natoms+1)) for prop in system.atoms_prop(): view = system.atoms.view[prop] value = np.asarray(np.vstack(( view, np.zeros_like(view[0]) )), dtype=system.atoms.dtype[prop]) d_system.atoms_prop(key=prop, value=value) d_system.atoms_prop(a_id=d_system.natoms-1, key='atype', value=atype) d_system.atoms_prop(a_id=d_system.natoms-1, key='pos', value=pos) #add property old_id with each atom's original id if d_system.atoms_prop(key='old_id') is None: d_system.atoms_prop(key='old_id', value=np.arange(d_system.natoms), dtype='int32') else: old_id = max(system.atoms_prop(key='old_id')) + 1 d_system.atoms_prop(a_id=d_system.natoms-1, key='old_id', value=old_id) return d_system
def value(input_dict, key, default_unit=None, default_term=None): """ Interprets a calculation parameter by converting it from a string to a float in working units. The parameter being converted is a str with one of two formats: - '<number>' - '<number> <unit>' Parameters ---------- input_dict : dict Dictionary containing input parameter key-value pairs. key : str The key of input_dict to evaluate. default_unit : str, optional Default unit to use if not specified in the parameter value. If not given, then no unit conversion will be done on unitless parameter values. default_term : str, optional Default str parameter value to use if key not in input_dict. Can be specified as '<value> <unit>' to ensure that the default value is always the same regardless of the working units or default_unit. If not given, then key must be in input_dict. Returns ------- float The interpreted value of the input parameter's str value in the working units. """ term = input_dict.get(key, default_term) try: i = term.strip().index(' ') value = float(term[:i]) unit = term[i+1:] except: value = float(term) unit = default_unit return uc.set_in_units(value, unit)
def __init__(self, model=None, name=None, params=None, **kwargs): """Initializes a Calculation object for a given style.""" # Initialize subsets used by the calculation self.__potential = LammpsPotential(self) self.__commands = LammpsCommands(self) self.__units = Units(self) self.__system = AtommanSystemLoad(self) self.__minimize = LammpsMinimize(self) self.__defect = Dislocation(self) self.__elastic = AtommanElasticConstants(self) subsets = (self.commands, self.potential, self.system, self.elastic, self.minimize, self.defect, self.units) # Initialize unique calculation attributes self.annealtemperature = 0.0 self.annealsteps = None self.randomseed = None self.duplicatecutoff = uc.set_in_units(0.5, 'angstrom') self.boundarywidth = 0.0 self.boundaryscale = False self.onlylinear = False self.__dumpfile_base = None self.__dumpfile_defect = None self.__symbols_base = None self.__symbols_defect = None self.__potential_energy_defect = None self.__dislocation = None self.__preln = None self.__K_tensor = None # Define calc shortcut self.calc = dislocation_array # Call parent constructor super().__init__(model=model, name=name, params=params, subsets=subsets, **kwargs)
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 pointdiffusion(lammps_command, system, potential, point_kwargs, mpi_command=None, temperature=300, runsteps=200000, thermosteps=None, dumpsteps=0, equilsteps=20000, randomseed=None): """ Evaluates the diffusion rate of a point defect at a given temperature. This method will run two simulations: an NVT run at the specified temperature to equilibrate the system, then an NVE run to measure the defect's diffusion rate. The diffusion rate is evaluated using the mean squared displacement of all atoms in the system, and using the assumption that diffusion is only due to the added defect(s). Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. point_kwargs : dict or list of dict One or more dictionaries containing the keyword arguments for the atomman.defect.point() function to generate specific point defect configuration(s). mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. temperature : float, optional The temperature to run at (default is 300.0). runsteps : int, optional The number of integration steps to perform (default is 200000). thermosteps : int, optional Thermo values will be reported every this many steps (default is 100). dumpsteps : int or None, optional Dump files will be saved every this many steps (default is 0, which does not output dump files). equilsteps : int, optional The number of timesteps at the beginning of the simulation to exclude when computing average values (default is 20000). randomseed : int or None, optional Random number seed used by LAMMPS in creating velocities and with the Langevin thermostat. (Default is None which will select a random int between 1 and 900000000.) Returns ------- dict Dictionary of results consisting of keys: - **'natoms'** (*int*) - The number of atoms in the system. - **'temp'** (*float*) - The mean measured temperature. - **'pxx'** (*float*) - The mean measured normal xx pressure. - **'pyy'** (*float*) - The mean measured normal yy pressure. - **'pzz'** (*float*) - The mean measured normal zz pressure. - **'Epot'** (*numpy.array*) - The mean measured total potential energy. - **'temp_std'** (*float*) - The standard deviation in the measured temperature values. - **'pxx_std'** (*float*) - The standard deviation in the measured normal xx pressure values. - **'pyy_std'** (*float*) - The standard deviation in the measured normal yy pressure values. - **'pzz_std'** (*float*) - The standard deviation in the measured normal zz pressure values. - **'Epot_std'** (*float*) - The standard deviation in the measured total potential energy values. - **'dx'** (*float*) - The computed diffusion constant along the x-direction. - **'dy'** (*float*) - The computed diffusion constant along the y-direction. - **'dz'** (*float*) - The computed diffusion constant along the y-direction. - **'d'** (*float*) - The total computed diffusion constant. """ # Add defect(s) to the initially perfect system if not isinstance(point_kwargs, (list, tuple)): point_kwargs = [point_kwargs] for pkwargs in point_kwargs: system = am.defect.point(system, **pkwargs) # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Check that temperature is greater than zero if temperature <= 0.0: raise ValueError('Temperature must be greater than zero') # Handle default values if thermosteps is None: thermosteps = runsteps // 1000 if thermosteps == 0: thermosteps = 1 if dumpsteps is None: dumpsteps = runsteps if randomseed is None: randomseed = random.randint(1, 900000000) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='initial.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['temperature'] = temperature lammps_variables['runsteps'] = runsteps lammps_variables['equilsteps'] = equilsteps lammps_variables['thermosteps'] = thermosteps lammps_variables['dumpsteps'] = dumpsteps lammps_variables['randomseed'] = randomseed lammps_variables['timestep'] = lmp.style.timestep(potential.units) # Set dump_info if dumpsteps == 0: lammps_variables['dump_info'] = '' else: lammps_variables['dump_info'] = '\n'.join([ '', '# Define dump files', 'dump dumpit all custom ${dumpsteps} *.dump id type x y z c_peatom', 'dump_modify dumpit format <dump_modify_format>', '', ]) # Set dump_modify_format based on lammps_date if lammps_date < datetime.date(2016, 8, 3): lammps_variables['dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script template_file = 'diffusion.template' lammps_script = 'diffusion.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, 'diffusion.in', mpi_command) # Extract LAMMPS thermo data. thermo = output.simulations[1]['thermo'] temps = thermo.Temp.values pxxs = uc.set_in_units(thermo.Pxx.values, lammps_units['pressure']) pyys = uc.set_in_units(thermo.Pyy.values, lammps_units['pressure']) pzzs = uc.set_in_units(thermo.Pzz.values, lammps_units['pressure']) potengs = uc.set_in_units(thermo.PotEng.values, lammps_units['energy']) steps = thermo.Step.values # Read user-defined thermo data if output.lammps_date < datetime.date(2016, 8, 1): msd_x = uc.set_in_units(thermo['msd[1]'].values, lammps_units['length']+'^2') msd_y = uc.set_in_units(thermo['msd[2]'].values, lammps_units['length']+'^2') msd_z = uc.set_in_units(thermo['msd[3]'].values, lammps_units['length']+'^2') msd = uc.set_in_units(thermo['msd[4]'].values, lammps_units['length']+'^2') else: msd_x = uc.set_in_units(thermo['c_msd[1]'].values, lammps_units['length']+'^2') msd_y = uc.set_in_units(thermo['c_msd[2]'].values, lammps_units['length']+'^2') msd_z = uc.set_in_units(thermo['c_msd[3]'].values, lammps_units['length']+'^2') msd = uc.set_in_units(thermo['c_msd[4]'].values, lammps_units['length']+'^2') # Initialize results dict results = {} results['natoms'] = system.natoms # Get mean and std for temperature, pressure, and potential energy results['temp'] = np.mean(temps) results['temp_std'] = np.std(temps) results['pxx'] = np.mean(pxxs) results['pxx_std'] = np.std(pxxs) results['pyy'] = np.mean(pyys) results['pyy_std'] = np.std(pyys) results['pzz'] = np.mean(pzzs) results['pzz_std'] = np.std(pzzs) results['Epot'] = np.mean(potengs) results['Epot_std'] = np.std(potengs) # Convert steps to times times = steps * uc.set_in_units(lammps_variables['timestep'], lammps_units['time']) # Estimate diffusion rates # MSD_ptd = natoms * MSD_atoms (if one defect in system) # MSD = 2 * ndim * D * t --> D = MSD/t / (2 * ndim) mx = np.polyfit(times, system.natoms * msd_x, 1)[0] my = np.polyfit(times, system.natoms * msd_y, 1)[0] mz = np.polyfit(times, system.natoms * msd_z, 1)[0] m = np.polyfit(times, system.natoms * msd, 1)[0] results['dx'] = mx / 2 results['dy'] = my / 2 results['dz'] = mz / 2 results['d'] = m / 6 return results
def surface_energy(lammps_command, system, potential, mpi_command=None, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom'), cutboxvector='c'): """ Evaluates surface formation energies by slicing along one periodic boundary of a bulk system. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). 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'). Returns ------- dict Dictionary of results consisting of keys: - **'dumpfile_base'** (*str*) - The filename of the LAMMPS dump file of the relaxed bulk system. - **'dumpfile_surf'** (*str*) - The filename of the LAMMPS dump file of the relaxed system containing the free surfaces. - **'E_total_base'** (*float*) - The total potential energy of the relaxed bulk system. - **'E_total_surf'** (*float*) - The total potential energy of the relaxed system containing the free surfaces. - **'A_surf'** (*float*) - The area of the free surface. - **'E_coh'** (*float*) - The cohesive energy of the relaxed bulk system. - **'E_surf_f'** (*float*) - The computed surface formation energy. Raises ------ ValueError For invalid cutboxvectors """ # Evaluate perfect system system.pbc = [True, True, True] perfect = relax_system(lammps_command, system, potential, mpi_command=mpi_command, etol=etol, ftol=ftol, maxiter=maxiter, maxeval=maxeval, dmax=dmax) # Extract results from perfect system dumpfile_base = 'perfect.dump' shutil.move(perfect['finaldumpfile'], dumpfile_base) shutil.move('log.lammps', 'perfect-log.lammps') E_total_base = perfect['potentialenergy'] # Set up defect system # A_surf is area of parallelogram defined by the two box vectors not along # the cutboxvector if cutboxvector == 'a': system.pbc[0] = False A_surf = np.linalg.norm(np.cross(system.box.bvect, system.box.cvect)) elif cutboxvector == 'b': system.pbc[1] = False A_surf = np.linalg.norm(np.cross(system.box.avect, system.box.cvect)) elif cutboxvector == 'c': system.pbc[2] = False A_surf = np.linalg.norm(np.cross(system.box.avect, system.box.bvect)) else: raise ValueError('Invalid cutboxvector') # Evaluate system with free surface surface = relax_system(lammps_command, system, potential, mpi_command=mpi_command, etol=etol, ftol=ftol, maxiter=maxiter, maxeval=maxeval, dmax=dmax) # Extract results from system with free surface dumpfile_surf = 'surface.dump' shutil.move(surface['finaldumpfile'], dumpfile_surf) shutil.move('log.lammps', 'surface-log.lammps') E_total_surf = surface['potentialenergy'] # Compute the free surface formation energy E_surf_f = (E_total_surf - E_total_base) / (2 * A_surf) # Save values to results dictionary results_dict = {} results_dict['dumpfile_base'] = dumpfile_base results_dict['dumpfile_surf'] = dumpfile_surf results_dict['E_total_base'] = E_total_base results_dict['E_total_surf'] = E_total_surf results_dict['A_surf'] = A_surf results_dict['E_coh'] = E_total_base / system.natoms results_dict['E_surf_f'] = E_surf_f return results_dict
def dislocation_array(system, dislsol=None, m=None, n=None, burgers=None, bwidth=None, cutoff=None): """ Method that converts a bulk crystal system into a periodic array of dislocations. A single dislocation is inserted using a dislocation solution. The system's box and pbc are altered such that the system is periodic and compatible across the two box vectors contained in the slip plane. The third box vector is non-periodic, resulting in free surfaces parallel to the dislocation's slip plane. Parameters ---------- system : atomman.System A perfect, bulk atomic system. dislsol : atomman.defect.Stroh or atomman.defect.IsotropicVolterra, optional A dislocation solution to use to displace atoms by. If not given, all atoms will be given linear displacements associated with the long-range limits. m : array-like object, optional The dislocation solution m unit vector. This vector is in the slip plane and perpendicular to the dislocation line direction. Only needed if dislsol is not given. n : array-like object, optional The dislocation solution n unit vector. This vector is normal to the slip plane. Only needed if dislsol is not given. burgers : array-like object, optional The Cartesian Burger's vector for the dislocation relative to the given system's Cartesian coordinates. Only needed if dislsol is not given. bwidth : float, optional The width of the boundary region at the free surfaces. Atoms within the boundaries will be displaced by linear displacements instead of by the dislocation solution. Only given if dislsol is not None. Default value if dislsol is given is 10 Angstroms. cutoff : float, optional Cutoff distance to use for identifying duplicate atoms to remove. For dislocations with an edge component, applying the displacements creates an extra half-plane of atoms that will have (nearly) identical positions with other atoms after altering the boundary conditions. Default cutoff value is 0.5 Angstrom. Returns ------- atomman.System The resulting periodic array of dislocations system. An additional atoms property 'old_id' will be added to map the atoms in the defect system back to the associated atoms in the original system. """ # ------------------------ Parameter handling --------------------------- # # Input parameter setup for linear gradients only if dislsol is None: if m is None or n is None: raise ValueError('m and n are needed if no dislsol is given') m = np.asarray(m) n = np.asarray(n) burgers = np.asarray(burgers) try: assert np.isclose(np.linalg.norm(m), 1.0) assert np.isclose(np.linalg.norm(n), 1.0) assert np.isclose(m.dot(n), 0.0) except: raise ValueError('m and n must be perpendicular unit vectors') if bwidth is not None: raise ValueError('bwidth not allowed if dislsol is not given') # Input parameter setup for dislsol else: m = dislsol.m n = dislsol.n burgers = dislsol.burgers if bwidth is None: bwidth = uc.set_in_units(10, 'angstrom') if cutoff is None: cutoff = uc.set_in_units(0.5, 'angstrom') # Extract system values pos = system.atoms.pos vects = system.box.vects spos = system.atoms_prop(key='pos', scale=True) # -------------------- Orientation identification ----------------------- # # Find box vector alligned with u = m x n u = np.cross(m, n) line = np.isclose(np.abs(np.dot(u, vects)), np.linalg.norm(vects, axis=1)) if np.sum(line) != 1: raise ValueError('box vector aligned with u = m x n not found') lineindex = np.where(line)[0][0] # Find out-of-plane box vector out = ~np.isclose(np.dot(n, vects), 0.0) if np.sum(out) > 1: raise ValueError('multiple box vectors have out-of-plane components') pnormindex = np.where(out)[0][0] # Set third box vector as edge direction motionindex = 3 - (lineindex + pnormindex) # Check for atoms exactly on the slip plane if np.isclose(spos[:, pnormindex], 0.5, rtol=0).sum() > 0: raise ValueError( "atom positions found on slip plane: apply a coordinate shift") # ---------------------- Boundary modification -------------------------- # # Modify box vector in the motion direction by +- burgers/2 newvects = deepcopy(vects) if burgers.dot(m) > 0: newvects[motionindex] -= burgers / 2 else: newvects[motionindex] += burgers / 2 newbox = Box(vects=newvects, origin=system.box.origin) # Make boundary condition perpendicular to slip plane non-periodic newpbc = [True, True, True] newpbc[pnormindex] = False # Get length of system along motionindex # WHICH IS BEST!?!?!? length = np.abs(vects[motionindex].dot(m)) #length = np.linalg.norm(newvects[motionindex]) # -------------------- duplicate atom identification -------------------- # # Create test system to identify "duplicate" atoms testsystem = System(atoms=deepcopy(system.atoms), box=newbox, pbc=newpbc, symbols=system.symbols) # Apply linear gradient shift to all atoms testsystem.atoms.pos += linear_displacement(pos, burgers, length, m, n) testsystem.atoms.old_id = range(testsystem.natoms) # Identify atoms at the motionindex boundary to include in the duplicate check spos = testsystem.atoms_prop(key='pos', scale=True) sburgers = np.abs(2 * burgers[motionindex] / (length)) boundaryatoms = testsystem.atoms[(spos[:, motionindex] < sburgers) | (spos[:, motionindex] > 1.0 - sburgers)] # Compare distances between boundary atoms to identify duplicates dup_atom_ids = [] for ni, i in enumerate(boundaryatoms.old_id[:-1]): js = boundaryatoms.old_id[ni + 1:] try: distances = np.linalg.norm(testsystem.dvect(i, js), axis=1) mindistance = distances.min() except: mindistance = np.linalg.norm(testsystem.dvect(i, js)) if mindistance < cutoff: dup_atom_ids.append(i) ii = np.ones(system.natoms, dtype=bool) ii[dup_atom_ids] = False # Count found duplicate atoms found = system.natoms - ii.sum() # Count expected number of duplicates based on volume change expected = system.natoms - (system.natoms * newbox.volume / system.box.volume) if np.isclose(expected, round(expected)): expected = int(round(expected)) else: raise ValueError( 'expected number of atoms to delete not an integer: check burgers vector' ) # Compare found versus expected number of atoms if found != expected: raise ValueError( 'Deleted atom mismatch: expected %i, found %i. Adjust system dimensions and/or cutoff' % (expected, found)) # ---------------------- Build dislocation system ----------------------- # # Generate new system with duplicate atoms removed newsystem = System(atoms=system.atoms[ii], box=newbox, pbc=newpbc, symbols=system.symbols) # Define old_id so atoms in newsystem can be mapped back to system newsystem.atoms.old_id = np.where(ii)[0] if dislsol is None: # Use only linear displacements disp = linear_displacement(newsystem.atoms.pos, burgers, length, m, n) else: # Identify boundary atoms miny = system.box.origin.dot(n) maxy = miny + vects[pnormindex].dot(n) if maxy < miny: miny, maxy = maxy, miny y = newsystem.atoms.pos.dot(n) ii = np.where((y <= miny + bwidth) | (y >= maxy - bwidth)) # Use dislsol in middle and linear displacements at boundary disp = dislsol.displacement(newsystem.atoms.pos) disp[:, pnormindex] -= disp[:, pnormindex].mean() disp[ii] = linear_displacement(newsystem.atoms.pos[ii], burgers, length, m, n) # Displace atoms and wrap newsystem.atoms.pos += disp newsystem.wrap() return newsystem
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 calc_cij(lammps_exe, ucell, potential, symbols, p_xx=0.0, p_yy=0.0, p_zz=0.0): """Runs cij_script and returns current Cij, stress, Ecoh, and new ucell guess.""" #setup system and pair info system_info = lmp.sys_gen(units = potential.units, atom_style = potential.atom_style, ucell = ucell, size = np.array([[0,3], [0,3], [0,3]])) pair_info = potential.pair_info(symbols) #create script and run with open('cij.in','w') as f: f.write(cij_script(system_info, pair_info)) data = lmp.run(lammps_exe, 'cij.in') #get units for pressure and energy used by LAMMPS simulation lmp_units = lmp.style.unit(potential.units) p_unit = lmp_units['pressure'] e_unit = lmp_units['energy'] #Extract thermo values. Each term ranges i=0-12 where i=0 is undeformed #The remaining values are for -/+ strain pairs in the six unique directions lx = np.array(data.finds('Lx')) ly = np.array(data.finds('Ly')) lz = np.array(data.finds('Lz')) xy = np.array(data.finds('Xy')) xz = np.array(data.finds('Xz')) yz = np.array(data.finds('Yz')) pxx = uc.set_in_units(np.array(data.finds('Pxx')), p_unit) pyy = uc.set_in_units(np.array(data.finds('Pyy')), p_unit) pzz = uc.set_in_units(np.array(data.finds('Pzz')), p_unit) pxy = uc.set_in_units(np.array(data.finds('Pxy')), p_unit) pxz = uc.set_in_units(np.array(data.finds('Pxz')), p_unit) pyz = uc.set_in_units(np.array(data.finds('Pyz')), p_unit) pe = uc.set_in_units(np.array(data.finds('peatom')), e_unit) #Set the six non-zero strain values strains = np.array([ (lx[2] - lx[1]) / lx[0], (ly[4] - ly[3]) / ly[0], (lz[6] - lz[5]) / lz[0], (yz[8] - yz[7]) / lz[0], (xz[10] - xz[9]) / lz[0], (xy[12] - xy[11]) / ly[0] ]) #calculate cij using stress changes associated with each non-zero strain cij = np.empty((6,6)) for i in xrange(6): delta_stress = np.array([ pxx[2*i+1]-pxx[2*i+2], pyy[2*i+1]-pyy[2*i+2], pzz[2*i+1]-pzz[2*i+2], pyz[2*i+1]-pyz[2*i+2], pxz[2*i+1]-pxz[2*i+2], pxy[2*i+1]-pxy[2*i+2] ]) cij[i] = delta_stress / strains[i] for i in xrange(6): for j in xrange(i): cij[i,j] = cij[j,i] = (cij[i,j] + cij[j,i]) / 2 C = am.tools.ElasticConstants(Cij=cij) if np.allclose(C.Cij, 0.0): raise RuntimeError('Divergence of elastic constants to <= 0') try: S = C.Sij except: raise RuntimeError('singular C:\n'+str(C.Cij)) #extract the current stress state stress = -1 * np.array([[pxx[0], pxy[0], pxz[0]], [pxy[0], pyy[0], pyz[0]], [pxz[0], pyz[0], pzz[0]]]) s_xx = stress[0,0] + p_xx s_yy = stress[1,1] + p_yy s_zz = stress[2,2] + p_zz new_a = ucell.box.a / (S[0,0]*s_xx + S[0,1]*s_yy + S[0,2]*s_zz + 1) new_b = ucell.box.b / (S[1,0]*s_xx + S[1,1]*s_yy + S[1,2]*s_zz + 1) new_c = ucell.box.c / (S[2,0]*s_xx + S[2,1]*s_yy + S[2,2]*s_zz + 1) if new_a <= 0 or new_b <= 0 or new_c <=0: raise RuntimeError('Divergence of box dimensions to <= 0') newbox = am.Box(a=new_a, b=new_b, c=new_c) ucell_new = deepcopy(ucell) ucell_new.box_set(vects=newbox.vects, scale=True) return {'C':C, 'stress':stress, 'ecoh':pe[0], 'ucell_new':ucell_new}
def e_vs_r(lammps_command, system, potential, mpi_command=None, ucell=None, rmin=uc.set_in_units(2.0, 'angstrom'), rmax=uc.set_in_units(6.0, 'angstrom'), rsteps=200): """ Performs a cohesive energy scan over a range of interatomic spaces, r. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. ucell : atomman.System, optional The fundamental unit cell correspodning to system. This is used to convert system dimensions to cell dimensions. If not given, ucell will be taken as system. rmin : float, optional The minimum r spacing to use (default value is 2.0 angstroms). rmax : float, optional The maximum r spacing to use (default value is 6.0 angstroms). rsteps : int, optional The number of r spacing steps to evaluate (default value is 200). Returns ------- dict Dictionary of results consisting of keys: - **'r_values'** (*numpy.array of float*) - All interatomic spacings, r, explored. - **'a_values'** (*numpy.array of float*) - All unit cell a lattice constants corresponding to the values explored. - **'Ecoh_values'** (*numpy.array of float*) - The computed cohesive energies for each r value. - **'min_cell'** (*list of atomman.System*) - Systems corresponding to the minima identified in the Ecoh_values. """ # Make system a deepcopy of itself (protect original from changes) system = deepcopy(system) # Set ucell = system if ucell not given if ucell is None: ucell = system # Calculate the r/a ratio for the unit cell r_a = r_a_ratio(ucell) # Get ratios of lx, ly, and lz of system relative to a of ucell lx_a = system.box.a / ucell.box.a ly_a = system.box.b / ucell.box.a lz_a = system.box.c / ucell.box.a alpha = system.box.alpha beta = system.box.beta gamma = system.box.gamma # Build lists of values r_values = np.linspace(rmin, rmax, rsteps) a_values = r_values / r_a Ecoh_values = np.empty(rsteps) # Loop over values for i in range(rsteps): # Rescale system's box a = a_values[i] system.box_set(a = a * lx_a, b = a * ly_a, c = a * lz_a, alpha=alpha, beta=beta, gamma=gamma, scale=True) # Get lammps units lammps_units = lmp.style.unit(potential.units) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='atom.dat', 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) # Write lammps input script template_file = 'run0.template' lammps_script = 'run0.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 data output = lmp.run(lammps_command, lammps_script, mpi_command) thermo = output.simulations[0]['thermo'] if output.lammps_date < datetime.date(2016, 8, 1): Ecoh_values[i] = uc.set_in_units(thermo.peatom.values[-1], lammps_units['energy']) else: Ecoh_values[i] = uc.set_in_units(thermo.v_peatom.values[-1], lammps_units['energy']) # Rename log.lammps shutil.move('log.lammps', 'run0-'+str(i)+'-log.lammps') # Find unit cell systems at the energy minimums min_cells = [] for i in range(1, rsteps - 1): if (Ecoh_values[i] < Ecoh_values[i-1] and Ecoh_values[i] < Ecoh_values[i+1]): a = a_values[i] cell = deepcopy(ucell) cell.box_set(a = a, b = a * ucell.box.b / ucell.box.a, c = a * ucell.box.c / ucell.box.a, alpha=alpha, beta=beta, gamma=gamma, scale=True) min_cells.append(cell) # Collect results results_dict = {} results_dict['r_values'] = r_values results_dict['a_values'] = a_values results_dict['Ecoh_values'] = Ecoh_values results_dict['min_cell'] = min_cells return results_dict
def load(data, pbc=(True, True, True), atom_style='atomic', units='metal'): """ Read a LAMMPS-style atom data file and return a System. Argument: data = file name, file-like object or string to read data from. Keyword Arguments: pbc -- list or tuple of three boolean values indicating which System directions are periodic. Default is (True, True, True). atom_style -- LAMMPS atom_style option associated with the data file. Default is 'atomic'. units -- LAMMPS units option associated with the data file. Default is 'metal'. When the file is read in, the units of all property values are automatically converted to atomman's set working units. """ units_dict = style.unit(units) readtime = False count = 0 xy = 0.0 xz = 0.0 yz = 0.0 system = None with uber_open_rmode(data) as fp: #loop over all lines in fp for line in fp: terms = line.split() if len(terms)>0: #read atomic information if time to do so if readtime == True: a_id = int(terms[0]) - 1 prop_vals[a_id] = terms[1:] count += 1 #save values to system once all atoms read in if count == natoms: readtime = False count = 0 start = 0 #iterate over all atom_style properties for name, v in props.iteritems(): if name != 'a_id': size, dim, dtype = v value = np.asarray(prop_vals[:, start:start+size], dtype=dtype) start += size #set units according to LAMMPS units style unit = units_dict.get(dim, None) system.atoms_prop(key=name, value=uc.set_in_units(value, unit)) #read number of atoms elif len(terms) == 2 and terms[1] == 'atoms': natoms = int(terms[0]) #read number of atom types elif len(terms) == 3 and terms[1] == 'atom' and terms[2] == 'types': natypes = int(terms[0]) #read boundary info elif len(terms) == 4 and terms[2] == 'xlo' and terms[3] == 'xhi': xlo = uc.set_in_units(float(terms[0]), units_dict['length']) xhi = uc.set_in_units(float(terms[1]), units_dict['length']) elif len(terms) == 4 and terms[2] == 'ylo' and terms[3] == 'yhi': ylo = uc.set_in_units(float(terms[0]), units_dict['length']) yhi = uc.set_in_units(float(terms[1]), units_dict['length']) elif len(terms) == 4 and terms[2] == 'zlo' and terms[3] == 'zhi': zlo = uc.set_in_units(float(terms[0]), units_dict['length']) zhi = uc.set_in_units(float(terms[1]), units_dict['length']) elif len(terms) == 6 and terms[3] == 'xy' and terms[4] == 'xz' and terms[5] == 'yz': xy = uc.set_in_units(float(terms[0]), units_dict['length']) xz = uc.set_in_units(float(terms[1]), units_dict['length']) yz = uc.set_in_units(float(terms[2]), units_dict['length']) #Flag when reached data and setup for reading elif len(terms) == 1 and terms[0] in ('Atoms', 'Velocities'): #create system if not already if system is None: box = am.Box(xlo=xlo, xhi=xhi, ylo=ylo, yhi=yhi, zlo=zlo, zhi=zhi, xy=xy, xz=xz, yz=yz) system = am.System(box=box, atoms=am.Atoms(natoms=natoms), pbc = pbc) if terms[0] == 'Atoms': props = style.atom(atom_style) else: props = style.velocity(atom_style) nvals = 0 for name, v in props.iteritems(): nvals += v[0] prop_vals = np.empty((natoms, nvals-1), dtype=float) readtime = True assert system.natypes == natypes, 'Number of atom types does not match!' return system
def interstitial(system, pos, scale=False, atol=None, **kwargs): """ Generates a new system by adding an interstitial point defect. 1. Adds a new atom to the end of the Atoms list. 2. Adds per-atom property old_id if it doesn't exist corresponding to the atom ids in the original system. 3. Sets any of the new atom's per-atom properties to values given as kwargs. Any undefined properties are given zero values except atype, which is set to 1. Parameters ---------- system : atomman.System The base System to add the defect to. pos : array-like object Position of the atom being added. scale : bool, optional Indicates if pos is Cartesian (False) or box-relative (True). Default value is False. atol : float, optional Absolute tolerance for position-based searching. Default value is 0.01 angstroms. **kwargs : any, optional Keyword arguments corresponding to per-atom property values for the new atom. By default, atype==1 and all other properties are set to be all zeros for the property's shape. Returns ------- atomman.System A new system with the interstitial added. """ # Set default atol if atol is None: atol = uc.set_in_units(0.01, 'angstrom') if scale: pos = system.unscale(pos) # Check that no atoms are already at pos dist = np.linalg.norm(system.dvect(pos, system.atoms.pos), axis=1) ptd_id = np.where(np.isclose(dist, 0.0, atol=atol)) if not (len(ptd_id) == 1 and len(ptd_id[0]) == 0): raise ValueError('atom already at pos') # Generate atomic index list for defect index = list(range(system.natoms)) index.append(0) # Build new system with atom 0 copied d_system = System(box=deepcopy(system.box), pbc=deepcopy(system.pbc), atoms=deepcopy(system.atoms[index]), symbols=system.symbols) # Add property old_id with each atom's original id if 'old_id' not in d_system.atoms_prop(): d_system.atoms.old_id = index # Set values for the new atom for prop in d_system.atoms_prop(): if prop == 'atype': d_system.atoms.atype[-1] = kwargs.pop('atype', 1) elif prop == 'pos': d_system.atoms.pos[-1] = pos elif prop == 'old_id': d_system.atoms.old_id[-1] = kwargs.pop('old_id', d_system.atoms.old_id.max()+1) else: d_system.atoms.view[prop][-1] = kwargs.pop(prop, np.zeros_like(d_system.atoms.view[prop][-1])) return d_system
def substitutional(system, pos=None, ptd_id=None, atype=1,scale=False, atol=None, **kwargs): """ Generates a new system by adding a substitutional point defect. 1. Moves the indicated atom to the end of the list and changes its atype to the value given. 2. Adds per-atom property old_id if it doesn't exist corresponding to the atom ids in the original system. 3. Sets any of the moved atom's per-atom properties to values given as kwargs. Any undefined properties are left unchanged. Parameters ---------- system : atomman.System The base System to add the defect to. pos : array-like object, optional Position of the atom being modified. Either pos or ptd_id must be given. ptd_id : int, optional Id of the atom to be modified. Either pos or ptd_id must be given. atype : int, optional Integer atomic type to change the identified atom to. Must be different than the atom's current id. Default value is 1. scale : bool, optional Indicates if pos is Cartesian (False) or box-relative (True). Default value is False. atol : float, optional Absolute tolerance for position-based searching. Default value is 0.01 angstroms. **kwargs : any, optional Keyword arguments corresponding to per-atom property values for the modified atom. By default, all properties (except atype) are left unchanged. Returns ------- atomman.System A new system with the substitutional added. """ # Set default atol if atol is None: atol = uc.set_in_units(0.01, 'angstrom') # Identify the id of the atom at pos if pos is not None: if ptd_id is not None: raise ValueError('pos and ptd_id cannot both be supplied') if scale: pos = system.unscale(pos) dist = np.linalg.norm(system.dvect(pos, system.atoms.pos), axis=1) ptd_id = np.where(np.isclose(dist, 0.0, atol=atol)) if len(ptd_id) == 1 and len(ptd_id[0]) == 1: ptd_id = ptd_id[0][0] else: raise ValueError('Unique atom at pos not identified') elif ptd_id is not None: if ptd_id < 0: ptd_id += system.natoms if ptd_id < 0 or ptd_id >= system.natoms: raise ValueError('invalid ptd_id') else: raise ValueError('Either pos or ptd_id required') if system.atoms.atype[ptd_id] == atype: raise ValueError('identified atom is already of the specified atype') # Generate atomic index list for defect index = list(range(system.natoms)) index.pop(ptd_id) index.append(ptd_id) # Build new system d_system = System(box=deepcopy(system.box), pbc=deepcopy(system.pbc), atoms=deepcopy(system.atoms[index]), symbols=system.symbols) # Add property old_id with each atom's original id if 'old_id' not in d_system.atoms_prop(): d_system.atoms.old_id = index # Set values for the new atom for prop in d_system.atoms_prop(): if prop == 'atype': d_system.atoms.atype[-1] = atype elif prop == 'pos': pass else: d_system.atoms.view[prop][-1] = kwargs.pop(prop, d_system.atoms.view[prop][-1]) return d_system
def dumbbell(system, pos=None, ptd_id=None, db_vect=None, scale=False, atol=None, **kwargs): """ Generates a new system by adding a dumbbell interstitial point defect. 1. Copies the indicated atom and moves both the original and copy to the end of the Atoms list. 2. Displaces the dumbbell atoms position's by +-db_vect. 3. Adds per-atom property old_id if it doesn't exist corresponding to the atom ids in the original system. 4. Sets any of the new atom's per-atom properties to values given as kwargs. Any undefined properties are left unchanged. Parameters ---------- system : atomman.System The base System to add the defect to. pos : array-like object, optional Position of the atom being modified. Either pos or ptd_id must be given. ptd_id : int, optional Id of the atom to be modified. Either pos or ptd_id must be given. db_vect : array-like object Vector shift to apply to the atoms in the dumbbell. scale : bool, optional Indicates if pos and db_vect are Cartesian (False) or box-relative (True). Default value is False. atol : float, optional Absolute tolerance for position-based searching. Default value is 0.01 angstroms. \*\*kwargs : any, optional Keyword arguments corresponding to per-atom property values for the new atom in the dumbbell. By default, all properties are left unchanged (i.e. same as atom that was copied). Returns ------- atomman.System A new system with the dumbbell added. """ # Set default atol if atol is None: atol = uc.set_in_units(0.01, 'angstrom') # Identify the id of the atom at pos if pos is not None: if ptd_id is not None: raise ValueError('pos and ptd_id cannot both be supplied') if scale: pos = system.unscale(pos) dist = np.linalg.norm(system.dvect(pos, system.atoms.pos), axis=1) ptd_id = np.where(np.isclose(dist, 0.0, atol=atol)) if len(ptd_id) == 1 and len(ptd_id[0]) == 1: ptd_id = ptd_id[0][0] else: raise ValueError('Unique atom at pos not identified') elif ptd_id is not None: if ptd_id < 0: ptd_id += system.natoms if ptd_id < 0 or ptd_id >= system.natoms: raise ValueError('invalid ptd_id') else: raise ValueError('Either pos or ptd_id required') # Unscale db_vect if scale: db_vect = system.unscale(db_vect) # Generate atomic index list for defect index = list(range(system.natoms)) index.pop(ptd_id) index.append(ptd_id) index.append(ptd_id) # Build new system d_system = System(box=deepcopy(system.box), pbc=deepcopy(system.pbc), atoms=deepcopy(system.atoms[index]), symbols=system.symbols) # Add property old_id with each atom's original id if 'old_id' not in d_system.atoms_prop(): d_system.atoms.old_id = index # Set values for the new atom for prop in d_system.atoms_prop(): if prop == 'pos': d_system.atoms.pos[-2] -= db_vect d_system.atoms.pos[-1] += db_vect elif prop == 'old_id': d_system.atoms.old_id[-1] = kwargs.pop('old_id', d_system.atoms.old_id.max()+1) else: d_system.atoms.view[prop][-1] = kwargs.pop(prop, d_system.atoms.view[prop][-1]) return d_system
def load(data, pbc=(True, True, True), symbols=None, atom_style='atomic', units='metal'): """ Read a LAMMPS-style atom data file. Parameters ---------- data : str or file-like object The atom data content to read. Can be str content, path name, or open file-like object. pbc : list of bool Three boolean values indicating which System directions are periodic. Default value is (True, True, True). symbols : tuple, optional Allows the list of element symbols to be assigned during loading. atom_style :str The LAMMPS atom_style option associated with the data file. Default value is 'atomic'. units : str The LAMMPS units option associated with the data file. Default value is 'metal'. Returns ------- atomman.System The corresponding system. Note all property values will be automatically converted to atomman.unitconvert's set working units. """ # Get units information units_dict = style.unit(units) # Initialize parameter values atomsstart = None velocitiesstart = None xy = 0.0 xz = 0.0 yz = 0.0 # Read str and files in the same way with uber_open_rmode(data) as fp: # Loop over all lines in fp for i, line in enumerate(fp): try: line = line.decode('UTF-8') except: pass # Remove comments after '#' try: comment_index = line.index('#') except: pass else: line = line[:comment_index] terms = line.split() # Skip blank lines if len(terms)>0: # Read number of atoms if len(terms) == 2 and terms[1] == 'atoms': natoms = int(terms[0]) # Read number of atom types elif len(terms) == 3 and terms[1] == 'atom' and terms[2] == 'types': #natypes = int(terms[0]) pass # Read boundary info elif len(terms) == 4 and terms[2] == 'xlo' and terms[3] == 'xhi': xlo = uc.set_in_units(float(terms[0]), units_dict['length']) xhi = uc.set_in_units(float(terms[1]), units_dict['length']) elif len(terms) == 4 and terms[2] == 'ylo' and terms[3] == 'yhi': ylo = uc.set_in_units(float(terms[0]), units_dict['length']) yhi = uc.set_in_units(float(terms[1]), units_dict['length']) elif len(terms) == 4 and terms[2] == 'zlo' and terms[3] == 'zhi': zlo = uc.set_in_units(float(terms[0]), units_dict['length']) zhi = uc.set_in_units(float(terms[1]), units_dict['length']) elif len(terms) == 6 and terms[3] == 'xy' and terms[4] == 'xz' and terms[5] == 'yz': xy = uc.set_in_units(float(terms[0]), units_dict['length']) xz = uc.set_in_units(float(terms[1]), units_dict['length']) yz = uc.set_in_units(float(terms[2]), units_dict['length']) # Identify starting line number for Atoms data elif len(terms) == 1 and terms[0] == 'Atoms': atomsstart = i + 1 # Identify starting line number for Velocity data elif len(terms) == 1 and terms[0] == 'Velocities': velocitiesstart = i + 1 # Create system box = Box(xlo=xlo, xhi=xhi, ylo=ylo, yhi=yhi, zlo=zlo, zhi=zhi, xy=xy, xz=xz, yz=yz) atoms = Atoms(natoms=natoms) system = System(box=box, atoms=atoms, pbc=pbc) # Read in Atoms info if atomsstart is not None: prop_info = atoms_prop_info(atom_style, units) system = load_table(data, box=system.box, system=system, symbols=symbols, prop_info=prop_info, skiprows=atomsstart, nrows=natoms, comment='#') else: raise ValueError('No Atoms section found!') # Read in Velocities info if velocitiesstart is not None: prop_info = velocities_prop_info(atom_style, units) system = load_table(data, box=system.box, system=system, prop_info=prop_info, skiprows=velocitiesstart, nrows=natoms) return system
def vacancy(system, pos=None, ptd_id=None, scale=False, atol=None): """ Generates a new system by adding a vacancy point defect. 1. Removes the indicated atom from the system 2. Adds per-atom property old_id if it doesn't exist corresponding to the atom ids in the original system. Parameters ---------- system : atomman.System The base System to add the defect to. pos : array-like object, optional Position of the atom to be removed. Either pos or ptd_id must be given. ptd_id : int, optional Id of the atom to be removed. Either pos or ptd_id must be given. scale : bool, optional Indicates if pos is Cartesian (False) or box-relative (True). Default value is False. atol : float, optional Absolute tolerance for position-based searching. Default value is 0.01 angstroms. Returns ------- atomman.System A new system with the vacancy added. """ # Set default atol if atol is None: atol = uc.set_in_units(0.01, 'angstrom') # Identify the id of the atom at pos if pos is not None: if ptd_id is not None: raise ValueError('pos and ptd_id cannot both be supplied') if scale: pos = system.unscale(pos) dist = np.linalg.norm(system.dvect(pos, system.atoms.pos), axis=1) ptd_id = np.where(np.isclose(dist, 0.0, atol=atol)) if len(ptd_id) == 1 and len(ptd_id[0]) == 1: ptd_id = ptd_id[0][0] else: raise ValueError('Unique atom at pos not identified') elif ptd_id is not None: if ptd_id < 0: ptd_id += system.natoms if ptd_id < 0 or ptd_id >= system.natoms: raise ValueError('invalid ptd_id') else: raise ValueError('Either pos or ptd_id required') # Generate atomic index list for defect index = list(range(system.natoms)) try: index.pop(ptd_id) except: raise TypeError('ptd_id must be an integer type') # Build new system d_system = System(box=deepcopy(system.box), pbc=deepcopy(system.pbc), atoms=deepcopy(system.atoms[index]), symbols=system.symbols) # Add property old_id with each atom's original id if 'old_id' not in d_system.atoms_prop(): d_system.atoms.old_id = index return d_system
def elastic_constants_static(lammps_command, system, potential, mpi_command=None, strainrange=1e-6, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom')): """ Repeatedly runs the ELASTIC example distributed with LAMMPS until box dimensions converge within a tolerance. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. strainrange : float, optional The small strain value to apply when calculating the elastic constants (default is 1e-6). etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). Returns ------- dict Dictionary of results consisting of keys: - **'a_lat'** (*float*) - The relaxed a lattice constant. - **'b_lat'** (*float*) - The relaxed b lattice constant. - **'c_lat'** (*float*) - The relaxed c lattice constant. - **'alpha_lat'** (*float*) - The alpha lattice angle. - **'beta_lat'** (*float*) - The beta lattice angle. - **'gamma_lat'** (*float*) - The gamma lattice angle. - **'E_coh'** (*float*) - The cohesive energy of the relaxed system. - **'stress'** (*numpy.array*) - The measured stress state of the relaxed system. - **'C_elastic'** (*atomman.ElasticConstants*) - The relaxed system's elastic constants. - **'system_relaxed'** (*atomman.System*) - The relaxed system. """ # Convert hexagonal cells to orthorhombic to avoid LAMMPS tilt issues if am.tools.ishexagonal(system.box): system = system.rotate([[2,-1,-1,0], [0, 1, -1, 0], [0,0,0,1]]) # Get lammps units lammps_units = lmp.style.unit(potential.units) # Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='init.dat', 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['strainrange'] = strainrange lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = uc.get_in_units(dmax, lammps_units['length']) # Fill in template files template_file = 'cij.template' lammps_script = 'cij.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, '<', '>')) template_file2 = 'potential.template' lammps_script2 = 'potential.in' with open(template_file2) as f: template = f.read() with open(lammps_script2, 'w') as f: f.write(iprPy.tools.filltemplate(template, lammps_variables, '<', '>')) # Run LAMMPS output = lmp.run(lammps_command, lammps_script, mpi_command) # Pull out initial state thermo = output.simulations[0]['thermo'] pxx0 = uc.set_in_units(thermo.Pxx.values[-1], lammps_units['pressure']) pyy0 = uc.set_in_units(thermo.Pyy.values[-1], lammps_units['pressure']) pzz0 = uc.set_in_units(thermo.Pzz.values[-1], lammps_units['pressure']) pyz0 = uc.set_in_units(thermo.Pyz.values[-1], lammps_units['pressure']) pxz0 = uc.set_in_units(thermo.Pxz.values[-1], lammps_units['pressure']) pxy0 = uc.set_in_units(thermo.Pxy.values[-1], lammps_units['pressure']) # Negative strains cij_n = np.empty((6,6)) for i in range(6): j = 1 + i * 2 # Pull out strained state thermo = output.simulations[j]['thermo'] pxx = uc.set_in_units(thermo.Pxx.values[-1], lammps_units['pressure']) pyy = uc.set_in_units(thermo.Pyy.values[-1], lammps_units['pressure']) pzz = uc.set_in_units(thermo.Pzz.values[-1], lammps_units['pressure']) pyz = uc.set_in_units(thermo.Pyz.values[-1], lammps_units['pressure']) pxz = uc.set_in_units(thermo.Pxz.values[-1], lammps_units['pressure']) pxy = uc.set_in_units(thermo.Pxy.values[-1], lammps_units['pressure']) # Calculate cij_n using stress changes cij_n[i] = np.array([pxx - pxx0, pyy - pyy0, pzz - pzz0, pyz - pyz0, pxz - pxz0, pxy - pxy0])/ strainrange # Positive strains cij_p = np.empty((6,6)) for i in range(6): j = 2 + i * 2 # Pull out strained state thermo = output.simulations[j]['thermo'] pxx = uc.set_in_units(thermo.Pxx.values[-1], lammps_units['pressure']) pyy = uc.set_in_units(thermo.Pyy.values[-1], lammps_units['pressure']) pzz = uc.set_in_units(thermo.Pzz.values[-1], lammps_units['pressure']) pyz = uc.set_in_units(thermo.Pyz.values[-1], lammps_units['pressure']) pxz = uc.set_in_units(thermo.Pxz.values[-1], lammps_units['pressure']) pxy = uc.set_in_units(thermo.Pxy.values[-1], lammps_units['pressure']) # Calculate cij_p using stress changes cij_p[i] = -np.array([pxx - pxx0, pyy - pyy0, pzz - pzz0, pyz - pyz0, pxz - pxz0, pxy - pxy0])/ strainrange # Average symmetric values cij = (cij_n + cij_p) / 2 for i in range(6): for j in range(i): cij[i,j] = cij[j,i] = (cij[i,j] + cij[j,i]) / 2 # Define results_dict results_dict = {} results_dict['raw_cij_negative'] = cij_n results_dict['raw_cij_positive'] = cij_p results_dict['C'] = am.ElasticConstants(Cij=cij) return results_dict
def 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 e_vs_r_scan(lammps_command: str, system: am.System, potential: am.lammps.Potential, mpi_command: Optional[str] = None, ucell: Optional[am.System] = None, rmin: float = uc.set_in_units(2.0, 'angstrom'), rmax: float = uc.set_in_units(6.0, 'angstrom'), rsteps: int = 200) -> dict: """ Performs a cohesive energy scan over a range of interatomic spaces, r. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. ucell : atomman.System, optional The fundamental unit cell correspodning to system. This is used to convert system dimensions to cell dimensions. If not given, ucell will be taken as system. rmin : float, optional The minimum r spacing to use (default value is 2.0 angstroms). rmax : float, optional The maximum r spacing to use (default value is 6.0 angstroms). rsteps : int, optional The number of r spacing steps to evaluate (default value is 200). Returns ------- dict Dictionary of results consisting of keys: - **'r_values'** (*numpy.array of float*) - All interatomic spacings, r, explored. - **'a_values'** (*numpy.array of float*) - All unit cell a lattice constants corresponding to the values explored. - **'Ecoh_values'** (*numpy.array of float*) - The computed cohesive energies for each r value. - **'min_cell'** (*list of atomman.System*) - Systems corresponding to the minima identified in the Ecoh_values. """ # Make system a deepcopy of itself (protect original from changes) system = deepcopy(system) # Set ucell = system if ucell not given if ucell is None: ucell = system # Calculate the r/a ratio for the unit cell r_a = ucell.r0() / ucell.box.a # Get ratios of lx, ly, and lz of system relative to a of ucell lx_a = system.box.a / ucell.box.a ly_a = system.box.b / ucell.box.a lz_a = system.box.c / ucell.box.a alpha = system.box.alpha beta = system.box.beta gamma = system.box.gamma # Build lists of values r_values = np.linspace(rmin, rmax, rsteps) a_values = r_values / r_a Ecoh_values = np.empty(rsteps) # Loop over values for i in range(rsteps): # Rescale system's box a = a_values[i] system.box_set(a=a * lx_a, b=a * ly_a, c=a * lz_a, alpha=alpha, beta=beta, gamma=gamma, scale=True) # Get lammps units lammps_units = lmp.style.unit(potential.units) # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='atom.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info # Write lammps input script lammps_script = 'run0.in' template = read_calc_file('iprPy.calculation.E_vs_r_scan', 'run0.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run lammps and extract data try: output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) except: Ecoh_values[i] = np.nan else: thermo = output.simulations[0]['thermo'] if output.lammps_date < datetime.date(2016, 8, 1): Ecoh_values[i] = uc.set_in_units(thermo.peatom.values[-1], lammps_units['energy']) else: Ecoh_values[i] = uc.set_in_units(thermo.v_peatom.values[-1], lammps_units['energy']) # Rename log.lammps try: shutil.move('log.lammps', 'run0-' + str(i) + '-log.lammps') except: pass if len(Ecoh_values[np.isfinite(Ecoh_values)]) == 0: raise ValueError( 'All LAMMPS runs failed. Potential likely invalid or incompatible.' ) # Find unit cell systems at the energy minimums min_cells = [] for i in range(1, rsteps - 1): if (Ecoh_values[i] < Ecoh_values[i - 1] and Ecoh_values[i] < Ecoh_values[i + 1]): a = a_values[i] cell = deepcopy(ucell) cell.box_set(a=a, b=a * ucell.box.b / ucell.box.a, c=a * ucell.box.c / ucell.box.a, alpha=alpha, beta=beta, gamma=gamma, scale=True) min_cells.append(cell) # Collect results results_dict = {} results_dict['r_values'] = r_values results_dict['a_values'] = a_values results_dict['Ecoh_values'] = Ecoh_values results_dict['min_cell'] = min_cells return results_dict
def 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 stackingfault(lammps_command, system, potential, mpi_command=None, cutboxvector=None, 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')): """ Computes the generalized stacking fault value 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. 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). Returns ------- dict Dictionary of results consisting of keys: - **'E_gsf'** (*float*) - The stacking fault formation energy. - **'E_total_0'** (*float*) - The total potential energy of the system before applying the faultshift. - **'E_total_sf'** (*float*) - The total potential energy of the system after applying the faultshift. - **'delta_disp'** (*float*) - The change in the center of mass difference between before and after applying the faultshift. - **'disp_0'** (*float*) - The center of mass difference between atoms above and below the fault plane in the cutboxvector direction for the system before applying the faultshift. - **'disp_sf'** (*float*) - The center of mass difference between atoms above and below the fault plane in the cutboxvector direction for the system after applying the faultshift. - **'A_fault'** (*float*) - The area of the fault surface. - **'dumpfile_0'** (*str*) - The name of the LAMMMPS dump file associated with the relaxed system before applying the faultshift. - **'dumpfile_sf'** (*str*) - The name of the LAMMMPS dump file associated with the relaxed system after applying the faultshift. """ # Evaluate the system without shifting along the fault plane zeroshift = stackingfaultpoint(lammps_command, system, potential, mpi_command=mpi_command, cutboxvector=cutboxvector, faultpos=faultpos, etol=etol, ftol=ftol, maxiter=maxiter, maxeval=maxeval, dmax=dmax, faultshift=[0.0, 0.0, 0.0]) # Extract terms E_total_0 = zeroshift['E_total'] disp_0 = zeroshift['disp'] A_fault = zeroshift['A_fault'] shutil.move('log.lammps', 'zeroshift-log.lammps') shutil.move(zeroshift['dumpfile'], 'zeroshift.dump') # Evaluate the system after shifting along the fault plane shifted = stackingfaultpoint(lammps_command, system, potential, mpi_command=mpi_command, cutboxvector=cutboxvector, faultpos=faultpos, etol=etol, ftol=ftol, maxiter=maxiter, maxeval=maxeval, dmax=dmax, faultshift=faultshift) # Extract terms E_total_sf = shifted['E_total'] disp_sf = shifted['disp'] shutil.move('log.lammps', 'shifted-log.lammps') shutil.move(shifted['dumpfile'], 'shifted.dump') # Compute the stacking fault energy E_gsf = (E_total_sf - E_total_0) / A_fault # Compute the change in displacement normal to fault plane delta_disp = disp_sf - disp_0 # Return processed results results = {} results['E_gsf'] = E_gsf results['E_total_0'] = E_total_0 results['E_total_sf'] = E_total_sf results['delta_disp'] = delta_disp results['disp_0'] = disp_0 results['disp_sf'] = disp_sf results['A_fault'] = A_fault results['dumpfile_0'] = 'zeroshift.dump' results['dumpfile_sf'] = 'shifted.dump' return results
def disl_relax(lammps_command, system, potential, mpi_command=None, annealtemp=0.0, randomseed=None, etol=0.0, ftol=1e-6, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom')): """ Sets up and runs the disl_relax.in LAMMPS script for relaxing a dislocation monopole system. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. annealtemp : float, optional The temperature to perform a dynamic relaxation at. (Default is 0.0, which will skip the dynamic relaxation.) randomseed : int or None, optional Random number seed used by LAMMPS in creating velocities and with the Langevin thermostat. (Default is None which will select a random int between 1 and 900000000.) etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). Returns ------- dict Dictionary of results consisting of keys: - **'logfile'** (*str*) - The name of the LAMMPS log file. - **'dumpfile'** (*str*) - The name of the LAMMPS dump file for the relaxed system. - **'E_total'** (*float*) - The total potential energy for the relaxed system. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='system.dat', 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['anneal_info'] = anneal_info(annealtemp, randomseed, potential.units) lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = dmax lammps_variables['group_move'] = ' '.join(np.array(range(1, system.natypes // 2 + 1), dtype=str)) # Set dump_modify format based on dump_modify_version if lammps_date < datetime.date(2016, 8, 3): lammps_variables['dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script template_file = 'disl_relax.template' lammps_script = 'disl_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) thermo = output.simulations[-1]['thermo'] # Extract output values results = {} results['logfile'] = 'log.lammps' results['dumpfile'] = '%i.dump' % thermo.Step.values[-1] results['E_total'] = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) return results
def load(data, pbc=(True, True, True), symbols=None, atom_style='atomic', units='metal'): """ Read a LAMMPS-style atom data file. Parameters ---------- data : str or file-like object The atom data content to read. Can be str content, path name, or open file-like object. pbc : list of bool Three boolean values indicating which System directions are periodic. Default value is (True, True, True). symbols : tuple, optional Allows the list of element symbols to be assigned during loading. atom_style :str The LAMMPS atom_style option associated with the data file. Default value is 'atomic'. units : str The LAMMPS units option associated with the data file. Default value is 'metal'. Returns ------- atomman.System The corresponding system. Note all property values will be automatically converted to atomman.unitconvert's set working units. """ # Get units information units_dict = style.unit(units) # Initialize parameter values atomsstart = None velocitiesstart = None xy = 0.0 xz = 0.0 yz = 0.0 # Read str and files in the same way with uber_open_rmode(data) as fp: # Loop over all lines in fp for i, line in enumerate(fp): try: line = line.decode('UTF-8') except: pass # Remove comments after '#' try: comment_index = line.index('#') except: pass else: line = line[:comment_index] terms = line.split() # Skip blank lines if len(terms) > 0: # Read number of atoms if len(terms) == 2 and terms[1] == 'atoms': natoms = int(terms[0]) # Read number of atom types elif len(terms ) == 3 and terms[1] == 'atom' and terms[2] == 'types': #natypes = int(terms[0]) pass # Read boundary info elif len(terms ) == 4 and terms[2] == 'xlo' and terms[3] == 'xhi': xlo = uc.set_in_units(float(terms[0]), units_dict['length']) xhi = uc.set_in_units(float(terms[1]), units_dict['length']) elif len(terms ) == 4 and terms[2] == 'ylo' and terms[3] == 'yhi': ylo = uc.set_in_units(float(terms[0]), units_dict['length']) yhi = uc.set_in_units(float(terms[1]), units_dict['length']) elif len(terms ) == 4 and terms[2] == 'zlo' and terms[3] == 'zhi': zlo = uc.set_in_units(float(terms[0]), units_dict['length']) zhi = uc.set_in_units(float(terms[1]), units_dict['length']) elif len(terms) == 6 and terms[3] == 'xy' and terms[ 4] == 'xz' and terms[5] == 'yz': xy = uc.set_in_units(float(terms[0]), units_dict['length']) xz = uc.set_in_units(float(terms[1]), units_dict['length']) yz = uc.set_in_units(float(terms[2]), units_dict['length']) # Identify starting line number for Atoms data elif len(terms) == 1 and terms[0] == 'Atoms': atomsstart = i + 1 # Identify starting line number for Velocity data elif len(terms) == 1 and terms[0] == 'Velocities': velocitiesstart = i + 1 # Create system box = Box(xlo=xlo, xhi=xhi, ylo=ylo, yhi=yhi, zlo=zlo, zhi=zhi, xy=xy, xz=xz, yz=yz) atoms = Atoms(natoms=natoms) system = System(box=box, atoms=atoms, pbc=pbc) # Read in Atoms info if atomsstart is not None: prop_info = atoms_prop_info(atom_style, units) system = load_table(data, box=system.box, system=system, symbols=symbols, prop_info=prop_info, skiprows=atomsstart, nrows=natoms, comment='#') else: raise ValueError('No Atoms section found!') # Read in Velocities info if velocitiesstart is not None: prop_info = velocities_prop_info(atom_style, units) system = load_table(data, box=system.box, system=system, prop_info=prop_info, skiprows=velocitiesstart, nrows=natoms) return system
def load(data, prop_info=None): """ Read a LAMMPS-style dump file and return a System. Argument: data = file name, file-like object or string to read data from. Keyword Argument: prop_info -- DataModelDict for relating the per-atom properties to/from the dump file and the System. Will create a default json instance <data>.json if prop_info is not given and <data>.json doesn't already exist. """ #read in prop_info if supplied if prop_info is not None: if isinstance(prop_info, (str, unicode)) and os.path.isfile(prop_info): with open(prop_info) as f: prop_info = f.read() prop_info = DataModelDict(prop_info) #check for default prop_info file else: try: with open(data+'.json') as fj: prop_info = DataModelDict(fj) except: prop_info = None box_unit = None #read box_unit if specified in prop_info if prop_info is not None: prop_info = prop_info.find('LAMMPS-dump-atoms_prop-relate') box_unit = prop_info['box_prop'].get('unit', None) with uber_open_rmode(data) as f: pbc = None box = None natoms = None system = None readnatoms = False readatoms = False readtimestep = False acount = 0 bcount = 3 #loop over all lines in file for line in f: terms = line.split() if len(terms) > 0: #read atomic values if time to do so if readatoms: #sort values by a_id and save to prop_vals a_id = long(terms[id_index]) - 1 prop_vals[a_id] = terms acount += 1 #save values to sys once all atoms read in if acount == natoms: readatoms = False #cycle over the defined atoms_prop in prop_info for prop, p_keys in prop_info['atoms_prop'].iteritems(): #set default keys dtype = p_keys.get('dtype', None) shape = p_keys.get('shape', None) shape = (natoms,) + np.empty(shape).shape value = np.empty(shape) #cycle over the defined LAMMPS-attributes in prop_info for attr, a_keys in prop_info['LAMMPS-attribute'].iteritems(): #cycle over list of relations for each LAMMPS-attribute for relation in a_keys.iteraslist('relation'): #if atoms prop and relation prop match if relation['prop'] == prop: #get unit and scale info unit = relation.get('unit', None) if unit == 'scaled': unit = None scale = True else: scale = False #find index of attribute in name_list a_index = name_list.index(attr) #check if relation has index listed try: index = relation['index'] if isinstance(index, list): index = (Ellipsis,) + tuple(index) else: index = (Ellipsis,) + (index,) value[index] = prop_vals[:, a_index] #scalar if no index except: value[:] = prop_vals[:, a_index] #test if values are ints if dtype not specified if dtype is None and np.allclose(np.asarray(value, dtype=int), value): value = np.asarray(value, dtype=int) else: value = np.asarray(value, dtype=dtype) #save prop values to system system.atoms_prop(key=prop, value=uc.set_in_units(value, unit), scale=scale) #read number of atoms if time to do so elif readnatoms: natoms = int(terms[0]) readnatoms = False elif readtimestep: timestep = int(terms[0]) readtimestep = False #read x boundary condition values if time to do so elif bcount == 0: xlo = uc.set_in_units(float(terms[0]), box_unit) xhi = uc.set_in_units(float(terms[1]), box_unit) if len(terms) == 3: xy = uc.set_in_units(float(terms[2]), box_unit) bcount += 1 #read y boundary condition values if time to do so elif bcount == 1: ylo = uc.set_in_units(float(terms[0]), box_unit) yhi = uc.set_in_units(float(terms[1]), box_unit) if len(terms) == 3: xz = uc.set_in_units(float(terms[2]), box_unit) bcount += 1 #read z boundary condition values if time to do so elif bcount == 2: zlo = uc.set_in_units(float(terms[0]), box_unit) zhi = uc.set_in_units(float(terms[1]), box_unit) if len(terms) == 3: yz = uc.set_in_units(float(terms[2]), box_unit) xlo = xlo - min((0.0, xy, xz, xy + xz)) xhi = xhi - max((0.0, xy, xz, xy + xz)) ylo = ylo - min((0.0, yz)) yhi = yhi - max((0.0, yz)) box = am.Box(xlo=xlo, xhi=xhi, ylo=ylo, yhi=yhi, zlo=zlo, zhi=zhi, xy=xy, xz=xz, yz=yz) else: box = am.Box(xlo=xlo, xhi=xhi, ylo=ylo, yhi=yhi, zlo=zlo, zhi=zhi) bcount += 1 #if not time to read value, check the ITEM: header information else: #only consider ITEM: lines if terms[0] == 'ITEM:': #ITEM: TIMESTEP indicates it is time to read the timestep if terms[1] == 'TIMESTEP': readtimestep = True #ITEM: NUMBER indicates it is time to read natoms elif terms[1] == 'NUMBER': readnatoms = True #ITEM: BOX gives pbc and indicates it is time to read box parameters elif terms[1] == 'BOX': pbc = [True, True, True] for i in xrange(3): if terms[i + len(terms) - 3] != 'pp': pbc[i] = False bcount = 0 #ITEM: ATOMS gives list of per-Atom property names and indicates it is time to read atomic values elif terms[1] == 'ATOMS': assert box is not None, 'Box information not found' assert natoms is not None, 'Number of atoms not found' #read list of property names name_list = terms[2:] id_index = name_list.index('id') #create empty array for reading property values prop_vals = np.empty((natoms, len(name_list))) #create and save default prop_info Data Model if needed if prop_info is None: prop_info = __prop_info_default_load(name_list) if isinstance(data, (str, unicode)) and len(data) < 80: with open(data+'.json', 'w') as fj: prop_info.json(fp=fj, indent=4) prop_info = prop_info.find('LAMMPS-dump-atoms_prop-relate') #create system and flag that it is time to read data system = am.System(atoms=am.Atoms(natoms=natoms), box=box, pbc=pbc) system.prop['timestep'] = timestep readatoms = True if system is None: raise ValueError('Failed to properly load dump file '+str(data)[:50]) return system
def __set(self, x, disregistry, gamma, axes, K_tensor, **kwargs): """ Sets default values for class parameters. Parameters ---------- x : numpy.ndarray An array of shape (N) giving the x coordinates corresponding to the disregistry solution. disregistry : numpy.ndarray A (N,3) array giving the initial disregistry vector guess at each x coordinate. gamma : atomman.defect.GammaSurface The gamma surface (stacking fault map) to use for computing the misfit energy. axes : numpy.ndarray A (3,3) array defining the crystal directions of the dislocation system. Used to orient the disregistry vector to the gamma surface in computing the misfit energy. K_tensor : numpy.ndarray A (3,3) array giving the anisotropic elastic energy coefficients corresponding to the dislocation system's orientation. Can be computed with atomman.defect.Stroh. tau : numpy.ndarray, optional A (3,3) array giving the stress tensor to apply to the system using the stress energy term. Only the xy, yy, and yz components are used. Default value is all zeros. alpha : list of float, optional The alpha coefficient(s) used by the nonlocal energy term. Default value is [0.0]. beta : numpy.ndarray, optional The (3,3) array of beta coefficient(s) used by the surface energy term. Default value is all zeros. cutofflongrange : float, optional The cutoff distance to use for computing the long-range energy. Default value is 1000 angstroms. burgers : numpy.ndarray, optional The (3,) array of the dislocation's Burgers vector relative to the dislocation system. Used only by the long-range energy. Default value is all zeros (long-range energy will be excluded). fullstress : bool, optional Flag indicating which stress energy algorithm to use. Default value is True. cdiffelastic : bool, optional Flag indicating if the dislocation density for the elastic energy component is computed with central difference (True) or simply neighboring values (False). Default value is False. cdiffsurface : bool, optional Flag indicating if the dislocation density for the surface energy component is computed with central difference (True) or simply neighboring values (False). Default value is True. cdiffstress : bool, optional Flag indicating if the dislocation density for the stress energy component is computed with central difference (True) or simply neighboring values (False). Only matters if fullstress is True. Default value is False. min_method : str, optional The scipy.optimize.minimize method to use. Default value is 'Powell'. min_options : dict, optional Any options to pass on to scipy.optimize.minimize. Default value is {}. """ # Set mandatory parameters self.__x = x self.__disregistry = disregistry self.__gamma = gamma self.__axes = axes self.__K_tensor = K_tensor # Set optional keyword parameters self.__tau = kwargs.get('tau', np.zeros((3,3))) self.__alpha = kwargs.get('alpha', [0.0]) if not isinstance(self.__alpha, list): self.__alpha = [self.__alpha] self.__beta = kwargs.get('beta', np.zeros((3,3))) self.__cutofflongrange = kwargs.get('cutofflongrange', uc.set_in_units(1000, 'angstrom')) self.__burgers = kwargs.get('burgers', np.zeros(3)) self.__fullstress = kwargs.get('fullstress', True) self.__cdiffelastic = kwargs.get('cdiffelastic', False) self.__cdiffsurface = kwargs.get('cdiffsurface', True) self.__cdiffstress = kwargs.get('cdiffstress', False) self.__min_method = kwargs.get('min_method', 'Powell') self.__min_options = kwargs.get('min_options', {})
def make_screw_plate_old(self, size=[40, 60, 3], rad=[100, 115], move=[0., 0., 0.], filename="lmp_init.txt", opt=None): alat = uc.set_in_units(self.pot['lattice'], 'angstrom') C11 = uc.set_in_units(self.pot['c11'], 'GPa') C12 = uc.set_in_units(self.pot['c12'], 'GPa') C44 = uc.set_in_units(self.pot['c44'], 'GPa') axes = np.array([[1, -2, 1], [1, 0, -1], [1, 1, 1]]) unitx = alat * np.sqrt(6) unity = alat * np.sqrt(2) sizex = size[0] sizey = size[1] sizez = size[2] c = am.ElasticConstants(C11=C11, C12=C12, C44=C44) burgers = 0.5 * alat * -np.array([1., 1., 1.]) # initializing a new Stroh object using the data stroh = am.defect.Stroh(c, burgers, axes=axes) # monopole system box = am.Box(a=alat, b=alat, c=alat) atoms = am.Atoms(natoms=2, prop={ 'atype': 2, 'pos': [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]] }) ucell = am.System(atoms=atoms, box=box, scale=True) system = am.rotate_cubic(ucell, axes) # shftx = 0.5 * alat * np.sqrt(6.) / 3. shftx = 0.0 shfty = -1. / 3. * alat * np.sqrt(2) / 2. # shfty = 2. / 3. * alat * np.sqrt(2) / 2. center = [0.5 * unitx * sizex, 0.5 * unity * sizey] new_pos = system.atoms_prop(key='pos') + np.array([shftx, shfty, 0.0]) system.atoms_prop(key='pos', value=new_pos) system.supersize((0, sizex), (0, sizey), (0, sizez)) # to make a plate # radius2 = rad[0] * rad[0] radiusout2 = rad[1] * rad[1] elements = [] for atom in system.atoms: elements.append('Nb') ase_atoms = am.convert.ase_Atoms.dump(system, elements) pos = ase_atoms.get_positions() delindex = [] for i in range(len(pos)): atom = ase_atoms[i] dx = pos[i, 0] - center[0] dy = pos[i, 1] - center[1] r = dx * dx + dy * dy if r > radiusout2: delindex.append(atom.index) if r < radius2: atom.symbol = 'W' del ase_atoms[delindex] (system, elements) = am.convert.ase_Atoms.load(ase_atoms) # use neb, it's to generate init configuration if opt in ['neb']: system_init = copy.deepcopy(system) shift = np.array([-0.5, -0.5, 0.0]) new_pos = system_init.atoms_prop(key='pos', scale=True) + shift system_init.atoms_prop(key='pos', value=new_pos, scale=True) disp = stroh.displacement(system_init.atoms_prop(key='pos')) system_init.atoms_prop(key='pos', value=system_init.atoms_prop(key='pos') + disp) shift = np.array([0.5, 0.50, 0.0]) new_pos = system_init.atoms_prop(key='pos', scale=True) + shift system_init.atoms_prop(key='pos', value=new_pos, scale=True) # for lammps read structure lmp.atom_data.dump(system_init, "init.txt") # for dd map plot ase.io.write("lmp_perf.cfg", images=ase_atoms, format='cfg') lmp.atom_data.dump(system, "lmp_perf.txt") shift = np.array([-0.5, -0.5, 0.0]) new_pos = system.atoms_prop(key='pos', scale=True) + shift system.atoms_prop(key='pos', value=new_pos, scale=True) new_pos = system.atoms_prop(key='pos') + move system.atoms_prop(key='pos', value=new_pos) disp = stroh.displacement(system.atoms_prop(key='pos')) # pull pull = False if pull is True: core_rows = [disp[:, 2].argsort()[-3:][::-1]] print(disp[core_rows]) exclus = np.arange(len(disp), dtype=int) unitburger = np.mean(disp[core_rows][:, 2]) print(unitburger) exclus = np.delete(exclus, core_rows) disp[core_rows] -= 1. / 3. * unitburger # disp[exclus] -= 1. / 3. * unitburger system.atoms_prop(key='pos', value=system.atoms_prop(key='pos') + disp) new_pos = system.atoms_prop(key='pos') - move system.atoms_prop(key='pos', value=new_pos) shift = np.array([0.500000, 0.500000, 0.000000]) new_pos = system.atoms_prop(key='pos', scale=True) + shift system.atoms_prop(key='pos', value=new_pos, scale=True) new_pos = system.atoms_prop(key='pos', scale=False) # for lammps read structure lmp.atom_data.dump(system, filename) dis_atoms = am.convert.ase_Atoms.dump(system, elements) return (ase_atoms, dis_atoms)
def relax_system(lammps_command, system, potential, mpi_command=None, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom')): """ Sets up and runs the min.in LAMMPS script for performing an energy/force minimization to relax a system. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). Returns ------- dict Dictionary of results consisting of keys: - **'logfile'** (*str*) - The name of the LAMMPS log file. - **'initialdatafile'** (*str*) - The name of the LAMMPS data file used to import an inital configuration. - **'initialdumpfile'** (*str*) - The name of the LAMMPS dump file corresponding to the inital configuration. - **'finaldumpfile'** (*str*) - The name of the LAMMPS dump file corresponding to the relaxed configuration. - **'potentialenergy'** (*float*) - The total potential energy of the relaxed system. """ # Ensure all atoms are within the system's box system.wrap() # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Define lammps variables lammps_variables = {} # Generate system and pair info system_info = system.dump('atom_data', f='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(system.symbols) # Pass in run parameters lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = uc.get_in_units(dmax, lammps_units['length']) # Set dump_modify format based on dump_modify_version if lammps_date < datetime.date(2016, 8, 3): lammps_variables['dump_modify_format'] = '"%i %i %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script template_file = 'min.template' lammps_script = 'min.in' 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 output values thermo = output.simulations[-1]['thermo'] results = {} results['logfile'] = 'log.lammps' results['initialdatafile'] = 'system.dat' results['initialdumpfile'] = 'atom.0' results['finaldumpfile'] = 'atom.%i' % thermo.Step.values[-1] results['potentialenergy'] = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) return results
def disl_relax(lammps_command, system, potential, mpi_command=None, annealtemp=0.0, randomseed=None, etol=0.0, ftol=1e-6, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom')): """ Sets up and runs the disl_relax.in LAMMPS script for relaxing a dislocation monopole system. Parameters ---------- lammps_command :str Command for running LAMMPS. system : atomman.System The system to perform the calculation on. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. annealtemp : float, optional The temperature to perform a dynamic relaxation at. (Default is 0.0, which will skip the dynamic relaxation.) randomseed : int or None, optional Random number seed used by LAMMPS in creating velocities and with the Langevin thermostat. (Default is None which will select a random int between 1 and 900000000.) etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). Returns ------- dict Dictionary of results consisting of keys: - **'logfile'** (*str*) - The name of the LAMMPS log file. - **'dumpfile'** (*str*) - The name of the LAMMPS dump file for the relaxed system. - **'E_total'** (*float*) - The total potential energy for the relaxed system. """ # Get lammps units lammps_units = lmp.style.unit(potential.units) #Get lammps version date lammps_date = lmp.checkversion(lammps_command)['date'] # Define lammps variables lammps_variables = {} system_info = system.dump('atom_data', f='system.dat', 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['anneal_info'] = anneal_info(annealtemp, randomseed, potential.units) lammps_variables['etol'] = etol lammps_variables['ftol'] = uc.get_in_units(ftol, lammps_units['force']) lammps_variables['maxiter'] = maxiter lammps_variables['maxeval'] = maxeval lammps_variables['dmax'] = dmax lammps_variables['group_move'] = ' '.join( np.array(range(1, system.natypes // 2 + 1), dtype=str)) # Set dump_modify format based on dump_modify_version if lammps_date < datetime.date(2016, 8, 3): lammps_variables[ 'dump_modify_format'] = '"%d %d %.13e %.13e %.13e %.13e"' else: lammps_variables['dump_modify_format'] = 'float %.13e' # Write lammps input script template_file = 'disl_relax.template' lammps_script = 'disl_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) thermo = output.simulations[-1]['thermo'] # Extract output values results = {} results['logfile'] = 'log.lammps' results['dumpfile'] = '%i.dump' % thermo.Step.values[-1] results['E_total'] = uc.set_in_units(thermo.PotEng.values[-1], lammps_units['energy']) return results
def diatom_scan(lammps_command: str, potential: am.lammps.Potential, symbols: list, mpi_command: Optional[str] = None, rmin: float = uc.set_in_units(0.02, 'angstrom'), rmax: float = uc.set_in_units(6.0, 'angstrom'), rsteps: int = 300) -> dict: """ Performs a diatom energy scan over a range of interatomic spaces, r. Parameters ---------- lammps_command :str Command for running LAMMPS. potential : atomman.lammps.Potential The LAMMPS implemented potential to use. symbols : list The potential symbols associated with the two atoms in the diatom. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. rmin : float, optional The minimum r spacing to use (default value is 0.02 angstroms). rmax : float, optional The maximum r spacing to use (default value is 6.0 angstroms). rsteps : int, optional The number of r spacing steps to evaluate (default value is 300). Returns ------- dict Dictionary of results consisting of keys: - **'r_values'** (*numpy.array of float*) - All interatomic spacings, r, explored. - **'energy_values'** (*numpy.array of float*) - The computed potential energies for each r value. """ # Build lists of values r_values = np.linspace(rmin, rmax, rsteps) energy_values = np.empty(rsteps) # Define atype based on symbols symbols = aslist(symbols) if len(symbols) == 1: atype = [1, 1] elif len(symbols) == 2: atype = [1, 2] else: raise ValueError('symbols must have one or two values') # Initialize system (will shift second atom's position later...) box = am.Box.cubic(a=rmax + 1) atoms = am.Atoms(atype=atype, pos=[[0.1, 0.1, 0.1], [0.1, 0.1, 0.1]]) system = am.System(atoms=atoms, box=box, pbc=[False, False, False], symbols=symbols) # Add charges if required if potential.atom_style == 'charge': system.atoms.prop_atype('charge', potential.charges(system.symbols)) # Get lammps units lammps_units = lmp.style.unit(potential.units) # Define lammps variables lammps_variables = {} # Loop over values for i in range(rsteps): # Shift second atom's x position system.atoms.pos[1] = np.array([0.1 + r_values[i], 0.1, 0.1]) # Save configuration system_info = system.dump('atom_data', f='diatom.dat', potential=potential) lammps_variables['atomman_system_pair_info'] = system_info # Write lammps input script lammps_script = 'run0.in' template = read_calc_file('iprPy.calculation.diatom_scan', 'run0.template') with open(lammps_script, 'w') as f: f.write(filltemplate(template, lammps_variables, '<', '>')) # Run lammps and extract data try: output = lmp.run(lammps_command, script_name=lammps_script, mpi_command=mpi_command) except: energy_values[i] = np.nan else: energy = output.simulations[0]['thermo'].PotEng.values[-1] energy_values[i] = uc.set_in_units(energy, lammps_units['energy']) if len(energy_values[np.isfinite(energy_values)]) == 0: raise ValueError( 'All LAMMPS runs failed. Potential likely invalid or incompatible.' ) # Collect results results_dict = {} results_dict['r_values'] = r_values results_dict['energy_values'] = energy_values return results_dict
def load(data, symbols=None, lammps_units='metal', prop_name=None, table_name=None, shape=None, unit=None, dtype=None, prop_info=None, return_prop_info=False): """ Reads in a LAMMPS atomic dump file into a System. Parameters ---------- data : str or file-like object The content, file path or file-like object containing the content to read. symbols : tuple, optional Allows the list of element symbols to be assigned during loading. lammps_units : str The LAMMPS units option associated with the parameters. Default value is 'metal'. prop_name : list, optional The Atoms properties to generate. table_name : list, optional The table column name(s) that correspond to each prop_name. If prop_name, table_name and prop_info are not given, prop_name and table_name will be read in from data. shape : list, optional The shape of each per-atom property. If not given, will be taken from standard LAMMPS parameter names, or left at () for direct property-table conversion. unit : list, optional Lists the units for each prop_name as stored in the table. For a value of None, no conversion will be performed for that property. For a value of 'scaled', the corresponding table values will be taken in box-scaled units. If not given, all unit values will be set to None (i.e. no conversions). dtype : list, optional Allows for the data type of each property to be explicitly given. Values of None will infer the data type from the corresponding property values. If not given, all values will be None. prop_info : list of dict, optional Structured form of property conversion parameters, in which each dictionary in the list corresponds to a single atoms property. Each dictionary must have a 'prop_name' field, and can optionally have 'table_name', 'shape', 'unit', and 'dtype' fields. return_prop_info : bool, optional Flag indicating if the full prop_info is to be returned. Default value is False. Returns ------- system : atomman.System The generated system. prop_info : list of dict The full prop_info detailing the property-table conversion. Returned if return_prop_info is True. """ lammps_unit = style.unit(lammps_units) # Initialize parameter values pbc = None box = None natoms = None atomsstart = None xy = 0.0 xz = 0.0 yz = 0.0 readnatoms = False readtimestep = False bcount = 3 # Read str and files in the same way with uber_open_rmode(data) as fp: # Loop over all lines in fp for i, line in enumerate(fp): terms = line.decode('UTF-8').split() # Skip blank lines if len(terms) > 0: # Read number of atoms if time to do so if readnatoms: natoms = int(terms[0]) readnatoms = False # Read timestep if time to do so elif readtimestep: #timestep = int(terms[0]) readtimestep = False # Read x boundary condition values if time to do so elif bcount == 0: xlo = uc.set_in_units(float(terms[0]), lammps_unit['length']) xhi = uc.set_in_units(float(terms[1]), lammps_unit['length']) if len(terms) == 3: xy = uc.set_in_units(float(terms[2]), lammps_unit['length']) bcount += 1 # Read y boundary condition values if time to do so elif bcount == 1: ylo = uc.set_in_units(float(terms[0]), lammps_unit['length']) yhi = uc.set_in_units(float(terms[1]), lammps_unit['length']) if len(terms) == 3: xz = uc.set_in_units(float(terms[2]), lammps_unit['length']) bcount += 1 # Read z boundary condition values if time to do so elif bcount == 2: zlo = uc.set_in_units(float(terms[0]), lammps_unit['length']) zhi = uc.set_in_units(float(terms[1]), lammps_unit['length']) if len(terms) == 3: yz = uc.set_in_units(float(terms[2]), lammps_unit['length']) # Convert from max, min to hi, lo xlo = xlo - min((0.0, xy, xz, xy + xz)) xhi = xhi - max((0.0, xy, xz, xy + xz)) ylo = ylo - min((0.0, yz)) yhi = yhi - max((0.0, yz)) bcount += 1 # Otherwise, only check lines starting with ITEM elif terms[0] == 'ITEM:': # ITEM: TIMESTEP indicates it is time to read the timestep if terms[1] == 'TIMESTEP': readtimestep = True # ITEM: NUMBER indicates it is time to read natoms elif terms[1] == 'NUMBER': readnatoms = True # ITEM: BOX gives pbc and indicates it is time to read box parameters elif terms[1] == 'BOX': pbc = [True, True, True] for i in range(3): if terms[i + len(terms) - 3] != 'pp': pbc[i] = False bcount = 0 # ITEM: ATOMS gives list of property names and indicates it is time to read atomic values elif terms[1] == 'ATOMS': # Read list of property names name_list = terms[2:] # Create default prop_name and table_name if needed if prop_info is None and prop_name is None: assert table_name is None, 'table_name cannot be given without prop_name' prop_name, table_name = matchprops(name_list) atomsstart = i + 1 # Create system box = Box(xlo=xlo, xhi=xhi, ylo=ylo, yhi=yhi, zlo=zlo, zhi=zhi, xy=xy, xz=xz, yz=yz) atoms = Atoms(natoms=natoms) system = System(box=box, atoms=atoms, pbc=pbc) # Generate prop_info prop_info = process_prop_info(prop_name=prop_name, table_name=table_name, shape=shape, unit=unit, dtype=dtype, prop_info=prop_info, lammps_units=lammps_units) # Remove duplicate pos fields firstpos = True short_prop_info = [] for pinfo in prop_info: if pinfo['prop_name'] in ['pos', 'spos', 'upos', 'supos']: if firstpos: pinfo['prop_name'] = 'pos' else: continue short_prop_info.append(pinfo) # Read atoms into system system = load_table(data, box=system.box, symbols=symbols, system=system, prop_info=short_prop_info, skiprows=atomsstart, nrows=natoms) if return_prop_info: return system, short_prop_info else: return system
def load(table, box, symbols=None, system=None, prop_name=None, table_name=None, shape=None, unit=None, dtype=None, prop_info=None, skiprows=None, nrows=None, comment=None): """ Reads in tabular data into atomic properties. Parameters ---------- table : str or file-like object The table content, file path or file-like object containing the content to read. box : atomman.Box The atomic box to use when generating a System around the data. symbols : tuple, optional Allows the list of element symbols to be assigned during loading. system : atomman.System, optional The atomic system to load the values to. If not given, a new system will be constructed. prop_name : list, optional The Atoms properties to generate. Must be given if prop_info is not. table_name : list, optional The table column name(s) that correspond to each prop_name. If not given, the table_name values will be based on the prop_name values. shape : list, optional The shape of each per-atom property. If not given, will be inferred from the length of each table_name value. unit : list, optional Lists the units for each prop_name as stored in the table. For a value of None, no conversion will be performed for that property. For a value of 'scaled', the corresponding table values will be taken in box-scaled units. If not given, all unit values will be set to None (i.e. no conversions). dtype : list, optional Allows for the data type of each property to be explicitly given. Values of None will infer the data type from the corresponding property values. If not given, all values will be None. prop_info : list of dict, optional Structured form of property conversion parameters, in which each dictionary in the list corresponds to a single atoms property. Each dictionary must have a 'prop_name' field, and can optionally have 'table_name', 'shape', 'unit', and 'dtype' fields. skiprows : int, optional Number of rows to skip before reading the data. nrows : int, optional Number of rows of data to read. comment : str, optional Specifies a character which indicates all text on a given line after is to be considered to be a comment and ignored by parser. This is often '#'. Returns ------- atomman.System The generated system. """ # Process conversion parameters prop_info = process_prop_info(prop_name=prop_name, table_name=table_name, shape=shape, unit=unit, dtype=dtype, prop_info=prop_info) # Build list of all table_names table_name = [] for prop in prop_info: table_name += prop['table_name'] # Read in table to dataframe with uber_open_rmode(table) as f: df = pd.read_csv(f, delim_whitespace=True, names=table_name, skiprows=skiprows, nrows=nrows, comment=comment) if 'id' in df: df = df.sort_values('id') # Generate System natoms = len(df) if system is None: system = System(atoms=Atoms(natoms=natoms), box=box) # Loop over all properties for prop in prop_info: pname = prop['prop_name'] if pname == 'a_id': continue # Get values value = df[prop['table_name']].values.reshape((natoms,) + prop['shape']) if prop['unit'] is not None: if prop['unit'] == "scaled": value = system.unscale(value) else: value = uc.set_in_units(value, prop['unit']) value = np.asarray(value, dtype=prop['dtype']) system.atoms.view[pname] = value # Set symbols if given if symbols is not None: system.symbols = symbols return system
def test_newton(self): newton = uc.set_in_units(1e5, 'dyn') assert pytest.approx(uc.get_in_units(newton, 'kg*m/s^2'), 1.0)
def __init__(self, volterra=None, gamma=None, model=None, tau=np.zeros((3, 3)), alpha=0.0, beta=np.zeros((3, 3)), cutofflongrange=None, fullstress=True, cdiffelastic=False, cdiffsurface=True, cdiffstress=False, min_method='Powell', min_kwargs=None, min_options=None): """ Initializes an SDVPN object. Parameters ---------- volterra : atomman.defect.VolterraDislocation, optional The elastic solution for a Volterra dislocation to use as the basis of the model. Either volterra or model are required, and both cannot be given at the same time. gamma : atomman.defect.GammaSurface, optional The gamma surface to use for the solution. Required unless model is given and the model content contains gamma surface data. model : str or DataModelDict, optional Saved data from previous SDVPN runs to load. Either volterra or model are required, and both cannot be given at the same time. tau : numpy.ndarray, optional A (3,3) array giving the stress tensor to apply to the system using the stress energy term. Only the xy, yy, and yz components are used. Default value is all zeros. alpha : list of float, optional The alpha coefficient(s) used by the nonlocal energy term. Default value is [0.0]. beta : numpy.ndarray, optional The (3,3) array of beta coefficient(s) used by the surface energy term. Default value is all zeros. cutofflongrange : float, optional The cutoff distance to use for computing the long-range energy. Default value is 1000 angstroms. fullstress : bool, optional Flag indicating which stress energy algorithm to use. Default value is True. cdiffelastic : bool, optional Flag indicating if the dislocation density for the elastic energy component is computed with central difference (True) or simply neighboring values (False). Default value is False. cdiffsurface : bool, optional Flag indicating if the dislocation density for the surface energy component is computed with central difference (True) or simply neighboring values (False). Default value is True. cdiffstress : bool, optional Flag indicating if the dislocation density for the stress energy component is computed with central difference (True) or simply neighboring values (False). Only matters if fullstress is True. Default value is False. min_method : str, optional The scipy.optimize.minimize method to use. Default value is 'Powell'. min_options : dict, optional Any options to pass on to scipy.optimize.minimize. Default value is {}. """ # Load solution from existing model if model is not None: if volterra is not None: raise ValueError('model cannot be given with volterra') self.load(model, gamma=gamma) # Extract parameters and check solution compatibility elif volterra is not None: # Check that gamma is given if gamma is None: raise ValueError('gamma is required if volterra is given') # Check that burgers is in the slip plane if not np.isclose(np.dot(volterra.n, volterra.burgers), 0.0): raise ValueError( 'dislocation burgers vector must be in the slip plane') # Get m, n, ξ, K_tensor, burgers, and transform from volterra m, n, ξ = volterra.m, volterra.n, volterra.ξ K_tensor = volterra.K_tensor burgers = volterra.burgers transform = volterra.transform # Transform K_tensor, burgers and transform to [m,n,ξ] orientation mnξ = np.array( [m, n, ξ]) # This is transformation matrix to [m,n,ξ] setting K_tensor = mnξ.dot(K_tensor.dot(mnξ.T)) burgers = mnξ.dot(burgers) transform = np.matmul(mnξ, transform) # Check if dislocation system is compatible with gamma surface planenormal = transform.dot(gamma.planenormal) if not np.isclose(planenormal[0], 0.0) or not np.isclose( planenormal[2], 0.0): raise ValueError( 'different slip planes for gamma and volterra found') # Set basic solution definition self.__K_tensor = K_tensor self.__burgers = burgers self.__transform = transform self.__gamma = gamma # Set options self.tau = tau self.alpha = alpha self.beta = beta if cutofflongrange is None: self.cutofflongrange = uc.set_in_units(1000, 'angstrom') else: self.cutofflongrange = cutofflongrange self.fullstress = fullstress self.cdiffelastic = cdiffelastic self.cdiffsurface = cdiffsurface self.cdiffstress = cdiffstress self.min_method = min_method self.min_options = min_options self.min_kwargs = min_kwargs else: raise ValueError('either dislsol or model must be given')
def dislocation_array(system, dislsol=None, m=None, n=None, burgers=None, bwidth=None, cutoff=None): """ Method that converts a bulk crystal system into a periodic array of dislocations. A single dislocation is inserted using a dislocation solution. The system's box and pbc are altered such that the system is periodic and compatible across the two box vectors contained in the slip plane. The third box vector is non-periodic, resulting in free surfaces parallel to the dislocation's slip plane. Parameters ---------- system : atomman.System A perfect, bulk atomic system. dislsol : atomman.defect.Stroh or atomman.defect.IsotropicVolterra, optional A dislocation solution to use to displace atoms by. If not given, all atoms will be given linear displacements associated with the long-range limits. m : array-like object, optional The dislocation solution m unit vector. This vector is in the slip plane and perpendicular to the dislocation line direction. Only needed if dislsol is not given. n : array-like object, optional The dislocation solution n unit vector. This vector is normal to the slip plane. Only needed if dislsol is not given. burgers : array-like object, optional The Cartesian Burger's vector for the dislocation relative to the given system's Cartesian coordinates. Only needed if dislsol is not given. bwidth : float, optional The width of the boundary region at the free surfaces. Atoms within the boundaries will be displaced by linear displacements instead of by the dislocation solution. Only given if dislsol is not None. Default value if dislsol is given is 10 Angstroms. cutoff : float, optional Cutoff distance to use for identifying duplicate atoms to remove. For dislocations with an edge component, applying the displacements creates an extra half-plane of atoms that will have (nearly) identical positions with other atoms after altering the boundary conditions. Default cutoff value is 0.5 Angstrom. """ # Input parameter setup if dislsol is None: if m is None or n is None: raise ValueError('m and n are needed if no dislsol is given') try: assert np.isclose(np.linalg.norm(m), 1.0) assert np.isclose(np.linalg.norm(n), 1.0) assert np.isclose(m.dot(n), 0.0) except: raise ValueError('m and n must be perpendicular unit vectors') if bwidth is not None: raise ValueError('bwidth not allowed if dislsol is not given') else: m = dislsol.m n = dislsol.n burgers = dislsol.burgers if bwidth is None: bwidth = uc.set_in_units(10, 'angstrom') if cutoff is None: cutoff = uc.set_in_units(0.5, 'angstrom') # Extract system values pos = system.atoms.pos vects = system.box.vects spos = system.atoms_prop(key='pos', scale=True) # Identify system orientation indices motionindex = None lineindex = None pnormindex = None for i in range(3): if np.isclose(np.abs(vects[i].dot(np.cross(m, n))), np.linalg.norm(vects[i])): lineindex = i elif not np.isclose(vects[i].dot(n), 0.0): if pnormindex is not None: raise ValueError("Multiple box vectors have components normal to dislocation solution's slip plane") pnormindex = i if lineindex is None: raise ValueError("No box vectors found parallel to dislocation solution's line vector") if pnormindex is None: raise ValueError("No box vectors have components normal to dislocation solution's slip plane") motionindex = 3 - (lineindex + pnormindex) # Compute new box vects and pbc newvects = deepcopy(system.box.vects) if burgers[motionindex] > 0: newvects[motionindex] -= burgers / 2 else: newvects[motionindex] += burgers / 2 newbox = Box(vects=newvects, origin=system.box.origin) newpbc = [True, True, True] newpbc[pnormindex] = False # Generate a test system to identify duplicate atoms length = np.abs(vects[motionindex].dot(m)) testsystem = System(atoms=deepcopy(system.atoms), box=newbox, pbc=newpbc, symbols=system.symbols) testsystem.atoms.pos += linear_displacement(pos, burgers, length, m, n) testsystem.atoms.old_id = range(testsystem.natoms) # Identify boundary atoms to check spos = testsystem.atoms_prop(key='pos', scale=True) sburgers = 2 * burgers[motionindex] / (length) boundaryatoms = testsystem.atoms[(spos[:, motionindex] < sburgers) | (spos[:, motionindex] > 1.0 - sburgers)] # Compare distances between boundary atoms to identify duplicates dup_atom_ids = [] mins = [] for ni, i in enumerate(boundaryatoms.old_id[:-1]): js = boundaryatoms.old_id[ni+1:] try: distances = np.linalg.norm(testsystem.dvect(i, js), axis=1) mindistance = distances.min() except: mindistance = np.linalg.norm(testsystem.dvect(i, js)) if mindistance < cutoff: dup_atom_ids.append(i) ii = np.ones(system.natoms, dtype=bool) ii[dup_atom_ids] = False # Generate new system with duplicate atoms removed newsystem = System(atoms=system.atoms[ii], box=newbox, pbc=newpbc, symbols=system.symbols) # Check if number of atoms deleted matches expected value expected = len(pos[(pos[:, motionindex] >= 0.0) & (pos[:, motionindex] <= np.abs(burgers[motionindex]))]) // 2 actual = system.natoms - newsystem.natoms if expected != actual: #warnings.warn('%i deleted atoms expected but %i atoms deleted' %(expected, actual), Warning) raise ValueError('Deleted atom mismatch: expected %i, actual %i. Adjust system dimensions and/or cutoff' %(expected, actual)) if dislsol is None: # Use only linear displacements disp = linear_displacement(newsystem.atoms.pos, burgers, length, m, n) else: # Identify boundary atoms miny = system.box.origin.dot(n) maxy = miny + vects[pnormindex].dot(n) if maxy < miny: miny, maxy = maxy, miny y = newsystem.atoms.pos.dot(n) ii = np.where((y <= miny + bwidth) | (y >= maxy - bwidth)) # Use dislsol in middle and linear displacements at boundary disp = dislsol.displacement(newsystem.atoms.pos) disp[:, pnormindex] -= disp[:, pnormindex].mean() disp[ii] = linear_displacement(newsystem.atoms.pos[ii], burgers, length, m, n) # Displace atoms and wrap newsystem.atoms.pos += disp newsystem.wrap() return newsystem
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 stackingfaultmap(lammps_command, system, potential, shiftvector1, shiftvector2, mpi_command=None, numshifts1=11, numshifts2=11, cutboxvector=None, faultpos=0.5, etol=0.0, ftol=0.0, maxiter=10000, maxeval=100000, dmax=uc.set_in_units(0.01, 'angstrom')): """ Computes a generalized stacking fault map for shifts along a regular 2D grid. 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. shiftvector1 : list of floats or numpy.array One of the generalized stacking fault shifting vectors. shiftvector2 : list of floats or numpy.array One of the generalized stacking fault shifting vectors. mpi_command : str, optional The MPI command for running LAMMPS in parallel. If not given, LAMMPS will run serially. etol : float, optional The energy tolerance for the structure minimization. This value is unitless. (Default is 0.0). ftol : float, optional The force tolerance for the structure minimization. This value is in units of force. (Default is 0.0). maxiter : int, optional The maximum number of minimization iterations to use (default is 10000). maxeval : int, optional The maximum number of minimization evaluations to use (default is 100000). dmax : float, optional The maximum distance in length units that any atom is allowed to relax in any direction during a single minimization iteration (default is 0.01 Angstroms). 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'). numshifts1 : int, optional The number of equally spaced shiftfractions to evaluate along shiftvector1. numshifts2 : int, optional The number of equally spaced shiftfractions to evaluate along shiftvector2. Returns ------- dict Dictionary of results consisting of keys: - **'shift1'** (*numpy.array of float*) - The fractional shifts along shiftvector1 where the stacking fault was evaluated. - **'shift2'** (*numpy.array of float*) - The fractional shifts along shiftvector2 where the stacking fault was evaluated. - **'E_gsf'** (*numpy.array of float*) - The stacking fault formation energies measured for all the (shift1, shift2) coordinates. - **'delta_disp'** (*numpy.array of float*) - The change in the center of mass difference between before and after applying the faultshift for all the (shift1, shift2) coordinates. - **'A_fault'** (*float*) - The area of the fault surface. """ # Start sf_df as empty list sf_df = [] # Construct mesh of regular points shifts1, shifts2 = np.meshgrid(np.linspace(0, 1, numshifts1), np.linspace(0, 1, numshifts2)) # Identify lammps_date version lammps_date = lmp.checkversion(lammps_command)['date'] # Loop over all shift combinations for shiftfraction1, shiftfraction2 in zip(shifts1.flat, shifts2.flat): # Evaluate the system at the shift sf_df.append(stackingfaultworker(lammps_command, system, potential, shiftvector1, shiftvector2, shiftfraction1, shiftfraction2, mpi_command=mpi_command, cutboxvector=cutboxvector, faultpos=faultpos, etol=etol, ftol=ftol, maxiter=maxiter, maxeval=maxeval, dmax=dmax, lammps_date=lammps_date)) # Convert sf_df to pandas DataFrame sf_df = pd.DataFrame(sf_df) # Identify the zeroshift column zeroshift = sf_df[(np.isclose(sf_df.shift1, 0.0, atol=1e-10, rtol=0.0) & np.isclose(sf_df.shift2, 0.0, atol=1e-10, rtol=0.0))] assert len(zeroshift) == 1, 'zeroshift simulation not uniquely identified' # Get zeroshift values E_total_0 = zeroshift.E_total.values[0] A_fault = zeroshift.A_fault.values[0] disp_0 = zeroshift.disp.values[0] # Compute the stacking fault energy E_gsf = (sf_df.E_total.values - E_total_0) / A_fault # Compute the change in displacement normal to fault plane delta_disp = sf_df.disp.values - disp_0 results_dict = {} results_dict['shift1'] = sf_df.shift1.values results_dict['shift2'] = sf_df.shift2.values results_dict['E_gsf'] = E_gsf results_dict['delta_disp'] = delta_disp results_dict['A_fault'] = A_fault return results_dict