def test_structure_transform(self): # Test trivial case trivial = self.fit_r4.structure_transform(self.structure, self.structure.copy()) self.assertArrayAlmostEqual(trivial, self.fit_r4) # Test simple rotation rot_symm_op = SymmOp.from_axis_angle_and_translation([1, 1, 1], 55.5) rot_struct = self.structure.copy() rot_struct.apply_operation(rot_symm_op) rot_tensor = self.fit_r4.rotate(rot_symm_op.rotation_matrix) trans_tensor = self.fit_r4.structure_transform(self.structure, rot_struct) self.assertArrayAlmostEqual(rot_tensor, trans_tensor) # Test supercell bigcell = self.structure.copy() bigcell.make_supercell([2, 2, 3]) trans_tensor = self.fit_r4.structure_transform(self.structure, bigcell) self.assertArrayAlmostEqual(self.fit_r4, trans_tensor) # Test rotated primitive to conventional for fcc structure sn = self.get_structure("Sn") sn_prim = SpacegroupAnalyzer(sn).get_primitive_standard_structure() sn_prim.apply_operation(rot_symm_op) rotated = self.fit_r4.rotate(rot_symm_op.rotation_matrix) transformed = self.fit_r4.structure_transform(sn, sn_prim) self.assertArrayAlmostEqual(rotated, transformed)
def test_fit(self): """ Take two known matched structures 1) Ensure match 2) Ensure match after translation and rotations 3) Ensure no-match after large site translation 4) Ensure match after site shuffling """ sm = StructureMatcher() self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) # Test rotational/translational invariance op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, np.array([0.4, 0.7, 0.9])) self.struct_list[1].apply_operation(op) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test failure under large atomic translation self.struct_list[1].translate_sites([0], [.4, .4, .2], frac_coords=True) self.assertFalse(sm.fit(self.struct_list[0], self.struct_list[1])) self.struct_list[1].translate_sites([0], [-.4, -.4, -.2], frac_coords=True) # random.shuffle(editor._sites) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test FrameworkComporator sm2 = StructureMatcher(comparator=FrameworkComparator()) lfp = read_structure(os.path.join(test_dir, "LiFePO4.cif")) nfp = read_structure(os.path.join(test_dir, "NaFePO4.cif")) self.assertTrue(sm2.fit(lfp, nfp)) self.assertFalse(sm.fit(lfp, nfp)) #Test anonymous fit. self.assertEqual(sm.fit_anonymous(lfp, nfp), {Composition("Li"): Composition("Na")}) self.assertAlmostEqual(sm.get_minimax_rms_anonymous(lfp, nfp)[0], 0.096084154118549828) #Test partial occupancies. s1 = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]], [{"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) s2 = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]], [{"Fe": 0.25}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.75}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertFalse(sm.fit(s1, s2)) self.assertFalse(sm.fit(s2, s1)) s2 = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]], [{"Fe": 0.25}, {"Fe": 0.25}, {"Fe": 0.25}, {"Fe": 0.25}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertEqual(sm.fit_anonymous(s1, s2), {Composition("Fe0.5"): Composition("Fe0.25")}) self.assertAlmostEqual(sm.get_minimax_rms_anonymous(s1, s2)[0], 0)
def test_init(self): fitter = StructureFitter(self.b, self.a) self.assertTrue(fitter.mapping_op != None, "No fit found!") #Now to try with rotated structure op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, np.array([0, 0, 1])) editor = StructureEditor(self.a) editor.apply_operation(op) fitter = StructureFitter(self.b, editor.modified_structure) self.assertTrue(fitter.mapping_op != None, "No fit found!") #test with a supercell mod = SupercellMaker(self.a, scaling_matrix=[[2, 0, 0], [0, 1, 0], [0, 0, 1]]) a_super = mod.modified_structure fitter = StructureFitter(self.b, a_super) self.assertTrue(fitter.mapping_op != None, "No fit found!") # Test with a structure with a translated point editor = StructureEditor(self.a) site = self.a[0] editor.delete_site(0) trans = np.random.randint(0, 1000, 3) editor.insert_site(0, site.species_and_occu, site.frac_coords + trans, False, False) fitter = StructureFitter(self.b, editor.modified_structure) self.assertTrue(fitter.mapping_op != None, "No fit found for translation {}!".format(trans)) parser = CifParser(os.path.join(test_dir, "FePO4a.cif")) a = parser.get_structures()[0] parser = CifParser(os.path.join(test_dir, "FePO4b.cif")) b = parser.get_structures()[0] fitter = StructureFitter(b, a) self.assertTrue(fitter.mapping_op != None, "No fit found!")
def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) self.structure.apply_operation(op) self.assertArrayAlmostEqual( self.structure.lattice.matrix, [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000], [2.217138, -0.000000, 3.135509]], 5)
def test_fit(self): """ Take two known matched structures 1) Ensure match 2) Ensure match after translation and rotations 3) Ensure no-match after large site translation 4) Ensure match after site shuffling """ sm = StructureMatcher() self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) # Test rotational/translational invariance op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, np.array([0.4, 0.7, 0.9])) self.struct_list[1].apply_operation(op) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test failure under large atomic translation self.struct_list[1].translate_sites([0], [.4, .4, .2], frac_coords=True) self.assertFalse(sm.fit(self.struct_list[0], self.struct_list[1])) self.struct_list[1].translate_sites([0], [-.4, -.4, -.2], frac_coords=True) # random.shuffle(editor._sites) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test FrameworkComporator sm2 = StructureMatcher(comparator=FrameworkComparator()) lfp = self.get_structure("LiFePO4") nfp = self.get_structure("NaFePO4") self.assertTrue(sm2.fit(lfp, nfp)) self.assertFalse(sm.fit(lfp, nfp)) #Test anonymous fit. self.assertEqual(sm.fit_anonymous(lfp, nfp), True) self.assertAlmostEqual(sm.get_rms_anonymous(lfp, nfp)[0], 0.060895871160262717) #Test partial occupancies. s1 = Structure(Lattice.cubic(3), [{"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) s2 = Structure(Lattice.cubic(3), [{"Fe": 0.25}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.75}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertFalse(sm.fit(s1, s2)) self.assertFalse(sm.fit(s2, s1)) s2 = Structure(Lattice.cubic(3), [{"Mn": 0.5}, {"Mn": 0.5}, {"Mn": 0.5}, {"Mn": 0.5}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertEqual(sm.fit_anonymous(s1, s2), True) self.assertAlmostEqual(sm.get_rms_anonymous(s1, s2)[0], 0)
def __init__(self, axis, angle, angle_in_radians=False): """ """ self._axis = axis self._angle = angle self._angle_in_radians = angle_in_radians self._symmop = SymmOp.from_axis_angle_and_translation( self._axis, self._angle, self._angle_in_radians)
def __init__(self, axis, angle, angle_in_radians=False): """ """ self.axis = axis self.angle = angle self.angle_in_radians = angle_in_radians self._symmop = SymmOp.from_axis_angle_and_translation( self.axis, self.angle, self.angle_in_radians)
def test_list_based_functions(self): # zeroed tc = TensorCollection([1e-4 * Tensor(np.eye(3))] * 4) for t in tc.zeroed(): self.assertArrayEqual(t, np.zeros((3, 3))) for t in tc.zeroed(1e-5): self.assertArrayEqual(t, 1e-4 * np.eye(3)) self.list_based_function_check("zeroed", tc) self.list_based_function_check("zeroed", tc, tol=1e-5) # transform symm_op = SymmOp.from_axis_angle_and_translation( [0, 0, 1], 30, False, [0, 0, 1] ) self.list_based_function_check("transform", self.seq_tc, symm_op=symm_op) # symmetrized self.list_based_function_check("symmetrized", self.seq_tc) # rotation a = 3.14 * 42.5 / 180 rotation = SquareTensor( [[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]] ) self.list_based_function_check("rotate", self.diff_rank, matrix=rotation) # is_symmetric self.assertFalse(self.seq_tc.is_symmetric()) self.assertTrue(self.diff_rank.is_symmetric()) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # voigt self.list_based_function_check("voigt", self.diff_rank) # is_voigt_symmetric self.assertTrue(self.diff_rank.is_voigt_symmetric()) self.assertFalse(self.seq_tc.is_voigt_symmetric()) # Convert to ieee for entry in self.ieee_data[:2]: xtal = entry["xtal"] tc = TensorCollection([entry["original_tensor"]] * 3) struct = entry["structure"] self.list_based_function_check("convert_to_ieee", tc, struct) # from_voigt tc_input = [t for t in np.random.random((3, 6, 6))] tc = TensorCollection.from_voigt(tc_input) for t_input, t in zip(tc_input, tc): self.assertArrayAlmostEqual(Tensor.from_voigt(t_input), t)
def _check_R2_axes_asym(self): """ Test for 2-fold rotation along the principal axes. Used to handle asymetric top molecules. """ for v in self.principal_axes: op = SymmOp.from_axis_angle_and_translation(v, 180) if self.is_valid_op(op): self.symmops.append(op) self.rot_sym.append((v, 2))
def _check_R2_axes_asym(self): """ Test for 2-fold rotation along the principal axes. Used to handle asymetric top molecules. """ for v in self.principal_axes: op = SymmOp.from_axis_angle_and_translation(v, 180) if self.is_valid_op(op): self.symmops.append(op) self.rot_sym.append((v, 2))
def test_list_based_functions(self): # zeroed tc = TensorCollection([1e-4*Tensor(np.eye(3))]*4) for t in tc.zeroed(): self.assertArrayEqual(t, np.zeros((3, 3))) for t in tc.zeroed(1e-5): self.assertArrayEqual(t, 1e-4*np.eye(3)) self.list_based_function_check("zeroed", tc) self.list_based_function_check("zeroed", tc, tol=1e-5) # transform symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1]) self.list_based_function_check("transform", self.seq_tc, symm_op=symm_op) # symmetrized self.list_based_function_check("symmetrized", self.seq_tc) # rotation a = 3.14 * 42.5 / 180 rotation = SquareTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]]) self.list_based_function_check("rotate", self.diff_rank, matrix=rotation) # is_symmetric self.assertFalse(self.seq_tc.is_symmetric()) self.assertTrue(self.diff_rank.is_symmetric()) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # voigt self.list_based_function_check("voigt", self.diff_rank) # is_voigt_symmetric self.assertTrue(self.diff_rank.is_voigt_symmetric()) self.assertFalse(self.seq_tc.is_voigt_symmetric()) # Convert to ieee for entry in self.ieee_data[:2]: xtal = entry['xtal'] tc = TensorCollection([entry['original_tensor']]*3) struct = entry['structure'] self.list_based_function_check("convert_to_ieee", tc, struct) # from_voigt tc_input = [t for t in np.random.random((3, 6, 6))] tc = TensorCollection.from_voigt(tc_input) for t_input, t in zip(tc_input, tc): self.assertArrayAlmostEqual(Tensor.from_voigt(t_input), t)
def __init__(self, axis, angle, angle_in_radians=False): """ Args: axis (3x1 array): Axis of rotation, e.g., [1, 0, 0] angle (float): Angle to rotate angle_in_radians (bool): Set to True if angle is supplied in radians. Else degrees are assumed. """ self.axis = axis self.angle = angle self.angle_in_radians = angle_in_radians self._symmop = SymmOp.from_axis_angle_and_translation(self.axis, self.angle, self.angle_in_radians)
def test_transform(self): # Rank 3 tensor = Tensor(np.arange(0, 27).reshape(3, 3, 3)) symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1]) new_tensor = tensor.transform(symm_op) self.assertArrayAlmostEqual( new_tensor, [[[-0.871, -2.884, -1.928], [-2.152, -6.665, -4.196], [-1.026, -2.830, -1.572]], [[0.044, 1.531, 1.804], [4.263, 21.008, 17.928], [5.170, 23.026, 18.722]], [[1.679, 7.268, 5.821], [9.268, 38.321, 29.919], [8.285, 33.651, 26.000]]], 3)
def _find_spherical_axes(self): """ Looks for R5, R4, R3 and R2 axes in speherical top molecules. Point group T molecules have only one unique 3-fold and one unique 2-fold axis. O molecules have one unique 4, 3 and 2-fold axes. I molecules have a unique 5-fold axis. """ rot_present = defaultdict(bool) origin_site, dist_el_sites = cluster_sites(self.centered_mol, self.tol) test_set = min(dist_el_sites.values(), key=lambda s: len(s)) coords = [s.coords for s in test_set] for c1, c2, c3 in itertools.combinations(coords, 3): for cc1, cc2 in itertools.combinations([c1, c2, c3], 2): if not rot_present[2]: test_axis = cc1 + cc2 if np.linalg.norm(test_axis) > self.tol: op = SymmOp.from_axis_angle_and_translation(test_axis, 180) rot_present[2] = self.is_valid_op(op) if rot_present[2]: self.symmops.append(op) self.rot_sym.append((test_axis, 2)) test_axis = np.cross(c2 - c1, c3 - c1) if np.linalg.norm(test_axis) > self.tol: for r in (3, 4, 5): if not rot_present[r]: op = SymmOp.from_axis_angle_and_translation( test_axis, 360 / r) rot_present[r] = self.is_valid_op(op) if rot_present[r]: self.symmops.append(op) self.rot_sym.append((test_axis, r)) break if rot_present[2] and rot_present[3] and ( rot_present[4] or rot_present[5]): break
def _find_spherical_axes(self): """ Looks for R5, R4, R3 and R2 axes in speherical top molecules. Point group T molecules have only one unique 3-fold and one unique 2-fold axis. O molecules have one unique 4, 3 and 2-fold axes. I molecules have a unique 5-fold axis. """ rot_present = defaultdict(bool) origin_site, dist_el_sites = cluster_sites(self.centered_mol, self.tol) test_set = min(dist_el_sites.values(), key=lambda s: len(s)) coords = [s.coords for s in test_set] for c1, c2, c3 in itertools.combinations(coords, 3): for cc1, cc2 in itertools.combinations([c1, c2, c3], 2): if not rot_present[2]: test_axis = cc1 + cc2 if np.linalg.norm(test_axis) > self.tol: op = SymmOp.from_axis_angle_and_translation( test_axis, 180) rot_present[2] = self.is_valid_op(op) if rot_present[2]: self.symmops.append(op) self.rot_sym.append((test_axis, 2)) test_axis = np.cross(c2 - c1, c3 - c1) if np.linalg.norm(test_axis) > self.tol: for r in (3, 4, 5): if not rot_present[r]: op = SymmOp.from_axis_angle_and_translation( test_axis, 360 / r) rot_present[r] = self.is_valid_op(op) if rot_present[r]: self.symmops.append(op) self.rot_sym.append((test_axis, r)) break if rot_present[2] and rot_present[3] and (rot_present[4] or rot_present[5]): break
def _check_perpendicular_r2_axis(self, axis): """ Checks for R2 axes perpendicular to unique axis. For handling symmetric top molecules. """ min_set = self._get_smallest_set_not_on_axis(axis) for s1, s2 in itertools.combinations(min_set, 2): test_axis = np.cross(s1.coords - s2.coords, axis) if np.linalg.norm(test_axis) > self.tol: op = SymmOp.from_axis_angle_and_translation(test_axis, 180) r2present = self.is_valid_op(op) if r2present: self.symmops.append(op) self.rot_sym.append((test_axis, 2)) return True
def __init__(self, axis, angle, angle_in_radians=False): """ Args: axis: Axis of rotation, e.g., [1, 0, 0] angle: Angle to rotate angle_in_radians: Set to True if angle is supplied in radians. Else degrees are assumed. """ self._axis = axis self._angle = angle self._angle_in_radians = angle_in_radians self._symmop = SymmOp.from_axis_angle_and_translation(self._axis, self._angle, self._angle_in_radians)
def _check_perpendicular_r2_axis(self, axis): """ Checks for R2 axes perpendicular to unique axis. For handling symmetric top molecules. """ min_set = self._get_smallest_set_not_on_axis(axis) for s1, s2 in itertools.combinations(min_set, 2): test_axis = np.cross(s1.coords - s2.coords, axis) if np.linalg.norm(test_axis) > self.tol: op = SymmOp.from_axis_angle_and_translation(test_axis, 180) r2present = self.is_valid_op(op) if r2present: self.symmops.append(op) self.rot_sym.append((test_axis, 2)) return True
def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) s = self.structure.copy() s.apply_operation(op) self.assertArrayAlmostEqual( s.lattice.matrix, [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000], [2.217138, -0.000000, 3.135509]], 5) op = SymmOp([[1, 1, 0, 0.5], [1, 0, 0, 0.5], [0, 0, 1, 0.5], [0, 0, 0, 1]]) s = self.structure.copy() s.apply_operation(op, fractional=True) self.assertArrayAlmostEqual( s.lattice.matrix, [[5.760297, 3.325710, 0.000000], [3.840198, 0.000000, 0.000000], [0.000000, -2.217138, 3.135509]], 5)
def test_transform(self): # Rank 3 tensor = TensorBase(np.arange(0, 27).reshape(3, 3, 3)) symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1]) new_tensor = tensor.transform(symm_op) self.assertArrayAlmostEqual(new_tensor, [[[-0.871, -2.884, -1.928], [-2.152, -6.665, -4.196], [-1.026, -2.830, -1.572]], [[0.044, 1.531, 1.804], [4.263, 21.008, 17.928], [5.170, 23.026, 18.722]], [[1.679, 7.268, 5.821], [9.268, 38.321, 29.919], [8.285, 33.651, 26.000]]], 3)
def _check_rot_sym(self, axis): """ Determines the rotational symmetry about supplied axis. Used only for symmetric top molecules which has possible rotational symmetry operations > 2. """ min_set = self._get_smallest_set_not_on_axis(axis) max_sym = len(min_set) for i in range(max_sym, 0, -1): if max_sym % i != 0: continue op = SymmOp.from_axis_angle_and_translation(axis, 360 / i) rotvalid = self.is_valid_op(op) if rotvalid: self.symmops.append(op) self.rot_sym.append((axis, i)) return i return 1
def _check_rot_sym(self, axis): """ Determines the rotational symmetry about supplied axis. Used only for symmetric top molecules which has possible rotational symmetry operations > 2. """ min_set = self._get_smallest_set_not_on_axis(axis) max_sym = len(min_set) for i in range(max_sym, 0, -1): if max_sym % i != 0: continue op = SymmOp.from_axis_angle_and_translation(axis, 360 / i) rotvalid = self.is_valid_op(op) if rotvalid: self.symmops.append(op) self.rot_sym.append((axis, i)) return i return 1
def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) s = self.structure.copy() s.apply_operation(op) self.assertArrayAlmostEqual( s.lattice.matrix, [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000], [2.217138, -0.000000, 3.135509]], 5) op = SymmOp([[1, 1, 0, 0.5], [1, 0, 0, 0.5], [0, 0, 1, 0.5], [0, 0, 0, 1]]) s = self.structure.copy() s.apply_operation(op, fractional=True) self.assertArrayAlmostEqual( s.lattice.matrix, [[5.760297, 3.325710, 0.000000], [3.840198, 0.000000, 0.000000], [0.000000, -2.217138, 3.135509]], 5)
def cover_surface(self, site_indices): """ puts the ligand molecule on the given list of site indices """ num_atoms = len(self.ligand) normal = self.normal # get a vector that points from one atom in the botton plane # to one atom on the top plane. This is required to make sure # that the surface normal points outwards from the surface on # to which we want to adsorb the ligand vec_vac = self.cart_coords[self.top_atoms[0]] - \ self.cart_coords[self.bottom_atoms[0]] # mov_vec = the vector along which the ligand will be displaced mov_vec = normal * self.displacement angle = get_angle(vec_vac, self.normal) # flip the orientation of normal if it is not pointing in # the right direction. if (angle > 90): normal_frac = self.lattice.get_fractional_coords(normal) normal_frac[2] = -normal_frac[2] normal = self.lattice.get_cartesian_coords(normal_frac) mov_vec = normal * self.displacement # get the index corresponding to the given atomic species in # the ligand that will bond with the surface on which the # ligand will be adsorbed adatom_index = self.get_index(self.adatom_on_lig) adsorbed_ligands_coords = [] # set the ligand coordinates for each adsorption site on # the surface for sindex in site_indices: # align the ligand wrt the site on the surface to which # it will be adsorbed origin = self.cart_coords[sindex] self.ligand.translate_sites(list(range(num_atoms)), origin - self.ligand[ adatom_index].coords) # displace the ligand by the given amount in the direction # normal to surface self.ligand.translate_sites(list(range(num_atoms)), mov_vec) # vector pointing from the adatom_on_lig to the # ligand center of mass vec_adatom_cm = self.ligand.center_of_mass - \ self.ligand[adatom_index].coords # rotate the ligand with respect to a vector that is # normal to the vec_adatom_cm and the normal to the surface # so that the ligand center of mass is aligned along the # outward normal to the surface origin = self.ligand[adatom_index].coords angle = get_angle(vec_adatom_cm, normal) if 1 < abs(angle % 180) < 179: # For angles which are not 0 or 180, # perform a rotation about the origin along an axis # perpendicular to both bonds to align bonds. axis = np.cross(vec_adatom_cm, normal) op = SymmOp.from_origin_axis_angle(origin, axis, angle) self.ligand.apply_operation(op) elif abs(abs(angle) - 180) < 1: # We have a 180 degree angle. # Simply do an inversion about the origin for i in range(len(self.ligand)): self.ligand[i] = (self.ligand[i].species_and_occu, origin - ( self.ligand[i].coords - origin)) # x - y - shifts x = self.x_shift y = self.y_shift rot = self.rot if x: self.ligand.translate_sites(list(range(num_atoms)), np.array([x, 0, 0])) if y: self.ligand.translate_sites(list(range(num_atoms)), np.array([0, y, 0])) if rot: self.ligand.apply_operation( SymmOp.from_axis_angle_and_translation( (1, 0, 0), rot[0], angle_in_radians=False, translation_vec=(0, 0, 0))) self.ligand.apply_operation( SymmOp.from_axis_angle_and_translation( (0, 1, 0), rot[1], angle_in_radians=False, translation_vec=(0, 0, 0))) self.ligand.apply_operation( SymmOp.from_axis_angle_and_translation( (0, 0, 1), rot[2], angle_in_radians=False, translation_vec=(0, 0, 0))) # 3d numpy array adsorbed_ligands_coords.append(self.ligand.cart_coords) # extend the slab structure with the adsorbant atoms adsorbed_ligands_coords = np.array(adsorbed_ligands_coords) for j in range(len(site_indices)): [self.append(self.ligand.species_and_occu[i], adsorbed_ligands_coords[j, i, :], coords_are_cartesian=True) for i in range(num_atoms)]
def test_fit(self): """ Take two known matched structures 1) Ensure match 2) Ensure match after translation and rotations 3) Ensure no-match after large site translation 4) Ensure match after site shuffling """ sm = StructureMatcher() self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) # Test rotational/translational invariance op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, np.array([0.4, 0.7, 0.9])) self.struct_list[1].apply_operation(op) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test failure under large atomic translation self.struct_list[1].translate_sites([0], [.4, .4, .2], frac_coords=True) self.assertFalse(sm.fit(self.struct_list[0], self.struct_list[1])) self.struct_list[1].translate_sites([0], [-.4, -.4, -.2], frac_coords=True) # random.shuffle(editor._sites) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test FrameworkComporator sm2 = StructureMatcher(comparator=FrameworkComparator()) lfp = self.get_structure("LiFePO4") nfp = self.get_structure("NaFePO4") self.assertTrue(sm2.fit(lfp, nfp)) self.assertFalse(sm.fit(lfp, nfp)) #Test anonymous fit. self.assertEqual(sm.fit_anonymous(lfp, nfp), True) self.assertAlmostEqual( sm.get_rms_anonymous(lfp, nfp)[0], 0.060895871160262717) #Test partial occupancies. s1 = Structure(Lattice.cubic(3), [{ "Fe": 0.5 }, { "Fe": 0.5 }, { "Fe": 0.5 }, { "Fe": 0.5 }], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) s2 = Structure(Lattice.cubic(3), [{ "Fe": 0.25 }, { "Fe": 0.5 }, { "Fe": 0.5 }, { "Fe": 0.75 }], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertFalse(sm.fit(s1, s2)) self.assertFalse(sm.fit(s2, s1)) s2 = Structure(Lattice.cubic(3), [{ "Mn": 0.5 }, { "Mn": 0.5 }, { "Mn": 0.5 }, { "Mn": 0.5 }], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertEqual(sm.fit_anonymous(s1, s2), True) self.assertAlmostEqual(sm.get_rms_anonymous(s1, s2)[0], 0)
def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) self.mol.apply_operation(op) self.assertArrayAlmostEqual(self.mol[2].coords, [0.000000, 1.026719, -0.363000])
def setUp(self): self.op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1])
def setUp(self): self.op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1])
def coloumb_configured_interface(iface, random=True, translations= None, rotations=None, samples=10, lowest=5, ecut=None): """ Creates Ligand Slab interfaces of user specified translations and rotations away from the initial guess of binding site configuration, returns lowest energy structure according to Coulomb model Args: Interface: Interface object: initial interface object random: True for using Gaussian sampled random numbers for rotations and translations translations: list of [x,y,z] translations to be performed rotation: list of [a,b,c] rotations to be performed w.r.t Ligand axis samples: number of interfaces to create lowest: number of structures to return according to order of minimum energies Returns: list of lowest energy interface objects """ ifaces= [] transform= [] for i in range(samples): if random: x = np.random.normal() # shift along x direction y = np.random.normal() # shift along y direction z = np.random.normal() # shift aling z direction a= SymmOp.from_axis_angle_and_translation(axis=[1,0,0],\ angle=np.random.normal(0,180)) b= SymmOp.from_axis_angle_and_translation(axis=[0,1,0],\ angle=np.random.normal(0,180)) c= SymmOp.from_axis_angle_and_translation(axis=[0,0,1],\ angle=np.random.normal(0,180)) ligand=iface.ligand ligand.apply_operation(a) ligand.apply_operation(b) ligand.apply_operation(c) # check if created interface maintains the ligand adsorbed # over the surface for j in iface.top_atoms: if not iface.cart_coords[j][2] + iface.displacement > \ min(ligand.cart_coords[:,2]): transform.append(True) if all(transform): iface= Interface(iface.strt, hkl=iface.hkl, min_thick=iface.min_thick, min_vac=iface.min_vac, supercell=iface.supercell, surface_coverage=iface.surface_coverage, ligand=iface.ligand, displacement=z, adatom_on_lig=iface.adatom_on_lig, adsorb_on_species=iface.adsorb_on_species, primitive= False, from_ase=True, x_shift=x, y_shift=y) iface.create_interface() energy= iface.calc_energy() iface.sort() if energy<ecut: ifaces.append((energy,iface)) # ifaces.zip(energy, iface) return ifaces
def get_grain_boundary_interface(structure=None, hkl_pair= {'hkl': [[1,0,0],[1,1,0]],\ 'thickness':[10,10]}, twist = 0, tilt = 0, separation=0): """ Args: structure: pymatgen structure to create grain boundary in hkl_pair: dict of {'hkl':thickness} twist: twist in degrees tilt: tilt in degrees """ structure = get_struct_from_mp(structure, MAPI_KEY="") sa = SpacegroupAnalyzer(structure) structure_conventional = sa.get_conventional_standard_structure() structure = structure_conventional.copy() structure.sort() #creation of lower part of grain boundary lower= Interface(structure,\ hkl = hkl_pair['hkl'][0], min_thick = hkl_pair['thickness'][0], min_vac = separation+hkl_pair['thickness'][1], primitive = False, from_ase = True, center_slab=False) lower.to(fmt="poscar", filename="POSCAR_lower.vasp") #creation of upper part of grain boundary upper= Interface(structure,\ hkl = hkl_pair['hkl'][1], min_thick = hkl_pair['thickness'][1], min_vac = 0, primitive = False, from_ase = True) #find top atoms reference of lower part of gb substrate_top_z = np.max(np.array([site.coords for site in lower])[:, 2]) # define twist and tilt vectors twist_shift_normal = lower.lattice.matrix[2,:]/\ np.linalg.norm(lower.lattice.matrix[2,:]) tilt_normal = upper.lattice.matrix[1,:]/\ np.linalg.norm(upper.lattice.matrix[2,:]) #define twist operation SymmOp object twist_op = SymmOp.from_axis_angle_and_translation(axis= twist_shift_normal,\ angle=twist, angle_in_radians=False,translation_vec=(0, 0, 0)) #define tilt operation SymmOp object tilt_op = SymmOp.from_axis_angle_and_translation(axis= tilt_normal,\ angle=tilt, angle_in_radians=False,translation_vec=(0, 0, 0)) upper.apply_operation(twist_op) upper.to(fmt="poscar", filename="POSCAR_upper.vasp") upper.apply_operation(tilt_op) #define shift separation along twist vector normal to upper plane shift = -1 * twist_shift_normal / np.linalg.norm( twist_shift_normal) * separation #define origin to shift w.r.t top of the lower grain origin = np.array([0, 0, substrate_top_z]) #shift sites in upper for site in upper: new_coords = site.coords - origin + shift lower.append(site.specie, new_coords, coords_are_cartesian=True) return lower
def cover_surface(self, site_indices): """ puts the ligand molecule on the given list of site indices """ num_atoms = len(self.ligand) normal = self.normal # get a vector that points from one atom in the botton plane # to one atom on the top plane. This is required to make sure # that the surface normal points outwards from the surface on # to which we want to adsorb the ligand vec_vac = self.cart_coords[self.top_atoms[0]] - \ self.cart_coords[self.bottom_atoms[0]] # mov_vec = the vector along which the ligand will be displaced mov_vec = normal * self.displacement angle = get_angle(vec_vac, self.normal) # flip the orientation of normal if it is not pointing in # the right direction. if (angle > 90): normal_frac = self.lattice.get_fractional_coords(normal) normal_frac[2] = -normal_frac[2] normal = self.lattice.get_cartesian_coords(normal_frac) mov_vec = normal * self.displacement # get the index corresponding to the given atomic species in # the ligand that will bond with the surface on which the # ligand will be adsorbed adatom_index = self.get_index(self.adatom_on_lig) adsorbed_ligands_coords = [] # set the ligand coordinates for each adsorption site on # the surface for sindex in site_indices: # align the ligand wrt the site on the surface to which # it will be adsorbed origin = self.cart_coords[sindex] self.ligand.translate_sites( list(range(num_atoms)), origin - self.ligand[adatom_index].coords) # displace the ligand by the given amount in the direction # normal to surface self.ligand.translate_sites(list(range(num_atoms)), mov_vec) # vector pointing from the adatom_on_lig to the # ligand center of mass vec_adatom_cm = self.ligand.center_of_mass - \ self.ligand[adatom_index].coords # rotate the ligand with respect to a vector that is # normal to the vec_adatom_cm and the normal to the surface # so that the ligand center of mass is aligned along the # outward normal to the surface origin = self.ligand[adatom_index].coords angle = get_angle(vec_adatom_cm, normal) if 1 < abs(angle % 180) < 179: # For angles which are not 0 or 180, # perform a rotation about the origin along an axis # perpendicular to both bonds to align bonds. axis = np.cross(vec_adatom_cm, normal) op = SymmOp.from_origin_axis_angle(origin, axis, angle) self.ligand.apply_operation(op) elif abs(abs(angle) - 180) < 1: # We have a 180 degree angle. # Simply do an inversion about the origin for i in range(len(self.ligand)): self.ligand[i] = (self.ligand[i].species_and_occu, origin - (self.ligand[i].coords - origin)) # x - y - shifts x = self.x_shift y = self.y_shift rot = self.rot if x: self.ligand.translate_sites(list(range(num_atoms)), np.array([x, 0, 0])) if y: self.ligand.translate_sites(list(range(num_atoms)), np.array([0, y, 0])) if rot: self.ligand.apply_operation( SymmOp.from_axis_angle_and_translation( (1, 0, 0), rot[0], angle_in_radians=False, translation_vec=(0, 0, 0))) self.ligand.apply_operation( SymmOp.from_axis_angle_and_translation( (0, 1, 0), rot[1], angle_in_radians=False, translation_vec=(0, 0, 0))) self.ligand.apply_operation( SymmOp.from_axis_angle_and_translation( (0, 0, 1), rot[2], angle_in_radians=False, translation_vec=(0, 0, 0))) # 3d numpy array adsorbed_ligands_coords.append(self.ligand.cart_coords) # extend the slab structure with the adsorbant atoms adsorbed_ligands_coords = np.array(adsorbed_ligands_coords) for j in range(len(site_indices)): [ self.append(self.ligand.species_and_occu[i], adsorbed_ligands_coords[j, i, :], coords_are_cartesian=True) for i in range(num_atoms) ]
def coloumb_configured_interface(iface, random=True, translations=None, rotations=None, samples=10, lowest=5, ecut=None): """ Creates Ligand Slab interfaces of user specified translations and rotations away from the initial guess of binding site configuration, returns lowest energy structure according to Coulomb model Args: Interface: Interface object: initial interface object random: True for using Gaussian sampled random numbers for rotations and translations translations: list of [x,y,z] translations to be performed rotation: list of [a,b,c] rotations to be performed w.r.t Ligand axis samples: number of interfaces to create lowest: number of structures to return according to order of minimum energies Returns: list of lowest energy interface objects """ ifaces = [] transform = [] for i in range(samples): if random: x = np.random.normal() # shift along x direction y = np.random.normal() # shift along y direction z = np.random.normal() # shift aling z direction a = SymmOp.from_axis_angle_and_translation(axis=[1, 0, 0], \ angle=np.random.normal(0, 180)) b = SymmOp.from_axis_angle_and_translation(axis=[0, 1, 0], \ angle=np.random.normal(0, 180)) c = SymmOp.from_axis_angle_and_translation(axis=[0, 0, 1], \ angle=np.random.normal(0, 180)) ligand = iface.ligand ligand.apply_operation(a) ligand.apply_operation(b) ligand.apply_operation(c) # check if created interface maintains the ligand adsorbed # over the surface for j in iface.top_atoms: if not iface.cart_coords[j][2] + iface.displacement > \ min(ligand.cart_coords[:, 2]): transform.append(True) if all(transform): iface = Interface(iface.strt, hkl=iface.hkl, min_thick=iface.min_thick, min_vac=iface.min_vac, supercell=iface.supercell, surface_coverage=iface.surface_coverage, ligand=iface.ligand, displacement=z, adatom_on_lig=iface.adatom_on_lig, adsorb_on_species=iface.adsorb_on_species, primitive=False, from_ase=True, x_shift=x, y_shift=y) iface.create_interface() energy = iface.calc_energy() iface.sort() if energy < ecut: ifaces.append((energy, iface)) # ifaces.zip(energy, iface) return ifaces
def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) self.mol.apply_operation(op) self.assertArrayAlmostEqual(self.mol[2].coords, [0.000000, 1.026719, -0.363000])
def get_grain_boundary_interface(structure=None, hkl_pair= {'hkl': [[1,0,0],[1,1,0]],\ 'thickness':[10,10]}, twist = 0, tilt = 0, separation=0): """ Args: structure: pymatgen structure to create grain boundary in hkl_pair: dict of {'hkl':thickness} twist: twist in degrees tilt: tilt in degrees """ structure = get_struct_from_mp(structure, MAPI_KEY="") sa = SpacegroupAnalyzer(structure) structure_conventional = sa.get_conventional_standard_structure() structure = structure_conventional.copy() structure.sort() #creation of lower part of grain boundary lower= Interface(structure,\ hkl = hkl_pair['hkl'][0], min_thick = hkl_pair['thickness'][0], min_vac = separation+hkl_pair['thickness'][1], primitive = False, from_ase = True, center_slab=False) lower.to(fmt="poscar", filename="POSCAR_lower.vasp") #creation of upper part of grain boundary upper= Interface(structure,\ hkl = hkl_pair['hkl'][1], min_thick = hkl_pair['thickness'][1], min_vac = 0, primitive = False, from_ase = True) #find top atoms reference of lower part of gb substrate_top_z = np.max(np.array([site.coords for site in lower])[:,2]) # define twist and tilt vectors twist_shift_normal = lower.lattice.matrix[2,:]/\ np.linalg.norm(lower.lattice.matrix[2,:]) tilt_normal = upper.lattice.matrix[1,:]/\ np.linalg.norm(upper.lattice.matrix[2,:]) #define twist operation SymmOp object twist_op = SymmOp.from_axis_angle_and_translation(axis= twist_shift_normal,\ angle=twist, angle_in_radians=False,translation_vec=(0, 0, 0)) #define tilt operation SymmOp object tilt_op = SymmOp.from_axis_angle_and_translation(axis= tilt_normal,\ angle=tilt, angle_in_radians=False,translation_vec=(0, 0, 0)) upper.apply_operation(twist_op) upper.to(fmt="poscar", filename="POSCAR_upper.vasp") upper.apply_operation(tilt_op) #define shift separation along twist vector normal to upper plane shift = -1*twist_shift_normal/np.linalg.norm(twist_shift_normal) * separation #define origin to shift w.r.t top of the lower grain origin = np.array([0,0, substrate_top_z]) #shift sites in upper for site in upper: new_coords = site.coords - origin + shift lower.append(site.specie, new_coords, coords_are_cartesian=True) return lower