def test_find_all_mappings(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, -1, 3], 40) rot = op.rotation_matrix scale = np.array([[0, 2, 0], [1, 1, 0], [0,0,1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) for (aligned_out, rot_out, scale_out) in latt.find_all_mappings(latt2): self.assertArrayAlmostEqual(np.inner(latt2.matrix, rot_out), aligned_out.matrix, 5) self.assertArrayAlmostEqual(np.dot(scale_out, latt.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.lengths_and_angles, latt2.lengths_and_angles) self.assertFalse(np.allclose(aligned_out.lengths_and_angles, latt.lengths_and_angles)) latt = Lattice.orthorhombic(9, 9, 5) self.assertEqual(len(list(latt.find_all_mappings(latt))), 16) #catch the singular matrix error latt = Lattice.from_lengths_and_angles([1,1,1], [10,10,10]) for l, _, _ in latt.find_all_mappings(latt, ltol=0.05, atol=11): self.assertTrue(isinstance(l, Lattice))
def fit_with_mapper(self, mapper): coords = [[0.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.089000], [1.026719, 0.000000, -0.363000], [-0.513360, -0.889165, -0.363000], [-0.513360, 0.889165, -0.363000]] mol1 = Molecule(["C", "H", "H", "H", "H"], coords) op = SymmOp.from_origin_axis_angle([0, 0, 0], [0.1, 0.2, 0.3], 60) rotcoords = [op.operate(c) for c in coords] mol2 = Molecule(["C", "H", "H", "H", "H"], rotcoords) mm = MoleculeMatcher(mapper=mapper) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "benzene2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "t2.xyz")) self.assertFalse(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "c1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "c2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "j1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "j2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "ethene1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "ethene2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "toluene1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "toluene2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "cyclohexane1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "cyclohexane2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "oxygen1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "oxygen2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mm = MoleculeMatcher(tolerance=0.001, mapper=mapper) mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz")) self.assertFalse(mm.fit(mol1, mol2))
def test_find_mapping(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, 3, 3], 35) rot = op.rotation_matrix scale = np.array([[1, 1, 0], [0, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) (latt, rot, scale2) = latt2.find_mapping(latt) self.assertAlmostEqual(abs(np.linalg.det(rot)), 1) self.assertTrue(np.allclose(scale2, scale) or np.allclose(scale2, -scale))
def _align_monomer(self, monomer, mon_vector, move_direction): """ rotate the monomer so that it is aligned along the move direction Args: monomer (Molecule) mon_vector (numpy.array): molecule vector that starts from the start atom index to the end atom index move_direction (numpy.array): the direction of the polymer chain extension """ axis = np.cross(mon_vector, move_direction) origin = monomer[self.start].coords angle = get_angle(mon_vector, move_direction) op = SymmOp.from_origin_axis_angle(origin, axis, angle) monomer.apply_operation(op)
def test_find_mapping(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, 3, 3], 35) rot = op.rotation_matrix scale = np.array([[1, 1, 0], [0, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) (aligned_out, rot_out, scale_out) = latt2.find_mapping(latt) self.assertAlmostEqual(abs(np.linalg.det(rot)), 1) rotated = SymmOp.from_rotation_and_translation(rot_out).operate_multi(latt.matrix) self.assertArrayAlmostEqual(rotated, aligned_out.matrix) self.assertArrayAlmostEqual(np.dot(scale_out, latt2.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.lengths_and_angles, latt.lengths_and_angles) self.assertFalse(np.allclose(aligned_out.lengths_and_angles, latt2.lengths_and_angles))
def test_rotated_molecule(self): coords = [ [0.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.089000], [1.026719, 0.000000, -0.363000], [-0.513360, -0.889165, -0.363000], [-0.513360, 0.889165, -0.363000], ] op = SymmOp.from_origin_axis_angle([0, 0, 0], [0.1, 0.2, 0.3], 60) rotcoords = [op.operate(c) for c in coords] mol1 = Molecule(["C", "H", "H", "H", "H"], coords) mol2 = Molecule(["C", "H", "H", "H", "H"], rotcoords) mm = GeneticOrderMatcher(mol1, threshold=0.3) _, rmsd = mm.fit(mol2)[0] self.assertAlmostEqual(rmsd, 0.0, places=6)
def test_find_mapping(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, 3, 3], 35) rot = op.rotation_matrix scale = np.array([[1, 1, 0], [0, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) (aligned_out, rot_out, scale_out) = latt2.find_mapping(latt) self.assertAlmostEqual(abs(np.linalg.det(rot)), 1) rotated = SymmOp.from_rotation_and_translation(rot_out).operate_multi(latt.matrix) self.assertArrayAlmostEqual(rotated, aligned_out.matrix) self.assertArrayAlmostEqual(np.dot(scale_out, latt2.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.lengths_and_angles, latt.lengths_and_angles) self.assertFalse(np.allclose(aligned_out.lengths_and_angles, latt2.lengths_and_angles))
def test_find_all_mappings(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, -1, 3], 40) rot = op.rotation_matrix scale = np.array([[0, 2, 0], [1, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) for (aligned_out, rot_out, scale_out) in latt.find_all_mappings(latt2): self.assertArrayAlmostEqual(np.inner(latt2.matrix, rot_out), aligned_out.matrix, 5) self.assertArrayAlmostEqual(np.dot(scale_out, latt.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.parameters, latt2.parameters) self.assertFalse(np.allclose(aligned_out.parameters, latt.parameters)) latt = Lattice.orthorhombic(9, 9, 5) self.assertEqual(len(list(latt.find_all_mappings(latt))), 16) # catch the singular matrix error latt = Lattice.from_parameters(1, 1, 1, 10, 10, 10) for l, _, _ in latt.find_all_mappings(latt, ltol=0.05, atol=11): self.assertTrue(isinstance(l, Lattice))
def test_find_all_mappings(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, -1, 3], 40) rot = op.rotation_matrix scale = np.array([[0, 2, 0], [1, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) for (aligned_out, rot_out, scale_out) in latt.find_all_mappings(latt2): self.assertArrayAlmostEqual(np.inner(latt2.matrix, rot_out), aligned_out.matrix) self.assertArrayAlmostEqual(np.dot(scale_out, latt.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.lengths_and_angles, latt2.lengths_and_angles) self.assertFalse( np.allclose(aligned_out.lengths_and_angles, latt.lengths_and_angles)) latt = Lattice.orthorhombic(9, 9, 5) self.assertEqual(len(list(latt.find_all_mappings(latt))), 16)
def rotate_mols(self): """ rotate the molecules wrt each other using the provided info """ # rotate the molecules around an axis that is # perpendicular to the molecular axes if self.angle: for mol in range(len(self.mols)): for ind_key, rot in self.angle[str(mol)].items(): perp_vec = np.cross(self.mol_vecs[int(ind_key)], self.mol_vecs[mol]) # if the vectors are parllel, # then perp_vec = (-y, x, 0) if np.abs(np.dot(self.mol_vecs[int(ind_key)], self.mol_vecs[mol]) - \ np.linalg.norm(self.mol_vecs[mol]) ** 2) < 1e-6: perp_vec = np.array([-self.mol_vecs[mol][1], self.mol_vecs[mol][0], 0]) org_pt = self.vec_indices[mol][0] op = SymmOp.from_origin_axis_angle( self.mols[mol].cart_coords[org_pt], axis=perp_vec, angle=rot) self.mols[mol].apply_operation(op)
def rotate_mols(self): """ rotate the molecules wrt each other using the provided info """ # rotate the molecules around an axis that is # perpendicular to the molecular axes if self.angle: for mol in range(len(self.mols)): for ind_key, rot in self.angle[str(mol)].items(): perp_vec = np.cross(self.mol_vecs[int(ind_key)], self.mol_vecs[mol]) # if the vectors are parllel, # then perp_vec = (-y, x, 0) if np.abs(np.dot(self.mol_vecs[int(ind_key)], self.mol_vecs[mol]) - \ np.linalg.norm(self.mol_vecs[mol]) ** 2) < 1e-6: perp_vec = np.array([-self.mol_vecs[mol][1], self.mol_vecs[mol][0], 0]) org_pt = self.vec_indices[mol][0] op = SymmOp.from_origin_axis_angle( self.mols[mol].cart_coords[org_pt], axis=perp_vec, angle=rot) self.mols[mol].apply_operation(op)
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 parse_coords(coord_lines): """ Helper method to parse coordinates. """ paras = {} var_pattern = re.compile("^([A-Za-z]+\S*)[\s=,]+([\d\-\.]+)$") for l in coord_lines: m = var_pattern.match(l.strip()) if m: paras[m.group(1)] = float(m.group(2)) species = [] coords = [] # Stores whether a Zmatrix format is detected. Once a zmatrix format # is detected, it is assumed for the remaining of the parsing. zmode = False for l in coord_lines: l = l.strip() if not l: break if (not zmode) and GaussianInput.xyz_patt.match(l): m = GaussianInput.xyz_patt.match(l) species.append(m.group(1)) toks = re.split("[,\s]+", l.strip()) if len(toks) > 4: coords.append([float(i) for i in toks[2:5]]) else: coords.append([float(i) for i in toks[1:4]]) elif GaussianInput.zmat_patt.match(l): zmode = True toks = re.split("[,\s]+", l.strip()) species.append(toks[0]) toks.pop(0) if len(toks) == 0: coords.append(np.array([0, 0, 0])) else: nn = [] parameters = [] while len(toks) > 1: ind = toks.pop(0) data = toks.pop(0) try: nn.append(int(ind)) except ValueError: nn.append(species.index(ind) + 1) try: val = float(data) parameters.append(val) except ValueError: if data.startswith("-"): parameters.append(-paras[data[1:]]) else: parameters.append(paras[data]) if len(nn) == 1: coords.append(np.array([0, 0, parameters[0]])) elif len(nn) == 2: coords1 = coords[nn[0] - 1] coords2 = coords[nn[1] - 1] bl = parameters[0] angle = parameters[1] axis = [0, 1, 0] op = SymmOp.from_origin_axis_angle( coords1, axis, angle, False) coord = op.operate(coords2) vec = coord - coords1 coord = vec * bl / np.linalg.norm(vec) + coords1 coords.append(coord) elif len(nn) == 3: coords1 = coords[nn[0] - 1] coords2 = coords[nn[1] - 1] coords3 = coords[nn[2] - 1] bl = parameters[0] angle = parameters[1] dih = parameters[2] v1 = coords3 - coords2 v2 = coords1 - coords2 axis = np.cross(v1, v2) op = SymmOp.from_origin_axis_angle( coords1, axis, angle, False) coord = op.operate(coords2) v1 = coord - coords1 v2 = coords1 - coords2 v3 = np.cross(v1, v2) adj = get_angle(v3, axis) axis = coords1 - coords2 op = SymmOp.from_origin_axis_angle( coords1, axis, dih - adj, False) coord = op.operate(coord) vec = coord - coords1 coord = vec * bl / np.linalg.norm(vec) + coords1 coords.append(coord) def parse_species(sp_str): """ The species specification can take many forms. E.g., simple integers representing atomic numbers ("8"), actual species string ("C") or a labelled species ("C1"). Sometimes, the species string is also not properly capitalized, e.g, ("c1"). This method should take care of these known formats. """ try: return int(sp_str) except ValueError: sp = re.sub("\d", "", sp_str) return sp.capitalize() species = [parse_species(sp) for sp in species] return Molecule(species, coords)
def fit_with_mapper(self, mapper): coords = [[0.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.089000], [1.026719, 0.000000, -0.363000], [-0.513360, -0.889165, -0.363000], [-0.513360, 0.889165, -0.363000]] mol1 = Molecule(["C", "H", "H", "H", "H"], coords) op = SymmOp.from_origin_axis_angle([0, 0, 0], [0.1, 0.2, 0.3], 60) rotcoords = [op.operate(c) for c in coords] mol2 = Molecule(["C", "H", "H", "H", "H"], rotcoords) mm = MoleculeMatcher(mapper=mapper) self.assertTrue(mm.fit(mol1, mol2)) mol1 = BabelMolAdaptor.from_file(os.path.join( test_dir, "benzene1.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file(os.path.join( test_dir, "benzene2.xyz")).pymatgen_mol self.assertTrue(mm.fit(mol1, mol2)) mol1 = BabelMolAdaptor.from_file(os.path.join( test_dir, "benzene1.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file(os.path.join(test_dir, "t2.xyz")).pymatgen_mol self.assertFalse(mm.fit(mol1, mol2)) mol1 = BabelMolAdaptor.from_file(os.path.join(test_dir, "c1.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file(os.path.join(test_dir, "c2.xyz")).pymatgen_mol self.assertTrue(mm.fit(mol1, mol2)) mol1 = BabelMolAdaptor.from_file(os.path.join(test_dir, "t3.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file(os.path.join(test_dir, "t4.xyz")).pymatgen_mol self.assertTrue(mm.fit(mol1, mol2)) mol1 = BabelMolAdaptor.from_file(os.path.join(test_dir, "j1.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file(os.path.join(test_dir, "j2.xyz")).pymatgen_mol self.assertTrue(mm.fit(mol1, mol2)) mol1 = BabelMolAdaptor.from_file(os.path.join( test_dir, "ethene1.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file(os.path.join( test_dir, "ethene2.xyz")).pymatgen_mol self.assertTrue(mm.fit(mol1, mol2)) mol1 = BabelMolAdaptor.from_file(os.path.join( test_dir, "toluene1.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file(os.path.join( test_dir, "toluene2.xyz")).pymatgen_mol self.assertTrue(mm.fit(mol1, mol2)) mol1 = BabelMolAdaptor.from_file( os.path.join(test_dir, "cyclohexane1.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file( os.path.join(test_dir, "cyclohexane2.xyz")).pymatgen_mol self.assertTrue(mm.fit(mol1, mol2)) mol1 = BabelMolAdaptor.from_file(os.path.join( test_dir, "oxygen1.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file(os.path.join( test_dir, "oxygen2.xyz")).pymatgen_mol self.assertTrue(mm.fit(mol1, mol2)) mm = MoleculeMatcher(tolerance=0.001, mapper=mapper) mol1 = BabelMolAdaptor.from_file(os.path.join(test_dir, "t3.xyz")).pymatgen_mol mol2 = BabelMolAdaptor.from_file(os.path.join(test_dir, "t4.xyz")).pymatgen_mol self.assertFalse(mm.fit(mol1, mol2))
def parse_coords(coord_lines): """ Helper method to parse coordinates. """ paras = {} var_pattern = re.compile("^([A-Za-z]+\S*)[\s=,]+([\d\-\.]+)$") for l in coord_lines: m = var_pattern.match(l.strip()) if m: paras[m.group(1)] = float(m.group(2)) species = [] coords = [] # Stores whether a Zmatrix format is detected. Once a zmatrix format # is detected, it is assumed for the remaining of the parsing. zmode = False for l in coord_lines: l = l.strip() if not l: break if (not zmode) and GaussianInput.xyz_patt.match(l): m = GaussianInput.xyz_patt.match(l) species.append(m.group(1)) toks = re.split("[,\s]+", l.strip()) if len(toks) > 4: coords.append(map(float, toks[2:5])) else: coords.append(map(float, toks[1:4])) elif GaussianInput.zmat_patt.match(l): zmode = True toks = re.split("[,\s]+", l.strip()) species.append(toks[0]) toks.pop(0) if len(toks) == 0: coords.append(np.array([0, 0, 0])) else: nn = [] parameters = [] while len(toks) > 1: ind = toks.pop(0) data = toks.pop(0) try: nn.append(int(ind)) except ValueError: nn.append(species.index(ind) + 1) try: val = float(data) parameters.append(val) except ValueError: if data.startswith("-"): parameters.append(-paras[data[1:]]) else: parameters.append(paras[data]) if len(nn) == 1: coords.append(np.array([0, 0, parameters[0]])) elif len(nn) == 2: coords1 = coords[nn[0] - 1] coords2 = coords[nn[1] - 1] bl = parameters[0] angle = parameters[1] axis = [0, 1, 0] op = SymmOp.from_origin_axis_angle(coords1, axis, angle, False) coord = op.operate(coords2) vec = coord - coords1 coord = vec * bl / np.linalg.norm(vec) + coords1 coords.append(coord) elif len(nn) == 3: coords1 = coords[nn[0] - 1] coords2 = coords[nn[1] - 1] coords3 = coords[nn[2] - 1] bl = parameters[0] angle = parameters[1] dih = parameters[2] v1 = coords3 - coords2 v2 = coords1 - coords2 axis = np.cross(v1, v2) op = SymmOp.from_origin_axis_angle(coords1, axis, angle, False) coord = op.operate(coords2) v1 = coord - coords1 v2 = coords1 - coords2 v3 = np.cross(v1, v2) adj = get_angle(v3, axis) axis = coords1 - coords2 op = SymmOp.from_origin_axis_angle(coords1, axis, dih - adj, False) coord = op.operate(coord) vec = coord - coords1 coord = vec * bl / np.linalg.norm(vec) + coords1 coords.append(coord) def parse_species(sp_str): """ The species specification can take many forms. E.g., simple integers representing atomic numbers ("8"), actual species string ("C") or a labelled species ("C1"). Sometimes, the species string is also not properly capitalized, e.g, ("c1"). This method should take care of these known formats. """ try: return int(sp_str) except ValueError: sp = re.sub("\d", "", sp_str) return sp.capitalize() species = map(parse_species, species) return Molecule(species, coords)
def add_water_monolayer(self, distance=2.85): """ Function that finds the number of water molecules necessary to create a monolayer on the slab surface and then orients these essentially equally spaced at distance from the surface O is always facing outward for easier convergence. This may lead to some inaccurate early guesses of structure though. At the end a random rotation is applied to make the water at least somewhat random Obviously, a relax or MD run is necessary to get equilibrium positions. author: Quinn Campbell [email protected] """ slab = self.slab water = Molecule( "HHO", [[-0.7598, 0.0, -0.5841], [0.7598, 0.0, -0.5841], [0, 0, 0]]) min_z = 1000.0 max_z = -10.0 water_distance = distance for site in slab: coord = site.coords if coord[2] < min_z: min_z = coord[2] if coord[2] > max_z: max_z = coord[2] a = slab.lattice.matrix[0] b = slab.lattice.matrix[1] norm_a = np.linalg.norm(a) norm_b = np.linalg.norm(b) site = a * random.random() + b * random.random() + [ 0.0, 0.0, max_z + water_distance ] sop = SymmOp.from_origin_axis_angle(origin=[0, 0, 0], axis=[1, 1, 1], angle=random.random() * 140.0 - 70.0) wat = water.copy() wat.apply_operation(sop) ads_structure = self.add_adsorbate(wat, site) wat_mono_density = 7.5 # in units of angstrom squared. Still up for finding exact number wat_mol_a = int(round(norm_a / math.sqrt(wat_mono_density))) wat_mol_b = int(round(norm_b / math.sqrt(wat_mono_density))) for i in range(wat_mol_a): for j in range(wat_mol_b): if i == 0 and j == 0: pass else: if i % 2 == 1: new_coord = site + float(i) / float(wat_mol_a) * a + ( float(j) / float(wat_mol_b) + 1.0 / (float(wat_mol_b) * 2.0)) * b else: new_coord = site + float(i) / float( wat_mol_a) * a + float(j) / float(wat_mol_b) * b sop = SymmOp.from_origin_axis_angle( origin=[0, 0, 0], axis=[1, 1, 1], angle=random.random() * 140.0 - 70.0) wat = water.copy() wat.apply_operation(sop) ads_structure = asf.add_adsorbate(wat, new_coord) asf = AdsorbateSiteFinder(ads_structure) ads_structure = asf.mirror_adsorbates() return ads_structure
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 parse_coords(coord_lines): """ Helper method to parse coordinates. """ paras = {} var_pattern = re.compile("^\s*([A-Za-z]+\S*)[\s=,]+([\d\-\.]+)\s*$") for l in coord_lines: m = var_pattern.match(l.strip()) if m: paras[m.group(1)] = float(m.group(2)) zmat_patt = re.compile("^\s*([A-Za-z]+)[\w\d\-\_]*([\s,]+(\w+)[\s,]+(\w+))*[\-\.\s,\w]*$") mixed_species_patt = re.compile("([A-Za-z]+)[\d\-\_]+") xyz_patt = re.compile("^\s*([A-Za-z]+[\w\d\-\_]*)\s+([\d\.eE\-]+)\s+([\d\.eE\-]+)\s+([\d\.eE\-]+)[\-\.\s,\w.]*$") parsed_species = [] species = [] coords = [] for l in coord_lines: l = l.strip() if l == "": break if xyz_patt.match(l): m = xyz_patt.match(l) m2 = mixed_species_patt.match(m.group(1)) if m2: parsed_species.append(m.group(1)) species.append(m2.group(1)) else: species.append(m.group(1)) toks = re.split("[,\s]+", l.strip()) if len(toks) > 4: coords.append(map(float, toks[2:5])) else: coords.append(map(float, toks[1:4])) elif zmat_patt.match(l): toks = re.split("[,\s]+", l.strip()) m = mixed_species_patt.match(toks[0]) if m: parsed_species.append(toks[0]) species.append(m.group(1)) else: species.append(toks[0]) toks.pop(0) if len(toks) == 0: coords.append(np.array([0, 0, 0])) else: nn = [] parameters = [] while len(toks) > 0: ind = toks.pop(0) data = toks.pop(0) try: int(ind) nn.append(int(ind)) except: nn.append(parsed_species.index(ind)) parameters.append(paras[data]) if len(nn) == 1: coords.append(np.array([0, 0, parameters[0]])) elif len(nn) == 2: coords1 = coords[nn[0] - 1] coords2 = coords[nn[1] - 1] bl = parameters[0] angle = parameters[1] axis = [0, 1, 0] coord = SymmOp.from_origin_axis_angle(coords1, axis, angle, False).operate(coords2) vec = coord - coords1 coord = vec * bl / np.linalg.norm(vec) + coords1 coords.append(coord) elif len(nn) == 3: coords1 = coords[nn[0] - 1] coords2 = coords[nn[1] - 1] coords3 = coords[nn[2] - 1] bl = parameters[0] angle = parameters[1] dih = parameters[2] v1 = coords3 - coords2 v2 = coords1 - coords2 axis = np.cross(v1, v2) coord = SymmOp.from_origin_axis_angle(coords1, axis, angle, False).operate(coords2) v1 = coord - coords1 v2 = coords1 - coords2 v3 = np.cross(v1, v2) d = np.dot(v3, axis) / np.linalg.norm(v3) / np.linalg.norm(axis) if d > 1: d = 1 elif d < -1: d = -1 adj = math.acos(d) * 180 / math.pi axis = coords1 - coords2 coord = SymmOp.from_origin_axis_angle(coords1, axis, dih - adj, False).operate(coord) vec = coord - coords1 coord = vec * bl / np.linalg.norm(vec) + coords1 coords.append(coord) return Molecule(species, coords)