def displace_site_positions( structure, displacement_vector_distribution_function_dictionary_by_type=None): """ Displaces the sites of structure using the displacement vector dist func provided for each type. If a dist func is not provided for a given site type, a zero function is used. """ Structure.validate(structure) if ( displacement_vector_distribution_function_dictionary_by_type == None ) or len(displacement_vector_distribution_function_dictionary_by_type ) == 0: raise Exception( "A displacement vector function for at least one atom type must be specified." ) for species_type in displacement_vector_distribution_function_dictionary_by_type: if not species_type in structure.sites.keys(): raise Exception("Strucuture does not have a site of type " + str(species_type)) #If a distribution function is not provided for a given type, set that type's function to the zero vector function for species_type in structure.sites.keys(): if not species_type in displacement_vector_distribution_function_dictionary_by_type: displacement_vector_distribution_function_dictionary_by_type[ species_type] = lambda: [0.0, 0.0, 0.0] for site in structure.sites: site.randomly_displace( displacement_vector_distribution_function_dictionary_by_type[ site['type']], structure.lattice)
def convert_phonopy_atoms_to_structure(phonopy_atoms_structure): """ Converts phonopy's representation of a structure to an instance of Structure. """ temporary_write_path = Path.get_temporary_path() Path.validate_does_not_exist(temporary_write_path) Path.validate_writeable(temporary_write_path) write_vasp(temporary_write_path, phonopy_atoms_structure) species_list = convert_phonopy_symbols_to_unique_species_list( phonopy_atoms_structure.symbols) structure_poscar_file = File(temporary_write_path) structure_poscar_file.insert( 5, " ".join(species_list)) #phonopy uses bad poscar format structure_poscar_file.write_to_path() final_structure = Structure(temporary_write_path) Path.remove(temporary_write_path) Structure.validate(final_structure) return final_structure
def __init__(self, path, structures_list, vasp_run_inputs_dictionary, wavecar_path=None): """ path should be the main path of the calculation set structures_list should be the set of structures for which forces are calculated. vasp_run_inputs_dictionary should look like: vasp_run_inputs_dictionary = { 'kpoint_scheme': 'Monkhorst', 'kpoint_subdivisions_list': [4, 4, 4], 'encut': 800, 'npar': 1 (optional) } wavecar_path should be the wavecar of a similar structure to the input structures of the structures_list. """ for structure in structures_list: Structure.validate(structure) self.path = path self.structures_list = structures_list self.vasp_run_inputs = copy.deepcopy(vasp_run_inputs_dictionary) self.wavecar_path = wavecar_path Path.make(path) self.initialize_vasp_runs()
def __init__(self, reference_structure, hessian, distorted_structure=None): """ If distorted_structure is inputted, then this will be used to seed the strain vector and eigen_component amplitudes. """ Structure.validate(reference_structure) eigen_pairs = hessian.get_sorted_hessian_eigen_pairs_list() # for eigen_pair in eigen_pairs: # print eigen_pair self.voigt_strains_list = [0.0] * 6 self.eigen_components_list = [] for eigen_pair in eigen_pairs: eigen_component = EigenComponent(eigen_pair, amplitude=0.0) self.eigen_components_list.append(eigen_component) if len(self.eigen_components_list ) != 3 * reference_structure.site_count: raise Exception( "Number of eigen components", len(self.eigen_components_list), "and number of degrees of freedom in reference structure", 3 * reference_structure.site_count, "are not equal.") self.reference_structure = reference_structure if distorted_structure: self.set_strains_and_amplitudes_from_distorted_structure( distorted_structure)
def any_sites_are_too_close( structure, minimum_atomic_distances_nested_dictionary_by_type, nearest_neighbors_max=3): """ Returns True if any two sites in this structure are within minimum_atomic_distance (angstroms) of each other. Min distances is different for each type pair, as specified by the input dictionary. nearest_neighbors_max controls how many nearest neighbor cell images to search. minimum_atomic_distances_nested_dictionary_by_type is in angstroms and looks like: { 'Ba': {'Ba': 1.5, 'Ti': 1.2, 'O': 1.4}, 'Ti': {'Ba': 1.2, 'Ti': 1.3, 'O': 1.3}, 'O': {'Ba': 1.4, 'Ti': 1.3, 'O': 0.8} } """ Structure.validate(structure) sites_list = structure.sites.get_sorted_list() for site_1_index in range(len(sites_list)): for site_2_index in range(site_1_index + 1, len(sites_list)): site_1 = sites_list[site_1_index] site_2 = sites_list[site_2_index] minimum_atomic_distance = minimum_atomic_distances_nested_dictionary_by_type[ site_1['type']][site_2['type']] if StructureAnalyzer.site_pair_is_too_close( structure, site_1, site_2, minimum_atomic_distance, nearest_neighbors_max): return True return False
def __init__(self, reference_structure, coordinate_mode='Cartesian'): """ """ Structure.validate(reference_structure) if not coordinate_mode in ['Cartesian', 'Direct']: raise Exception("Invalid coordinate mode given:", coordinate_mode) self.reference_structure = reference_structure self.displacement_vector = [0.0] * 3 * reference_structure.site_count self.coordinate_mode = coordinate_mode
def __init__(self, q_points_list, primitive_cell_structure): """ q_points_list should be the set of q_points instances. No particular ordering is necessary, but the order of this list will be preserved in the ordered dicitonary which stores this list. primitive_cell_structure should be the unit_cell used to generate the phonon normal mode data. """ Structure.validate(primitive_cell_structure) self.primitive_cell_structure = primitive_cell_structure self.initialize_q_points(q_points_list) self.validate_data()
def __init__(self, path, reference_structure=None, distorted_structure=None, vasp_run_inputs_dictionary=None): """ reference_structure should be a Structure instance with the same lattice as distorted structure. Usually this reference is chosen to be centrosymmetry (like ideal perovskite) so that absolute polarizations of the distorted structure can be caluclated. distorted_structure should be a Structure instance with the same lattice as the reference structure, but some of the atoms shifted to cause a change in polarization. vasp_run_inputs_dictionary should look like: vasp_run_inputs_dictionary = { 'kpoint_scheme': 'Monkhorst', 'kpoint_subdivisions_list': [4, 4, 4], 'encut': 800, } """ self.path = Path.expand(path) if (reference_structure == None) or (distorted_structure == None) or (vasp_run_inputs_dictionary == None): self.load() else: Structure.validate(reference_structure) Structure.validate(distorted_structure) if not reference_structure.lattice.equals( distorted_structure.lattice): raise Exception( "Warning: It's very difficult to interpret polarization results when the lattices of the reference and distorted structures are not equal. This is likely an error.", reference_structure.lattice, distorted_structure.lattice) self.reference_structure = reference_structure self.distorted_structure = distorted_structure self.vasp_run_inputs = copy.deepcopy(vasp_run_inputs_dictionary) self.vasp_run_list = [] self.save() Path.make(path) self.initialize_vasp_runs()
def __init__(self, path, initial_structures_list, reference_structure, vasp_relaxation_inputs_dictionary, reference_lattice_constant, misfit_strains_list, supercell_dimensions_list, calculate_polarizations=False): """ path should be the main path of the calculation set initial_structures_list should be the set of structures that are relaxed at each misfit strain reference structure can have any lattice but its atom positions must be in direct coords as the positions to compare polarizations to (choose a centrosymmetric structure if possible) vasp_relaxation_inputs_dictionary should look something like: { 'external_relaxation_count': 4, 'kpoint_schemes_list': ['Gamma'], 'kpoint_subdivisions_lists': [[1, 1, 1], [1, 1, 2], [2, 2, 4]], 'submission_script_modification_keys_list': ['100', 'standard', 'standard_gamma'], #optional - will default to whatever queue adapter gives 'submission_node_count_list': [1, 2], 'ediff': [0.001, 0.00001, 0.0000001], 'encut': [200, 400, 600, 800], 'isif' : [5, 2, 3] #any other incar parameters with value as a list } reference_lattice_constant should be the lattice constant a0 which, when multiplied by the list of misfit strains, generates the new in-plane lattice constant at those strains. For each lattice constant and each structure, a relaxation is performed. Then, the lowest energy structures at each misfit strain can be determined, and a convex hull results. """ for structure in initial_structures_list: Structure.validate(structure) basic_validators.validate_real_number(reference_lattice_constant) for misfit_strain in misfit_strains_list: basic_validators.validate_real_number(misfit_strain) self.path = path self.initial_structures_list = initial_structures_list self.reference_structure = reference_structure self.vasp_relaxation_inputs_dictionary = vasp_relaxation_inputs_dictionary self.reference_lattice_constant = reference_lattice_constant self.misfit_strains_list = misfit_strains_list self.supercell_dimensions_list = supercell_dimensions_list self.calculate_polarizations = calculate_polarizations Path.make(path) self.initialize_vasp_relaxations()
def get_supercell(structure, supercell_dimensions_list): """ Returns a new structure that is a supercell structure (structure is not modified) Argument supercell_dimensions_list could look like [1,3,4]. """ Structure.validate(structure) if not len(supercell_dimensions_list) == 3: raise Exception( "Argument supercell_dimensions_list must be of length 3") for dimension in supercell_dimensions_list: if not int(dimension) == dimension: raise Exception( "Only integer values accepted for supercell dimension factors." ) structure.convert_sites_to_direct_coordinates() new_lattice = structure.lattice.get_super_lattice( supercell_dimensions_list) new_sites = SiteCollection() for original_site in structure.sites: for a in range(supercell_dimensions_list[0]): for b in range(supercell_dimensions_list[1]): for c in range(supercell_dimensions_list[2]): a_frac = float(a) / float(supercell_dimensions_list[0]) b_frac = float(b) / float(supercell_dimensions_list[1]) c_frac = float(c) / float(supercell_dimensions_list[2]) new_site = copy.deepcopy(original_site) old_position = original_site['position'] new_site['position'] = [ old_position[0] / supercell_dimensions_list[0] + a_frac, old_position[1] / supercell_dimensions_list[1] + b_frac, old_position[2] / supercell_dimensions_list[2] + c_frac ] new_sites.append(new_site) return Structure(lattice=new_lattice, sites=new_sites)
def convert_structure_to_phonopy_atoms(structure): """ Takes structure, a Structure instance, and returns a PhonopyAtoms class (phonopy's representation of structures) """ temporary_write_path = Path.get_temporary_path() Structure.validate(structure) Path.validate_does_not_exist(temporary_write_path) Path.validate_writeable(temporary_write_path) structure.to_poscar_file_path(temporary_write_path) phonopy_structure = read_vasp(temporary_write_path) Path.remove(temporary_write_path) return phonopy_structure
def __init__(self, eigenvector, frequency, q_point_fractional_coordinates, band_index, primitive_cell_structure, atomic_masses_list): """ eigenvector should be a list that looks like [atom_1_x_component_of_generalized_displacement_complex_number, atom_1_y..., ..., atom_2_x, ...] -> the sqrt(mass) term should be left in for each component. This vector should be a list of length Nat, where Nat is the number of atoms in the primitive cell used to generate the phonon band structure. The q_point input argument should be a tuple of three real numbers in reduced coordinates with no factor of 2Pi, e.g. (0.5, 0.5, 0.0). frequency should be a complex number - if imaginary only then mode is unstable. band_index should start at zero and go to Nat*3-1 atomic_masses_list should look like [atomic_1_mass, atomic_2_mass, ..., atomic_Natoms_in_cell_mass] """ complex_vector_magnitude = np.linalg.norm(eigenvector) basic_validators.validate_approximately_equal(complex_vector_magnitude, 1.0, tolerance=0.000001) basic_validators.validate_complex_number(frequency) if (frequency.real > 0.0) and (frequency.imag > 0.0): raise Exception("Frequency cannot have mixed real and imaginary components. Frequency is", frequency) if not len(q_point_fractional_coordinates) == 3: raise Exception("Qpoint argument must have three compononents. Argument is", q_point_fractional_coordinates) if not isinstance(q_point_fractional_coordinates, tuple): raise Exception("q_point_fractional_coordinates must be a tuple", q_point_fractional_coordinates) if primitive_cell_structure.site_count != len(atomic_masses_list): raise Exception("Length of atomic masses list and number of sites are not equal.") Structure.validate(primitive_cell_structure) self.eigenvector = eigenvector self.frequency = frequency self.q_point_fractional_coordinates = q_point_fractional_coordinates self.band_index = band_index #0 through Nat self.primitive_cell_structure = primitive_cell_structure self.atomic_masses_list = atomic_masses_list self.eigen_displacements_list = self.get_eigen_displacements_vector()
def __init__(self, primitive_cell_structure, phonon_band_structure, supercell_dimensions_list, distorted_structure=None): """ primitive_cell_structure should be the primitive cell Structure class instance that was used to generate the phonon band structure. phonon_band_structure should be a PhononBandStructure instance with, at minimim, normal modes for the necessary wave vectors, loosely (38.10, pg 295 Born and Huang) -->For example, if a 2x1x1 supercell is expected, the following q points must be provided: (+1/2, 0, 0), (0, 0, 0) supercell_dimensions if distorted_structure is given, the dispalcements of this structure relative to the supercell reference structure are used to determine the normal coordinates list. """ Structure.validate(primitive_cell_structure) self.primitive_cell_structure = primitive_cell_structure self.phonon_band_structure = phonon_band_structure self.supercell_dimensions_list = supercell_dimensions_list #the total number of degrees of freedom to describe the ionic displacments of the system self.dof_count = 3 * self.primitive_cell_structure.site_count * self.supercell_dimensions_list[ 0] * self.supercell_dimensions_list[ 1] * self.supercell_dimensions_list[2] self.reference_supercell_structure = StructureManipulator.get_supercell( primitive_cell_structure, supercell_dimensions_list) self.validate_necessary_wave_vectors_exist() #FIX::self.number_of_normal_coordinates = 2*self.primitive_cell_structure.site_count*3*supercell_dimensions_list[0]*supercell_dimensions_list[1]*supercell_dimensions_list[2] self.initialize_normal_coordinates_list() if distorted_structure != None: self.set_normal_coordinates_list_from_distorted_structure( distorted_structure)
def __init__(self, q_point_fractional_coordinates, normal_modes_list, primitive_cell_structure): """ The q_point input argument should be a tuple of three real numbers in reduced coordinates with no factor of 2Pi, e.g. (0.5, 0.5, 0.0). normal_modes_list should be a list of normal modes with identical q_point values. This list should be arranged by order of band_index. """ if not len(q_point_fractional_coordinates) == 3: raise Exception("Qpoint argument must have three compononents. Argument is", q_point_fractional_coordinates) Structure.validate(primitive_cell_structure) if primitive_cell_structure.site_count*3 != len(normal_modes_list): raise Exception("Number of normal mode instances should be equal to the number of atoms in the primitive cell times three. Normal_modes_list is", normal_modes_list, "primitive structure is", primitive_cell_structure) self.q_point_fractional_coordinates = q_point_fractional_coordinates self.normal_modes_list = normal_modes_list self.primitive_cell_structure = primitive_cell_structure self.validate_normal_modes_list()
def __init__(self, primitive_cell_structure, phonon_band_structure, supercell_dimensions_list, normal_coordinate_instances_list=None): """ primitive_cell_structure should be the primitive cell Structure class instance that was used to generate the phonon band structure. phonon_band_structure should be a PhononBandStructure instance with, at minimim, normal modes for the necessary wave vectors, loosely (38.10, pg 295 Born and Huang) -->For example, if a 2x1x1 supercell is expected, the following q points must be provided: (+1/2, 0, 0), (0, 0, 0) supercell_dimensions if normal_coordinate_instances_list, this list is used to set the normal coordinates, else, the normal coordinates are initialized to zero. """ Structure.validate(primitive_cell_structure) self.primitive_cell_structure = primitive_cell_structure self.phonon_band_structure = phonon_band_structure self.supercell_dimensions_list = supercell_dimensions_list self.reference_supercell_structure = StructureManipulator.get_supercell(primitive_cell_structure, supercell_dimensions_list) self.validate_necessary_wave_vectors_exist() #FIX::self.number_of_normal_coordinates = 2*self.primitive_cell_structure.site_count*3*supercell_dimensions_list[0]*supercell_dimensions_list[1]*supercell_dimensions_list[2] if normal_coordinate_instances_list != None: # if len(normal_coordinate_instances_list) != self.number_of_normal_coordinates: # raise Exception("The number of given normal coordinates is not equal to the number needed to describe the structural distortions. Normal coordinates list given is", normal_coordinate_instances_list) # else: self.normal_coordinates_list = copy.deepcopy(normal_coordinate_instances_list) else: self.initialize_normal_coordinates_list()
def get_instance_from_displaced_structure_relative_to_reference_structure( reference_structure, displaced_structure, coordinate_mode='Cartesian'): """ Returns a DisplacementVector instance made by comparing each atom in displaced_structure with those of reference_structure. The atoms are mapped to each other based on their positions in the site collection lists. The vector connecting two sites is the shortest one possible under periodic boundary conditions. The coordinate_mode argument determines whether direct fractional coordinates (unitless) or Cartesian coordinates (in Angstroms) are used to describe the displacements. """ Structure.validate(reference_structure) Structure.validate(displaced_structure) if reference_structure.site_count != displaced_structure.site_count: raise Exception("Site counts of two structures must be equal.", reference_structure.site_count, displaced_structure.site_count) lattice_1_np_array = reference_structure.lattice.to_np_array().flatten( ) lattice_2_np_array = displaced_structure.lattice.to_np_array().flatten( ) difference_array = lattice_2_np_array - lattice_1_np_array if np.linalg.norm(difference_array) > 1e-8: raise Exception( "Lattice for reference and displaced structure are not equivalent. This will break the pbc shortest vector portion of this call.", difference_array) reference_structure = copy.deepcopy(reference_structure) displaced_structure = copy.deepcopy(displaced_structure) reference_structure.convert_sites_to_coordinate_mode('Direct') displaced_structure.convert_sites_to_coordinate_mode('Direct') displacement_vector = DisplacementVector( reference_structure=reference_structure, coordinate_mode=coordinate_mode) for site_index in range(reference_structure.site_count): reference_site = reference_structure.sites[site_index] displaced_site = displaced_structure.sites[site_index] if reference_site['type'] != displaced_site['type']: raise Exception("Types of two structures do not align.", reference_site['type'], displaced_site['type']) #if lattices of displaced and reference and not the same at this point, problems will arise here!!!! shortest_pbc_vector_between_sites = Vector.get_minimum_distance_between_two_periodic_points( fractional_coordinate_1=reference_site[ 'position'], ###############setting N_max to 2 - may need to be larger for heavily sheared systems!!! fractional_coordinate_2=displaced_site['position'], lattice=reference_structure.lattice, N_max=2, return_vector=True)[1] if coordinate_mode == 'Cartesian': shortest_pbc_vector_between_sites = Vector.get_in_cartesian_coordinates( direct_vector=shortest_pbc_vector_between_sites, lattice=reference_structure.lattice) for i in range(3): displacement_vector[site_index * 3 + i] = shortest_pbc_vector_between_sites[i] return displacement_vector
def displace_site_positions_with_minimum_distance_constraints( structure, displacement_vector_distribution_function_dictionary_by_type=None, minimum_atomic_distances_nested_dictionary_by_type=None): """ Displaces the atoms of structure using the specified probability distribution functions for each atom type (modifies in place) while ensuring the site positions obey provided minimum atommic distance constraints. Algorithm: 1. Randomly displace sites according to the given distribution funcitons for each type. 2. For those atoms that are too close under p.b.c. (as defined in the minimum distance matrix), randomly choose on of the 'collided' pair and re-displace according to its distribution function. 3. Repeat until no two atoms are too close. displacement_vector_distribution_function_dictionary_by_type should look like: { 'Ba': dist_func_1, #dist funcs are methods that return cartesian vectors in angstroms ([x, y, z]) using distributions of your choosing 'Ti': dist_func_2, ... } minimum_atomic_distances_nested_dictionary_by_type is in angstroms and looks like: { 'Ba': {'Ba': 1.5, 'Ti': 1.2, 'O': 1.4}, 'Ti': {'Ba': 1.2, 'Ti': 1.3, 'O': 1.3}, 'O': {'Ba': 1.4, 'Ti': 1.3, 'O': 0.8} } Calling any of the dist_funcs must return a displacement vector that uses cartesian coordinates and angstroms as its units. If no function is given for a type, the zero vector function is used. This method should approximately preserve the overall distribution rho(x1, y1, z1, x2, y2, z2, ...) resulting from multiplication of the indiviidual independent atomic distributions but with the regions of atoms too close (distance < min_atomic_dist) set to rho = 0. This just renormalizes the other parts of the distribution space so integral of rho sums to unity. """ Structure.validate(structure) original_structure = copy.deepcopy(structure) original_sites_list = copy.deepcopy(structure.sites.get_sorted_list()) new_sites_list = structure.sites.get_sorted_list() sites_to_check_indices_list = range( len(new_sites_list) ) #start with checking every index (looks like [0, 1, 2, ..., num_atoms-1]) StructureManipulator.displace_site_positions( structure, displacement_vector_distribution_function_dictionary_by_type) for try_count in range(200): indices_of_site_pairs_that_are_too_close_list = StructureAnalyzer.get_indices_of_site_pairs_that_are_too_close_to_sites_list( structure, sites_to_check_indices_list, minimum_atomic_distances_nested_dictionary_by_type) sites_to_check_indices_list = [] indices_to_displace_list = [] if indices_of_site_pairs_that_are_too_close_list != []: print indices_of_site_pairs_that_are_too_close_list for (site_1_index, site_2_index ) in indices_of_site_pairs_that_are_too_close_list: probabilities_list = [0.5, 0.5] random_selector = RandomSelector(probabilities_list) event_index = random_selector.get_event_index() if event_index == 0: index_to_displace = site_1_index else: index_to_displace = site_2_index print "moving randomly selected index " + str( index_to_displace) + " of pair " + str( (site_1_index, site_2_index)) if index_to_displace in indices_to_displace_list: print "already in index list of sites that have been moved" continue new_sites_list[index_to_displace][ 'coordinate_mode'] = original_sites_list[ index_to_displace]['coordinate_mode'] new_sites_list[index_to_displace][ 'position'] = copy.deepcopy( original_sites_list[index_to_displace]['position']) new_sites_list[index_to_displace].randomly_displace( displacement_vector_distribution_function_dictionary_by_type[ new_sites_list[index_to_displace]['type']], structure.lattice) sites_to_check_indices_list.append(index_to_displace) indices_to_displace_list.append(index_to_displace) else: return raise Exception( "Could not displace this structure while satisfying the given constraints" )