def add_subs(self, *lbls, inds=-1): """Returns the element list and cartesian geometry from a combination of substituents. Parameters ---------- lbls : list A list of substituent labels to be combined. inds : int or array_like, optional The indices for substitution between substituents. Setting inds=-1 (default) makes the last atom the subtituted atom. Otherwise a list of indices can be given for the first of each pair of substituents. Returns ------- elem : (N,) ndarray The atomic symbols of the combined substituent. xyz : (N, 3) ndarray The atomic cartesian coordinates of the combined substituent. """ if isinstance(inds, int): inds = (len(lbls) - 1) * [inds] elif len(inds) != len(lbls) - 1: raise ValueError('Number of inds != number of labels - 1') rot = 0 lbl0 = self.syn[lbls[0].lower()] elem = self.elem[lbl0] xyz = self.xyz[lbl0] for i, label in zip(inds, lbls[1:]): dist = np.linalg.norm(xyz - xyz[i], axis=1) dist[i] += np.max(dist) ibond = np.argmin(dist) rot = (rot + 1) % 2 ax = con.unit_vec(xyz[i] - xyz[ibond]) lbl = self.syn[label.lower()] new_elem = self.elem[lbl] new_xyz = displace.rotate(self.xyz[lbl], rot * np.pi, 'Z') new_xyz = displace.align_axis(new_xyz, 'Z', ax) blen = con.get_covrad(elem[ibond]) + con.get_covrad(new_elem[0]) new_xyz += xyz[ibond] + blen * ax elem = np.hstack((np.delete(elem, i), new_elem)) xyz = np.vstack((np.delete(xyz, i, axis=0), new_xyz)) return elem, xyz
def test_align_axis_origin(): xyz = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(xyz, 'Z', 'X', origin=xyz[0]) soln = np.array([xyz[:, 2] - xyz[0, 2], xyz[:, 1], xyz[0, 2] * np.ones(6)]).T assert np.allclose(new_xyz, soln)
def test_align_axis_ind(): soln = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(soln, 'Z', '-Y', ind=[0, 1]) soln[[0, 1], 1] = -soln[[0, 1], 2] soln[[0, 1], 2] = 0. assert np.allclose(new_xyz, soln)
def test_align_axis_pi_z(): ax = np.array([0., 0., -2.]) soln = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(soln, ax, 'Z') soln[:, 2] *= -1 assert np.allclose(new_xyz, soln)
def test_align_axis_same(): ax1 = np.array([1., -2., 0.]) ax2 = 2 * ax1 xyz = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(xyz, ax1, ax2) assert np.allclose(new_xyz, xyz)
def test_align_axis_default(): xyz = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(xyz, xyz[0] - xyz[1], 'Y') soln = np.array([xyz[:, 0], xyz[:, 2], -xyz[:, 1]]).T assert np.allclose(new_xyz, soln)
def subst(elem, xyz, sublbl, isub, ibond=None, pl=None, vec=None): """Returns a molecular geometry with an specified atom replaced by substituent. Labels are case-insensitive. The index isub gives the position to be substituted. If specified, ibond gives the atom bonded to the substituent. Otherwise, the nearest atom to isub is used. The orientation of the substituent can be given as a vector (the plane normal) or an index (the plane containing isub, ibond and pl). If isub is given as a list, the entire list of atoms is removed and the first index is treated as the position of the substituent. Parameters ---------- elem : (N,) array_like The atomic symbols of the unsubstituted molecule. xyz : (N, 3) array_like The atomic cartesian coordinates of the unsubstituted molecule. sublbl : str The substituent label. isub : int or list The atomic index (or indices) to be replaced by the substituent. ibond : int, optional The atomic index of the atom bonded to position isub. If None (default), the nearest atom is chosen. pl : int or array_like, optional The atomic index or vector defining the xz-plane of the substituent. If an index is given, the plane normal to the isub-ibond-pl plane is used. If None (default), the plane is arbitrarily set to [1, 1, 1] and the bond axis is projected out. vec : (N, 3) array_like, optional The atomic cartesian vectors of the unsubstitued molecule. Default is None. Returns ------- new_elem : (N,) ndarray The atomic symbols of the substituted molecule. new_xyz : (N, 3) ndarray The atomic cartesian coordinates of the substituted molecule. new_vec : (N, 3) ndarray The atomic cartesian vectors of the substituted molecule. Substituent atoms are all set of zero. If vec is None, new_vec is all zeros. """ elem = np.array(elem) xyz = np.atleast_2d(xyz) if not isinstance(isub, int): ipos = isub[0] else: isub = [isub] ipos = isub[0] if ibond is None: dist = np.linalg.norm(xyz - xyz[ipos], axis=1) dist[ipos] += np.max(dist) ibond = np.argmin(dist) elif ibond == ipos: raise ValueError('sub and bond indices cannot be the same') ax = con.unit_vec(xyz[ipos] - xyz[ibond]) if pl is None: # choose an arbitrary axis and project out the bond axis pl = np.ones(3) pl -= np.dot(pl, ax) * ax elif isinstance(pl, int): if pl == ipos: raise ValueError('plane and sub indices cannot be the same') elif pl == ibond: raise ValueError('plane and bond indices cannot be the same') pl = np.cross(xyz[ipos] - xyz[ibond], xyz[pl] - xyz[ibond]) sub_el, sub_xyz = import_sub(sublbl) if elem[ipos] == sub_el[0]: blen = np.linalg.norm(xyz[ipos] - xyz[ibond]) else: blen = con.get_covrad(elem[ibond]) + con.get_covrad(sub_el[0]) # rotate to correct orientation and displace to correct position sub_xyz = displace.align_axis(sub_xyz, 'Z', ax) sub_pl = displace.align_axis([0., 1., 0.], 'Z', ax) sub_xyz = displace.align_axis(sub_xyz, sub_pl, pl) sub_xyz += xyz[ibond] + blen * ax # build the final geometry ind1 = [i for i in range(ipos) if i not in isub[1:]] ind2 = [i for i in range(ipos + 1, len(elem)) if i not in isub[1:]] new_elem = np.hstack((elem[ind1], sub_el, elem[ind2])) new_xyz = np.vstack((xyz[ind1], sub_xyz, xyz[ind2])) if vec is None: return new_elem, new_xyz, None else: new_vec = np.vstack((vec[ind1], np.zeros((len(sub_el), 3)), vec[ind2])) return new_elem, new_xyz, new_vec
def test_align_axis_origin(): xyz = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(xyz, 'z', 'x', origin=xyz[0]) soln = np.array([xyz[:,2] - xyz[0,2], xyz[:,1], xyz[0,2]*np.ones(6)]).T assert np.allclose(new_xyz, soln)
def test_align_axis_ind(): soln = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(soln, 'z', '-y', ind=[0,1]) soln[[0,1],1] = -soln[[0,1],2] soln[[0,1],2] = 0. assert np.allclose(new_xyz, soln)
def test_align_axis_pi_z(): ax = np.array([0., 0., -2.]) soln = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(soln, ax, 'z') soln[:,2] *= -1 assert np.allclose(new_xyz, soln)
def test_align_axis_same(): ax1 = np.array([1., -2., 0.]) ax2 = 2*ax1 xyz = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(xyz, ax1, ax2) assert np.allclose(new_xyz, xyz)
def test_align_axis_default(): xyz = np.copy(eg.c2h4[1]) new_xyz = displace.align_axis(xyz, xyz[0]-xyz[1], 'y') soln = np.array([xyz[:,0], xyz[:,2], -xyz[:,1]]).T assert np.allclose(new_xyz, soln)