def fit(self, p: Molecule): """Order, rotate and transform all of the matched `p` molecule according to the given `threshold`. Args: p: a `Molecule` object what will be matched with the target one. Returns: Array of the possible matches where the elements are: p_prime: Rotated and translated of the `p` `Molecule` object rmsd: Root-mean-square-deviation between `p_prime` and the `target` """ out = [] for inds in self.permutations(p): p_prime = p.copy() p_prime._sites = [p_prime[i] for i in inds] U, V, rmsd = super().match(p_prime) # Rotate and translate matrix `p` onto the target molecule. # P' = P * U + V for site in p_prime: site.coords = np.dot(site.coords, U) + V out.append((p_prime, rmsd)) return out
def fit(self, p: Molecule): """Rotate and transform `p` molecule according to the best match. Args: p: a `Molecule` object what will be matched with the target one. Returns: p_prime: Rotated and translated of the `p` `Molecule` object rmsd: Root-mean-square-deviation between `p_prime` and the `target` """ U, V, rmsd = self.match(p) # Rotate and translate matrix `p` onto the target molecule. # P' = P * U + V p_prime = p.copy() for site in p_prime: site.coords = np.dot(site.coords, U) + V return p_prime, rmsd
def match(self, p: Molecule): """Similar as `KabschMatcher.match` but this method also finds all of the possible atomic orders according to the `threshold`. Args: p: a `Molecule` object what will be matched with the target one. Returns: Array of the possible matches where the elements are: inds: The indices of atoms U: 3x3 rotation matrix V: Translation vector rmsd: Root mean squared deviation between P and Q """ out = [] for inds in self.permutations(p): p_prime = p.copy() p_prime._sites = [p_prime[i] for i in inds] U, V, rmsd = super().match(p_prime) out.append((inds, U, V, rmsd)) return out
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