def test_get_uc_pos(): errors = [] # set up parameters to initiate get_uc_pos uc_lattice = mg_Li.symm_structure.lattice isite = [x for x in input_struct_i.sites if x.species_string == "Li"][0] esite = [x for x in input_struct_e.sites if x.species_string == "Li"][0] sm = StructureMatcher(ignored_species=[list(mg_Li.m_graph.graph.edges(data=True))[0][2]["hop"].isite.specie.name]) wi_specie = mg_Li.symm_structure[-1].specie p0, p1, p2 = get_uc_pos(isite, esite, mg_Li.symm_structure, input_struct_i, sm) # generate correct sites to compare test_p0 = PeriodicSite( wi_specie, np.array([2.91418875, 1.02974425, 4.4933425]), uc_lattice, coords_are_cartesian=True ) test_p1 = PeriodicSite( wi_specie, np.array([4.82950555, 1.0247028, 4.10369437]), uc_lattice, coords_are_cartesian=True ) test_p2 = PeriodicSite( wi_specie, np.array([6.74482475, 1.01967025, 3.7140425]), uc_lattice, coords_are_cartesian=True ) if not test_p0.__eq__(p0): errors.append("Initial site does not match") if not test_p1.__eq__(p1): errors.append("Middle site does not match") if not test_p2.__eq__(p2): errors.append("Ending site does not match") assert not errors, "errors occured:\n" + "\n".join(errors)
def get_uc_pos( isite: PeriodicSite, esite: PeriodicSite, uc: Structure, sc: Structure, sm: StructureMatcher, ) -> Tuple[PeriodicSite, PeriodicSite, PeriodicSite]: """Take positions in the supercel and transform into the unitcell positions Args: isite: initial site in the SC esite: ending site in the SC uc: Unit Cell structre sc: Super Cell structure sm: StructureMatcher object with the working ion ignored Returns: The positions in the unit cell """ mapping = get_matched_structure_mapping(base=uc, inserted=sc, sm=sm) if mapping is None: raise ValueError( "Cannot obtain inverse mapping, consider lowering tolerances " "in StructureMatcher") sc_m, total_t = mapping sc_ipos = isite.frac_coords sc_ipos_t = sc_ipos - total_t uc_ipos = sc_ipos_t.dot(sc_m) image_trans = np.floor(uc_ipos) uc_ipos = uc_ipos - image_trans uc_ipos = _get_first_close_site(uc_ipos, uc) sc_epos = esite.frac_coords sc_epos_t = sc_epos - total_t uc_epos = sc_epos_t.dot(sc_m) uc_epos = uc_epos - image_trans uc_epos = _get_first_close_site(uc_epos, uc) sc_msite = PeriodicSite( esite.specie, (sc_ipos + sc_epos) / 2, esite.lattice, ) sc_mpos = sc_msite.frac_coords sc_mpos_t = sc_mpos - total_t uc_mpos = sc_mpos_t.dot(sc_m) uc_mpos = uc_mpos - image_trans uc_mpos = _get_first_close_site(uc_mpos, uc) p0 = PeriodicSite(isite.specie, uc_ipos, uc.lattice) p1 = PeriodicSite(esite.specie, uc_mpos, uc.lattice) p2 = PeriodicSite(esite.specie, uc_epos, uc.lattice) return p0, p1, p2
def test_add_sp_hydrogen(): """Add H on simplified case with one neighbor""" site_b_coord = [0, 0, 0] site_a_coord = [0.5, 0, 0] lattice = Lattice.from_parameters(1, 1, 1, 90, 90, 90) site_a = PeriodicSite(Composition("H"), site_a_coord, lattice) site_b = ConnectedSite( PeriodicSite(Composition("H"), site_b_coord, lattice)) hydrogen = add_sp_hydrogen(site_a, [site_b]) assert len(hydrogen) == 3 assert np.abs(np.linalg.norm(np.array(site_a_coord) - hydrogen) - 1) < 0.01 assert np.linalg.norm(hydrogen[1]) < 0.01 assert np.linalg.norm(hydrogen[2]) < 0.01
def delete_bt_layer(self, bt, tol=0.25, axis=2): """ Delete bottom or top layer of the structure. Args: bt (str): Specify whether it's a top or bottom layer delete. "b" means bottom layer and "t" means top layer. tol (float), Angstrom: Tolerance factor to determine whether two atoms are at the same plane. Default to 0.25 axis (int): The direction of top and bottom layers. 0: x, 1: y, 2: z """ if bt == "t": l1, l2 = (-1, -2) else: l1, l2 = (0, 1) l = self.lattice.abc[axis] layers = self.sort_sites_in_layers(tol=tol, axis=axis) l_dist = abs(layers[l1][0].coords[axis] - layers[l2][0].coords[axis]) l_vector = [1, 1] l_vector.insert(axis, (l - l_dist) / l) new_lat = Lattice(self.lattice.matrix * np.array(l_vector)[:, None]) layers.pop(l1) sites = reduce(lambda x, y: np.concatenate((x, y), axis=0), layers) new_sites = [] l_dist = 0 if bt == "t" else l_dist l_vector = [0, 0] l_vector.insert(axis, l_dist) for i in sites: new_sites.append(PeriodicSite(i.specie, i.coords - l_vector, new_lat, coords_are_cartesian=True)) self._sites = new_sites self._lattice = new_lat
def create_saturated_interstitial_structure(interstitial_def, dist_tol=0.1): """ this takes a Interstitial defect object and generates the sublattice for it based on the structure's space group. Useful for understanding multiplicity of an interstitial defect in thermodynamic analysis. NOTE: if large relaxation happens to interstitial or defect involves a complex then there may be additional degrees of freedom that need to be considered for the multiplicity. Args: dist_tol: changing distance tolerance of saturated structure, allowing for possibly overlapping sites but ensuring space group is maintained Returns: Structure object decorated with interstitial site equivalents """ sga = SpacegroupAnalyzer(interstitial_def.bulk_structure.copy()) sg_ops = sga.get_symmetry_operations(cartesian=True) # copy bulk structure to make saturated interstitial structure out of # artificially lower distance_tolerance to allow for distinct interstitials # with lower symmetry to be replicated - This is OK because one would never # actually use this structure for a practical calcualtion... saturated_defect_struct = interstitial_def.bulk_structure.copy() saturated_defect_struct.DISTANCE_TOLERANCE = dist_tol for sgo in sg_ops: new_interstit_coords = sgo.operate(interstitial_def.site.coords[:]) poss_new_site = PeriodicSite( interstitial_def.site.specie, new_interstit_coords, saturated_defect_struct.lattice, to_unit_cell=True, coords_are_cartesian=True, ) try: # will raise value error if site already exists in structure saturated_defect_struct.append( poss_new_site.specie, poss_new_site.coords, coords_are_cartesian=True, validate_proximity=True, ) except ValueError: pass # do final space group analysis to make sure symmetry not lowered by saturating defect structure saturated_sga = SpacegroupAnalyzer(saturated_defect_struct) if saturated_sga.get_space_group_number() != sga.get_space_group_number(): raise ValueError( "Warning! Interstitial sublattice generation " "has changed space group symmetry. Recommend " "reducing dist_tol and trying again..." ) return saturated_defect_struct
def get_nearest_neighbours_from_point(vec_cartesian,struct): site = PeriodicSite(DummySpecie(),vec_cartesian,struct.lattice,coords_are_cartesian=True) dists = [i[1] for i in struct.get_neighbors(site,10,include_index=True) if i[1]>0.1] min_dist = min(dists) nearest_neighbour_infos=struct.get_neighbors(site,min_dist+0.2,include_index=True) nearest_neighbours=[i[2] for i in nearest_neighbour_infos if i[1]>0.1] return nearest_neighbours
def setUp(self): self.lattice = Lattice.cubic(10.0) self.si = Element("Si") self.site = PeriodicSite("Fe", np.array([0.25, 0.35, 0.45]), self.lattice) self.site2 = PeriodicSite({"Si":0.5}, np.array([0, 0, 0]), self.lattice) self.assertEquals(self.site2.species_and_occu, {Element('Si'): 0.5}, "Inconsistent site created!") self.propertied_site = PeriodicSite(Specie("Fe", 2), [0.25, 0.35, 0.45], self.lattice, properties={'magmom':5.1, 'charge':4.2})
def test_get_symmetry_operations(self): fracsymmops = self.sg.get_symmetry_operations() symmops = self.sg.get_symmetry_operations(True) self.assertEqual(len(symmops), 8) latt = self.structure.lattice for fop, op in zip(fracsymmops, symmops): for site in self.structure: newfrac = fop.operate(site.frac_coords) newcart = op.operate(site.coords) self.assertTrue(np.allclose(latt.get_fractional_coords(newcart), newfrac)) found = False newsite = PeriodicSite(site.species_and_occu, newcart, latt, coords_are_cartesian=True) for testsite in self.structure: if newsite.is_periodic_image(testsite, 1e-3): found = True break self.assertTrue(found)
def test_add_sp3_hydrogens_on_cn1(): """Test on a toy example with one neighbor on the x axis""" site_b_coord = [0, 0, 0] site_a_coord = [0.5, 0, 0] lattice = Lattice.from_parameters(1, 1, 1, 90, 90, 90) site_a = PeriodicSite(Composition("H"), site_a_coord, lattice) site_b = ConnectedSite( PeriodicSite(Composition("H"), site_b_coord, lattice)) vectors = add_sp3_hydrogens_on_cn1(site_a, [site_b]) for vector in vectors: assert (np.linalg.norm(vector - np.array(site_a_coord)) - 1) < 0.01 assert vector[0] > 0.5 assert vector[1] != 0 assert vector[2] != 0
def from_dict(cls, d): return cls(ComputedStructureEntry.from_dict(d['entry']), PeriodicSite.from_dict(d['site']), multiplicity=d.get('multiplicity', None), supercell_size=d.get('supercell_size', [1, 1, 1]), charge=d.get('charge', 0.0), charge_correction=d.get('charge_correction', 0.0), other_correction=d.get('other_correction', 0.0), name=d.get('name', None))
def test_to_from_dict(self): d = self.site2.to_dict site = PeriodicSite.from_dict(d) self.assertEqual(site, self.site2) self.assertNotEqual(site, self.site) d = self.propertied_site.to_dict site = Site.from_dict(d) self.assertEqual(site.magmom, 5.1) self.assertEqual(site.charge, 4.2)
def test_add_sp2_hydrogen(): """simplified test case of existing CN2 coordination""" site_b_coord = [0.1, 0.1, 0] site_a_coord = [0, 0.5, 0] site_c_coord = [0.1, 0.9, 0] lattice = Lattice.from_parameters(1, 1, 1, 90, 90, 90) site_a = PeriodicSite(Composition("H"), site_a_coord, lattice) site_b = ConnectedSite( PeriodicSite(Composition("H"), site_b_coord, lattice)) site_c = ConnectedSite( PeriodicSite(Composition("H"), site_c_coord, lattice)) hydrogen = add_sp2_hydrogen(site_a, [site_b, site_c]) assert len(hydrogen) == 3 assert np.abs(np.linalg.norm(hydrogen - np.array(site_a_coord)) - 1) < 0.01 assert np.abs(hydrogen[1] - 0.5) < 0.001 assert hydrogen[0] < 0 assert np.abs(hydrogen[2]) < 0.001
def test_add_methylene_hydrogens(): """simplified test case of existing CN2 coordination""" site_b_coord = [0.1, 0.1, 0] site_a_coord = [0, 0.5, 0] site_c_coord = [0.1, 0.9, 0] lattice = Lattice.from_parameters(1, 1, 1, 90, 90, 90) site_a = PeriodicSite(Composition("H"), site_a_coord, lattice) site_b = ConnectedSite( PeriodicSite(Composition("H"), site_b_coord, lattice)) site_c = ConnectedSite( PeriodicSite(Composition("H"), site_c_coord, lattice)) hydrogens = add_methylene_hydrogens(site_a, [site_b, site_c]) assert len(hydrogens) == 2 for vector in hydrogens: assert np.abs(np.linalg.norm(vector - np.array(site_a_coord)) - 1) < 0.01 assert np.abs(vector[1] - 0.5) < 0.01 assert vector[0] < 0.5 assert vector[2] <= 0.5
def setUp(self): l = Lattice([[3.52,0.0,2.033], [1.174,3.32,2.033], \ [0.0,0.0,4.066]]) s_bulk = Structure(l, ['Ga', 'As'], \ [[0.0000, 0.0000, 0.0000], \ [0.2500, 0.2500, 0.2500]]) defect_site = PeriodicSite('As', [0.25, 0.25, 0.25], l) defect = Vacancy(s_bulk, defect_site, charge=1.) defect_entry = DefectEntry(defect, 0.) entries = [defect_entry] vbm = 0.2 band_gap = 1. dpd = DefectPhaseDiagram(entries, vbm, band_gap) self.dp = DefectPlotter(dpd)
def test_distance_and_image(self): other_site = PeriodicSite("Fe", np.array([1, 1, 1]), self.lattice) (distance, image) = self.site.distance_and_image(other_site) self.assertAlmostEquals(distance, 6.22494979899, 5) self.assertTrue(([-1, -1, -1] == image).all()) (distance, image) = self.site.distance_and_image(other_site, [1, 0, 0]) self.assertAlmostEquals(distance, 19.461500456028563, 5) # Test that old and new distance algo give the same ans for "standard lattices" lattice = Lattice(np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])) site1 = PeriodicSite("Fe", np.array([0.01, 0.02, 0.03]), lattice) site2 = PeriodicSite("Fe", np.array([0.99, 0.98, 0.97]), lattice) self.assertTrue(site1.distance_and_image_old(site2)[0] == site1.distance_and_image(site2)[0]) lattice = Lattice.from_parameters(1, 0.01, 1, 10, 10, 10) site1 = PeriodicSite("Fe", np.array([0.01, 0.02, 0.03]), lattice) site2 = PeriodicSite("Fe", np.array([0.99, 0.98, 0.97]), lattice) self.assertTrue(site1.distance_and_image_old(site2)[0] > site1.distance_and_image(site2)[0]) site2 = PeriodicSite("Fe", np.random.rand(3), lattice) (dist_old, jimage_old) = site1.distance_and_image_old(site2) (dist_new, jimage_new) = site1.distance_and_image(site2) self.assertTrue(dist_old >= dist_new, "New distance algo should always give smaller answers!") self.assertFalse((dist_old == dist_new) ^ (jimage_old == jimage_new).all(), "If old dist == new dist, the images returned must be the same!")
def get_reconstructed_structure(components: List[Component], simplify_molecules: bool = True) -> Structure: """Reconstructs a structure from a list of components. Has the option to simplify molecular components into a single site positioned at the centre of mass of the molecular. If using this option, the components must have been generated with ``inc_molecule_graph=True``. Args: components: A list of structure components, generated using :obj:`pymatgen.analysis.dimensionality.get_structure_components`, with ``inc_molecule_graph=True``. simplify_molecules: Whether to simplify the molecular components into a single site positioned at the centre of mass of the molecule. Returns: The reconstructed structure. """ mol_sites = [] other_sites = [] if simplify_molecules: mol_components, components = filter_molecular_components(components) if mol_components: lattice = mol_components[0]["structure_graph"].structure.lattice mol_sites = [ PeriodicSite( c["structure_graph"].structure[0].specie, c["molecule_graph"].molecule.center_of_mass, lattice, coords_are_cartesian=True, ) for c in mol_components ] if components: other_sites = [ site for c in components for site in c["structure_graph"].structure ] return Structure.from_sites(other_sites + mol_sites)
def are_symmetrically_equivalent(self, sites1, sites2, symm_prec=1e-3): """ Given two sets of PeriodicSites, test if they are actually symmetrically equivalent under this space group. Useful, for example, if you want to test if selecting atoms 1 and 2 out of a set of 4 atoms are symmetrically the same as selecting atoms 3 and 4, etc. One use is in PartialRemoveSpecie transformation to return only symmetrically distinct arrangements of atoms. Args: sites1 ([Site]): 1st set of sites sites2 ([Site]): 2nd set of sites symm_prec (float): Tolerance in atomic distance to test if atoms are symmetrically similar. Returns: (bool): Whether the two sets of sites are symmetrically equivalent. """ def in_sites(site): for test_site in sites1: if test_site.is_periodic_image(site, symm_prec, False): return True return False for op in self: newsites2 = [ PeriodicSite(site.species_and_occu, op.operate(site.frac_coords), site.lattice) for site in sites2 ] ismapping = True for site in newsites2: if not in_sites(site): ismapping = False break if ismapping: return True return False
def make_supercell(self, scaling_matrix): """ Create a supercell. Very similar to pymatgen's Structure.make_supercell However, we need to make sure that all fractional coordinates that equal to 1 will become 0 and the lattice are redefined so that x_c = [0, 0, c] Args: scaling_matrix (3x3 matrix): The scaling matrix to make supercell. """ s = self * scaling_matrix for i, site in enumerate(s): f_coords = np.mod(site.frac_coords, 1) # The following for loop is probably not necessary. But I will leave # it here for now. for j, v in enumerate(f_coords): if abs(v - 1) < 1e-6: f_coords[j] = 0 s[i] = PeriodicSite(site.specie, f_coords, site.lattice, properties=site.properties) self._sites = s.sites self._lattice = s.lattice new_lat = Lattice.from_parameters(*s.lattice.parameters) self.lattice = new_lat
def get_primitive_standard_structure(self, international_monoclinic=True): """ Gives a structure with a primitive cell according to certain standards the standards are defined in Setyawan, W., & Curtarolo, S. (2010). High-throughput electronic band structure calculations: Challenges and tools. Computational Materials Science, 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010 Returns: The structure in a primitive standardized cell """ conv = self.get_conventional_standard_structure( international_monoclinic=international_monoclinic) lattice = self.get_lattice_type() if "P" in self.get_spacegroup_symbol() or lattice == "hexagonal": return conv if lattice == "rhombohedral": # check if the conventional representation is hexagonal or # rhombohedral lengths, angles = conv.lattice.lengths_and_angles if abs(lengths[0] - lengths[2]) < 0.0001: transf = np.eye else: transf = np.array([[-1, 1, 1], [2, 1, 1], [-1, -2, 1]], dtype=np.float) / 3 elif "I" in self.get_spacegroup_symbol(): transf = np.array([[-1, 1, 1], [1, -1, 1], [1, 1, -1]], dtype=np.float) / 2 elif "F" in self.get_spacegroup_symbol(): transf = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=np.float) / 2 elif "C" in self.get_spacegroup_symbol(): if self.get_crystal_system() == "monoclinic": transf = np.array([[1, 1, 0], [-1, 1, 0], [0, 0, 2]], dtype=np.float) / 2 else: transf = np.array([[1, -1, 0], [1, 1, 0], [0, 0, 2]], dtype=np.float) / 2 else: transf = np.eye(3) new_sites = [] latt = Lattice(np.dot(transf, conv.lattice.matrix)) for s in conv: new_s = PeriodicSite(s.specie, s.coords, latt, to_unit_cell=True, coords_are_cartesian=True, properties=s.properties) if not any(map(new_s.is_periodic_image, new_sites)): new_sites.append(new_s) if lattice == "rhombohedral": prim = Structure.from_sites(new_sites) lengths, angles = prim.lattice.lengths_and_angles a = lengths[0] alpha = math.pi * angles[0] / 180 new_matrix = [[a * cos(alpha / 2), -a * sin(alpha / 2), 0], [a * cos(alpha / 2), a * sin(alpha / 2), 0], [ a * cos(alpha) / cos(alpha / 2), 0, a * math.sqrt(1 - (cos(alpha)**2 / (cos(alpha / 2)**2))) ]] new_sites = [] latt = Lattice(new_matrix) for s in prim: new_s = PeriodicSite(s.specie, s.frac_coords, latt, to_unit_cell=True, properties=s.properties) if not any(map(new_s.is_periodic_image, new_sites)): new_sites.append(new_s) return Structure.from_sites(new_sites) return Structure.from_sites(new_sites)
def __init__(self, structure, max_min_oxid, allowed_subst, oxid_states, min_dis=13., inter=False, interstitials_extr_elts=[], vac=True, extrapolation=True): """ Args: structure: the bulk structure max_min_oxid: the minimal and maximum oxidation state of each element as a dict. For instance {Element("O"):(-2,0)} allowed_subst: the allowed substitutions between elements. Including intrinsic (e.g., anti-sites) and extrinsic. Example: {Element("Co"):[Element("Zn"),Element("Mn")]} means Co sites can be substituted by Mn or Zn. oxid_states: the oxidation state of the elements in the compound e.g. {Element("Fe"):2,Element("O"):-2} size_limit: maximum atom numbers allowed in the supercell interstitials_sites: a list of PeriodicSites in the bulk structure on which we put an interstitial standardized: True means we use a standardized primitive cell """ DefectStructMaker.__init__(self, structure, min_dis, extrapolation) self.defects = [] nb_per_elts = {e:0 for e in structure.composition.elements} ### bulk### def get_bulk_sc(stru,sc_size): s=stru.copy() s.make_supercell(sc_size,to_unit_cell=True) return s self.defects.append({'short_name':'bulk','uc_type':self.uc_type,'sc_type':self.sc_type,'bulk_unitcell':self.struct_orig,'supercells': [{'size': s_size,'structure':get_bulk_sc(self.struct_orig, s_size)} for s_size in self.optimized_supercell]}) self.defects.append({'short_name':'dielectric','structure':self.struct_orig,'uc_type':self.uc_type}) charges_limit=range(-100,100) for s in self.struct.equivalent_sites: nb_per_elts[s[0].specie] = nb_per_elts[s[0].specie]+1 #vac if vac==True: list_charges=[] for c in range(max_min_oxid[s[0].specie][0], max_min_oxid[s[0].specie][1]+1): list_charges.append(-c) if 0 not in list_charges: list_charges.append(0) list_charges=range(min(list_charges),max(list_charges)+1) list_charges=list(set(charges_limit)&set(list_charges)) self.defects.append({'short_name': s[0].specie.symbol+str(nb_per_elts[s[0].specie])+"_vac", 'unique_sites': s[0], 'supercells':[{'size': s_size,'structure':self.make_defect_cell_vacancy(s[0], s_size), 'struct_no_move':self.make_defect_cell_vacancy(s[0], s_size,move_neighbours=False)} for s_size in self.optimized_supercell], 'charges':list_charges, 'uc_type':self.uc_type,'sc_type':self.sc_type, 'bulk_unitcell':self.struct_orig}) #sub if s[0].specie in allowed_subst: for subst in allowed_subst[s[0].specie]: list_charges_subst=[c-oxid_states[s[0].specie] for c in range(max_min_oxid[subst][0], max_min_oxid[subst][1]+1)] if 0 not in list_charges_subst: list_charges_subst.append(0) list_charges_subst=range(min(list_charges_subst),max(list_charges_subst)+1) list_charges_subst=list(set(charges_limit)&set(list_charges_subst)) self.defects.append({'short_name':s[0].specie.symbol+str(nb_per_elts[s[0].specie])+"_subst_" +subst.symbol, 'unique_sites':s[0],'supercells':[{'size':s_size,'structure': self.make_defect_cell_intrinsic_subst_defects(s[0], subst, s_size), 'struct_no_move':self.make_defect_cell_intrinsic_subst_defects( s[0], subst, s_size,move_neighbours=False)} for s_size in self.optimized_supercell], 'charges':list_charges_subst,'uc_type':self.uc_type,'sc_type':self.sc_type,'bulk_unitcell':self.struct_orig}) #interstitials if inter: for elt in self.struct.composition.elements: count = 1 list_charges_inter=[c for c in range(max_min_oxid[elt][0],max_min_oxid[elt][1]+1)] if 0 not in list_charges_inter: list_charges_inter.append(0) list_charges_inter=range(min(list_charges_inter),max(list_charges_inter)+1) list_charges_inter=list(set(charges_limit)&set(list_charges_inter)) for frac_coord in self.inter_sites: self.defects.append({'short_name':elt.symbol+str(count)+"_inter", 'unique_sites':PeriodicSite(elt, frac_coord, structure.lattice), 'supercells':[{'size':s_size, 'structure':self.make_defect_interstitial( PeriodicSite(elt, frac_coord, self.struct_orig.lattice), s_size)} for s_size in self.optimized_supercell], 'charges':list_charges_inter, 'uc_type':self.uc_type,'sc_type':self.sc_type, 'bulk_unitcell':self.struct_orig}) count = count+1 for elt in interstitials_extr_elts: count = 1 list_charges_subst=[c for c in range(max_min_oxid[elt][0],max_min_oxid[elt][1]+1)] if 0 not in list_charges_subst: list_charges_subst.append(0) list_charges_subst=range(min(list_charges_subst),max(list_charges_subst)+1) list_charges_subst=list(set(charges_limit)&set(list_charges_subst)) for frac_coord in self.inter_sites: self.defects.append({'short_name':elt.symbol+str(count)+"_inter", 'unique_sites':PeriodicSite(elt, frac_coord, structure.lattice), 'supercells':[{'size':s_size, 'structure':self.make_defect_interstitial( PeriodicSite(elt, frac_coord, self.struct_orig.lattice), s_size)} for s_size in self.optimized_supercell], 'charges':list_charges_subst,'uc_type':self.uc_type,'sc_type':self.sc_type, 'bulk_unitcell':self.struct_orig}) count = count+1
def test_equality(self): other_site = PeriodicSite("Fe", np.array([1, 1, 1]), self.lattice) self.assertTrue(self.site.__eq__(self.site)) self.assertFalse(other_site.__eq__(self.site)) self.assertFalse(self.site.__ne__(self.site)) self.assertTrue(other_site.__ne__(self.site))
def get_primitive_standard_structure(self): """ Gives a structure with a primitive cell according to certain standards the standards are defined in Setyawan, W., & Curtarolo, S. (2010). High-throughput electronic band structure calculations: Challenges and tools. Computational Materials Science, 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010 Returns: The structure in a primitive standardized cell """ conv = self.get_conventional_standard_structure() lattice = self.get_lattice_type() if "P" in self.get_spacegroup_symbol() or lattice == "hexagonal": return conv if lattice == "rhombohedral": conv = self.get_refined_structure() lengths, angles = conv.lattice.lengths_and_angles a = lengths[0] alpha = math.pi * angles[0] / 180 new_matrix = [ [a * cos(alpha / 2), -a * sin(alpha / 2), 0], [a * cos(alpha / 2), a * sin(alpha / 2), 0], [a * cos(alpha) / cos(alpha / 2), 0, a * math.sqrt(1 - (cos(alpha) ** 2 / (cos(alpha / 2) ** 2)))]] new_sites = [] for s in conv.sites: new_s = PeriodicSite( s.specie, s.frac_coords, Lattice(new_matrix), to_unit_cell=True, properties=s.properties) unique = True for t in new_sites: if new_s.is_periodic_image(t): unique = False if unique: new_sites.append(new_s) return Structure.from_sites(new_sites) transf = np.eye(3) if "I" in self.get_spacegroup_symbol(): transf = np.array([[-1, 1, 1], [1, -1, 1], [1, 1, -1]], dtype=np.float) / 2 elif "F" in self.get_spacegroup_symbol(): transf = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=np.float) / 2 elif "C" in self.get_spacegroup_symbol(): if self.get_crystal_system() == "monoclinic": transf = np.array([[1, 1, 0], [-1, 1, 0], [0, 0, 2]], dtype=np.float) / 2 else: transf = np.array([[1, -1, 0], [1, 1, 0], [0, 0, 2]], dtype=np.float) / 2 new_sites = [] for s in conv.sites: new_s = PeriodicSite( s.specie, s.coords, Lattice(np.dot(transf, conv.lattice.matrix)), to_unit_cell=True, coords_are_cartesian=True, properties=s.properties) unique = True for t in new_sites: if new_s.is_periodic_image(t): unique = False if unique: new_sites.append(new_s) return Structure.from_sites(new_sites)
def apply_symmop(psites, ops): """ symmop and xyz in cif file: lets say xyz -- op1 --> x'y'z' and xyz -- op2 --> x!y!z! and it is possible to have x'y'z' is_close x!y!z! this means one should take only x'y'z' or x!y!z!, aka op1 is equivalent to op2 due to the symmetry implicated by xyz/the asymmectric unit, e.g. ALOVOO.cif -- Z=2, asymmectric unit given by cif is one molecule, but there're 4 ops so we need first check if the cif file behaves like this """ op_xyzs = [] for op in ops: n_xyzs = [] for ps in psites: new_coord = op.operate(ps.frac_coords) # new_coord = np.array([i - math.floor(i) for i in new_coord]) n_xyzs.append(new_coord) op_xyzs.append(n_xyzs) latt = psites[0].lattice def pbc_dist(fc1, fc2, lattice): v, d2 = pbc_shortest_vectors(lattice, fc1, fc2, return_d2=True) return math.sqrt(d2[0, 0]) def pbc_distmat(fcl1, fcl2): distmat = np.zeros((len(fcl1), len(fcl1))) for i in range(len(fcl1)): for j in range(i, len(fcl1)): distmat[i][j] = pbc_dist(fcl1[i], fcl2[j], latt) distmat[j][i] = distmat[i][j] return distmat def two_xyzs_close(xyzs1, xyzs2, tol=1e-5): dmat = pbc_distmat(xyzs1, xyzs2) almost_zeros = dmat[(dmat < tol)] if len(almost_zeros) > 0: return True return False op_identities = np.zeros((len(ops), len(ops)), dtype=bool) for i, j in itertools.combinations(range(len(ops)), 2): ixyzs = op_xyzs[i] jxyzs = op_xyzs[j] if two_xyzs_close(ixyzs, jxyzs): op_identities[i][j] = True op_identities[j][i] = True groups = [[0]] for i in range(len(ops)): for ig in range(len(groups)): if all(op_identities[i][j] for j in groups[ig]): groups[ig].append(i) if i not in [item for sublist in groups for item in sublist]: groups.append([i]) unique_ops = [ops[g[0]] for g in groups] new_psites = [] for ps in psites: iasym = 0 for op in unique_ops: new_coord = op.operate(ps.frac_coords) new_coord = np.array([i - math.floor(i) for i in new_coord]) new_properties = deepcopy(ps.properties) new_properties['iasym'] = iasym new_ps = PeriodicSite(ps.species_string, new_coord, ps.lattice, properties=deepcopy(new_properties)) new_psites.append(new_ps) iasym += 1 return new_psites, unique_ops
class PeriodicSiteTest(unittest.TestCase): def setUp(self): self.lattice = Lattice.cubic(10.0) self.si = Element("Si") self.site = PeriodicSite("Fe", np.array([0.25, 0.35, 0.45]), self.lattice) self.site2 = PeriodicSite({"Si":0.5}, np.array([0, 0, 0]), self.lattice) self.assertEquals(self.site2.species_and_occu, {Element('Si'): 0.5}, "Inconsistent site created!") self.propertied_site = PeriodicSite(Specie("Fe", 2), [0.25, 0.35, 0.45], self.lattice, properties={'magmom':5.1, 'charge':4.2}) def test_properties(self): """ Test the properties for a site """ self.assertEquals(self.site.a, 0.25) self.assertEquals(self.site.b, 0.35) self.assertEquals(self.site.c, 0.45) self.assertEquals(self.site.x, 2.5) self.assertEquals(self.site.y, 3.5) self.assertEquals(self.site.z, 4.5) self.assertTrue(self.site.is_ordered) self.assertFalse(self.site2.is_ordered) self.assertEqual(self.propertied_site.magmom, 5.1) self.assertEqual(self.propertied_site.charge, 4.2) def test_distance(self): other_site = PeriodicSite("Fe", np.array([0, 0, 0]), self.lattice) self.assertAlmostEquals(self.site.distance(other_site), 6.22494979899, 5) def test_distance_from_point(self): self.assertNotAlmostEqual(self.site.distance_from_point(np.array([0.1, 0.1, 0.1])), 6.22494979899, 5) self.assertAlmostEqual(self.site.distance_from_point(np.array([0.1, 0.1, 0.1])), 6.0564015718906887, 5) def test_distance_and_image(self): other_site = PeriodicSite("Fe", np.array([1, 1, 1]), self.lattice) (distance, image) = self.site.distance_and_image(other_site) self.assertAlmostEquals(distance, 6.22494979899, 5) self.assertTrue(([-1, -1, -1] == image).all()) (distance, image) = self.site.distance_and_image(other_site, [1, 0, 0]) self.assertAlmostEquals(distance, 19.461500456028563, 5) # Test that old and new distance algo give the same ans for "standard lattices" lattice = Lattice(np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])) site1 = PeriodicSite("Fe", np.array([0.01, 0.02, 0.03]), lattice) site2 = PeriodicSite("Fe", np.array([0.99, 0.98, 0.97]), lattice) self.assertTrue(site1.distance_and_image_old(site2)[0] == site1.distance_and_image(site2)[0]) lattice = Lattice.from_parameters(1, 0.01, 1, 10, 10, 10) site1 = PeriodicSite("Fe", np.array([0.01, 0.02, 0.03]), lattice) site2 = PeriodicSite("Fe", np.array([0.99, 0.98, 0.97]), lattice) self.assertTrue(site1.distance_and_image_old(site2)[0] > site1.distance_and_image(site2)[0]) site2 = PeriodicSite("Fe", np.random.rand(3), lattice) (dist_old, jimage_old) = site1.distance_and_image_old(site2) (dist_new, jimage_new) = site1.distance_and_image(site2) self.assertTrue(dist_old >= dist_new, "New distance algo should always give smaller answers!") self.assertFalse((dist_old == dist_new) ^ (jimage_old == jimage_new).all(), "If old dist == new dist, the images returned must be the same!") def test_is_periodic_image(self): other = PeriodicSite("Fe", np.array([1.25, 2.35, 4.45]), self.lattice) self.assertTrue(self.site.is_periodic_image(other), "This other site should be a periodic image.") other = PeriodicSite("Fe", np.array([1.25, 2.35, 4.46]), self.lattice) self.assertFalse(self.site.is_periodic_image(other), "This other site should not be a periodic image.") other = PeriodicSite("Fe", np.array([1.25, 2.35, 4.45]), Lattice.rhombohedral(2)) self.assertFalse(self.site.is_periodic_image(other), "Different lattices should result in different periodic sites.") def test_equality(self): other_site = PeriodicSite("Fe", np.array([1, 1, 1]), self.lattice) self.assertTrue(self.site.__eq__(self.site)) self.assertFalse(other_site.__eq__(self.site)) self.assertFalse(self.site.__ne__(self.site)) self.assertTrue(other_site.__ne__(self.site)) def test_to_from_dict(self): d = self.site2.to_dict site = PeriodicSite.from_dict(d) self.assertEqual(site, self.site2) self.assertNotEqual(site, self.site) d = self.propertied_site.to_dict site = Site.from_dict(d) self.assertEqual(site.magmom, 5.1) self.assertEqual(site.charge, 4.2)