def _get_azimuth_elev(self, miller_index): """ Args: miller_index: viewing direction Returns: azim, elev for plotting """ if miller_index in [(0, 0, 1), (0, 0, 0, 1)]: return 0, 90 cart = self.lattice.get_cartesian_coords(miller_index) azim = get_angle([cart[0], cart[1], 0], (1, 0, 0)) v = [cart[0], cart[1], 0] elev = get_angle(cart, v) return azim, elev
def Bond_angle(pmg_struct, label2site_index, atom1_label,atom2_label,atom3_label): ''' input: atom1_label, atom2_label, atom3_label -- str, Ex) Fe5, O1, etc. label2site_index -- dictionary defined in script pmg_struct -- pymatgen structure output: angle -- float, Angle of (atom1-atom2-atom3) will be calculated. Angle is calculated by using shortest vector of (atom1-atom2) and (atom3-atom2) within periodic boundary condition. ''' atom1_pmg_index=convert_site_index(label2site_index, atom1_label) atom2_pmg_index=convert_site_index(label2site_index, atom2_label) atom3_pmg_index=convert_site_index(label2site_index, atom3_label) # Get fractional coordinates for each atoms atom1_fcoords=pmg_struct.sites[atom1_pmg_index].frac_coords atom2_fcoords=pmg_struct.sites[atom2_pmg_index].frac_coords atom3_fcoords=pmg_struct.sites[atom3_pmg_index].frac_coords # pbc_shortest_vector from atom2 to atom1 vector1=pbc_shortest_vectors(pmg_struct.lattice,atom2_fcoords,atom1_fcoords) # pbc_shortest_vector from atom2 to atom3 vector2=pbc_shortest_vectors(pmg_struct.lattice,atom2_fcoords,atom3_fcoords) # angle betseen two vectors # pbc_shortest_vector can digest set of sites, #but here we only use one 1 site to get angle. angle=get_angle(vector1[0][0],vector2[0][0]) return angle
def get_angle(self, s_index, i, j, k): """ Returns **Minimum Image** angle specified by three sites. Args: s_index: Structure index in structures list i (int): Index of first site. j (int): Index of second site. k (int): Index of third site. Returns: (float) Angle in degrees. """ structure = self.structures[s_index] lat_vec = np.array([structure.lattice.a, structure.lattice.b, structure.lattice.c]) v1 = structure[i].coords - structure[j].coords v2 = structure[k].coords - structure[j].coords for v in range(3): if np.fabs(v1[v]) > lat_vec[v] / 2.0: v1[v] -= np.sign(v1[v]) * lat_vec[v] if np.fabs(v2[v]) > lat_vec[v] / 2.0: v2[v] -= np.sign(v2[v]) * lat_vec[v] return get_angle(v1, v2, units="degrees")
def _get_azimuth_elev(self, miller_index): """ Args: miller_index: viewing direction Returns: azim, elev for plotting """ if miller_index == (0, 0, 1) or miller_index == (0, 0, 0, 1): return 0, 90 else: cart = self.lattice.get_cartesian_coords(miller_index) azim = get_angle([cart[0], cart[1], 0], (1, 0, 0)) v = [cart[0], cart[1], 0] elev = get_angle(cart, v) return azim, elev
def get_bond_angles(voronoi_neighbor_pairs): bond_angles_avg = [] bond_angles_std = [] for center_site, neighbor_sites in voronoi_neighbor_pairs.items(): local_bond_angles = [] for neighbor1_site, neighbor2_site in neighbor_sites: neighbor1_vector = neighbor1_site.coords - center_site.coords neighbor2_vector = neighbor2_site.coords - center_site.coords local_bond_angles.append( get_angle(neighbor1_vector, neighbor2_vector, units="degrees")) bond_angles_avg.append(np.mean(local_bond_angles)) bond_angles_std.append(np.std( local_bond_angles)) # sample stdev (for population, set ddof=1) return (bond_angles_avg, bond_angles_std)
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 check_and_force_collinear(mypatstructure, angle_tolerance=10.0): """Make sure the list of magmoms use the same spin axis by taking the largest magnetic moments as a reference axis for collinear calculations Parameters: ----------- mypatstructure: Structure structure object angle_tolerance: float the minimum angle to consider between the reference spin axis and magnetic moments vector. Default=10. Returns: -------- all_true : boolean, if True the angle is within the threshold """ from pymatgen.electronic_structure.core import Magmom from pymatgen.util.coord import get_angle magmoms = mypatstructure.site_properties['magmom'] cif_moments, direction = Magmom.get_consistent_set_and_saxis(magmoms) #print("direction", direction) magmoms_ = np.empty([0,3]) for comp in magmoms: magmoms_ = np.vstack([magmoms_, [comp[0], comp[1], comp[2]]]) #print("comp", comp[0], comp[1], comp[2]) # filter non zero moments magmoms_nzr = [m for m in magmoms_ if abs(np.any(m))] true_or_false = Magmom.are_collinear(magmoms) #if true_or_false == False: angles = np.empty([0, 1]) for m in magmoms_nzr: angles = np.vstack([angles, get_angle(unit_vector(m), direction, units="degrees")]) #print("angles", angles) store_check = [] for angle in angles: store_check.append(check_angle_bool(angle, angle_tolerance)) all_true = np.allclose(True, store_check) return all_true
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 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 get_next_nearest_neighbors( self, site_index: int, inc_inequivalent_site_index: bool = True) -> List[Dict[str, Any]]: """Gets information about the bonded next nearest neighbors. Args: site_index: The site index (zero based). inc_inequivalent_site_index: Whether to include the inequivalent site indices. Returns: A list of the next nearest neighbor information. For each next nearest neighbor site, returns a :obj:`dict` with the format:: {'element': el, 'connectivity': con, 'geometry': geom, 'angles': angles, 'distance': distance} The ``connectivity`` property is the connectivity type to the next nearest neighbor, e.g. "face", "corner", or "edge". The ``geometry`` property gives the geometry of the next nearest neighbor site. See the ``get_site_geometry`` method for the format of this data. The ``angles`` property gives the bond angles between the site and the next nearest neighbour. Returned as a :obj:`list` of :obj:`int`. Multiple bond angles are given when the two sites share more than nearest neighbor (e.g. if they are face-sharing or edge-sharing). The ``distance`` property gives the distance between the site and the next nearest neighbor. If ``inc_inequivalent_site_index=True``, the data will have an additional key ``'inequiv_index'`` corresponding to the inequivalent site index. E.g. if two sites are structurally/symmetrically equivalent (depending on the value of ``self.use_symmetry_equivalent_sites`` then they will have the same ``inequiv_index``. """ def get_coords(a_site_index, a_site_image): return np.asarray( self.bonded_structure.structure.lattice.get_cartesian_coords( self.bonded_structure.structure.frac_coords[a_site_index] + a_site_image)) nn_sites = self.bonded_structure.get_connected_sites(site_index) next_nn_sites = [ site for nn_site in nn_sites for site in self.bonded_structure.get_connected_sites( nn_site.index, jimage=nn_site.jimage) ] nn_sites_set = {(site.index, site.jimage) for site in nn_sites} seen_nnn_sites = set() next_nn_summary = [] for nnn_site in next_nn_sites: if (nnn_site.index == site_index and nnn_site.jimage == (0, 0, 0) or (nnn_site.index, nnn_site.jimage) in seen_nnn_sites): # skip the nnn site if it is the original atom of interest continue seen_nnn_sites.add((nnn_site.index, nnn_site.jimage)) sites = {(site.index, site.jimage) for site in self.bonded_structure.get_connected_sites( nnn_site.index, jimage=nnn_site.jimage)} shared_sites = nn_sites_set.intersection(sites) n_shared_atoms = len(shared_sites) if n_shared_atoms == 1: connectivity = "corner" elif n_shared_atoms == 2: connectivity = "edge" else: connectivity = "face" site_coords = get_coords(site_index, (0, 0, 0)) nnn_site_coords = get_coords(nnn_site.index, nnn_site.jimage) nn_site_coords = [ get_coords(nn_site_index, nn_site_image) for nn_site_index, nn_site_image in shared_sites ] # can't just use Structure.get_angles to calculate angles as it # doesn't take into account the site image angles = [ get_angle(site_coords - x, nnn_site_coords - x) for x in nn_site_coords ] distance = np.linalg.norm(site_coords - nnn_site_coords) geometry = self.get_site_geometry(nnn_site.index) summary = { "element": str(nnn_site.site.specie), "connectivity": connectivity, "geometry": geometry, "angles": angles, "distance": distance, } if inc_inequivalent_site_index: summary["inequiv_index"] = self.equivalent_sites[ nnn_site.index] next_nn_summary.append(summary) return next_nn_summary
def _parse_molecule(cls, contents): """ Helper method to parse coordinates of Molecule. Copied from GaussianInput class. """ paras = {} var_pattern = re.compile("^([A-Za-z]+\S*)[\s=,]+([\d\-\.]+)$") for l in contents: 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 contents: l = l.strip() if not l: break if (not zmode) and cls.xyz_patt.match(l): m = cls.xyz_patt.match(l) species.append(m.group(1)) toks = re.split("[,\s]+", l.strip()) if len(toks) > 4: coords.append(list(map(float, toks[2:5]))) else: coords.append(list(map(float, toks[1:4]))) elif cls.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.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, 0.0, float(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 = list(map(parse_species, species)) return Molecule(species, coords)
def get_angle_between_site_and_neighbors(site, neighbors): vec_1 = site.coords - neighbors[1].site.coords vec_2 = site.coords - neighbors[0].site.coords return get_angle(vec_1, vec_2)
def test_get_angle(self): v1 = (1, 0, 0) v2 = (1, 1, 1) self.assertAlmostEqual(coord.get_angle(v1, v2), 54.7356103172) self.assertAlmostEqual(coord.get_angle(v1, v2, units="radians"), 0.9553166181245092)