def get_R_der(x, y): """ Calculate the derivatives of the correlation matrix with respect to the Cartesian coordinates. Parameters ---------- x : numpy.ndarray Trial coordinates, dimensionality (number of atoms) x 3 y : numpy.ndarray Target coordinates, dimensionalty must match trial coordinates Returns ------- numpy.ndarray u, w, i, j : First two dimensions are (n_atoms, 3), the variables being differentiated Second two dimensions are (3, 3), the elements of the R-matrix derivatives with respect to atom u, dimension w """ x = x - np.mean(x, axis=0) y = y - np.mean(y, axis=0) # 3 x 3 x N_atoms x 3 ADiffR = np.zeros((x.shape[0], 3, 3, 3), dtype=float) for u in range(x.shape[0]): for w in range(3): for i in range(3): for j in range(3): if i == w: ADiffR[u, w, i, j] = y[u, j] fdcheck = False if fdcheck: h = 1e-4 R0 = build_correlation(x, y) for u in range(x.shape[0]): for w in range(3): x[u, w] += h RPlus = build_correlation(x, y) x[u, w] -= 2 * h RMinus = build_correlation(x, y) x[u, w] += h FDiffR = (RPlus - RMinus) / (2 * h) logger.info("%i %i %12.6f\n" % (u, w, np.max(np.abs(ADiffR[u, w] - FDiffR)))) return ADiffR
def get_R_der(x, y): """ Calculate the derivatives of the correlation matrix with respect to the Cartesian coordinates. Parameters ---------- x : numpy.ndarray Trial coordinates, dimensionality (number of atoms) x 3 y : numpy.ndarray Target coordinates, dimensionalty must match trial coordinates Returns ------- numpy.ndarray u, w, i, j : First two dimensions are (n_atoms, 3), the variables being differentiated Second two dimensions are (3, 3), the elements of the R-matrix derivatives with respect to atom u, dimension w """ x = x - np.mean(x,axis=0) y = y - np.mean(y,axis=0) # 3 x 3 x N_atoms x 3 ADiffR = np.zeros((x.shape[0], 3, 3, 3), dtype=float) for u in range(x.shape[0]): for w in range(3): for i in range(3): for j in range(3): if i == w: ADiffR[u, w, i, j] = y[u, j] fdcheck = False if fdcheck: h = 1e-4 R0 = build_correlation(x, y) for u in range(x.shape[0]): for w in range(3): x[u, w] += h RPlus = build_correlation(x, y) x[u, w] -= 2*h RMinus = build_correlation(x, y) x[u, w] += h FDiffR = (RPlus-RMinus)/(2*h) logger.info("%i %i %12.6f\n" % (u, w, np.max(np.abs(ADiffR[u, w]-FDiffR)))) return ADiffR
def get_expmap_der(x, y, second=False, fdcheck=False, use_loops=False): """ Given trial coordinates x and target coordinates y, return the derivatives of the exponential map that brings x into maximal coincidence (minimum RMSD) with y, with respect to the coordinates of x. Parameters ---------- x : numpy.ndarray Trial coordinates, dimensionality (number of atoms) x 3 y : numpy.ndarray Target coordinates, dimensionalty must match trial coordinates second : bool If true, return the second derivative matrix as well. fdcheck : bool If true, perform a finite difference check and return finite difference gradients use_loops : bool If true, use the slower reference implementation that uses for loops Returns ------- numpy.ndarray u, w, i: First two dimensions are (n_atoms, 3), the variables being differentiated Third dimension is 3, the elements of the exponential map derivatives with respect to atom u, dimension w numpy.ndarray (if second=True) u, w, a, b, i: First four dimensions are (n_atoms, 3, n_atoms, 3), the variables being differentiated Fifth dimension is 3, the elements of the exponential map second derivatives with respect to atom u, dimension w, atom a, dimension b """ # t0 = time.time() q = get_quat(x,y) v = get_expmap(x,y) if second: fac, dfac, dfac2 = calc_fac_dfac(q[0], second=True) else: fac, dfac = calc_fac_dfac(q[0]) dvdq = np.zeros((4, 3), dtype=float) dvdq[0, :] = dfac*q[1:] for i in range(3): dvdq[i+1, i] = fac if second: dvdq2 = np.zeros((4, 4, 3), dtype=float) dvdq2[0, 0, :] = dfac2*q[1:] for i in range(3): dvdq2[0, i+1, i] = dfac dvdq2[i+1, 0, i] = dfac if fdcheck: h = 1e-6 fac, _ = calc_fac_dfac(q[0]) V0 = fac*q[1:] logger.info("-=# Now checking first derivatives of exponential map w/r.t. quaternion #=-\n") for p in range(4): # Do backwards difference only, because arccos of q[0] > 1 is undefined q[p] -= h fac, _ = calc_fac_dfac(q[0]) VMinus = fac*q[1:] q[p] += h FDiffV = (V0-VMinus)/h maxerr = np.max(np.abs(dvdq[p]-FDiffV)) logger.info("q %3i : maxerr = %.3e %s\n" % (i, maxerr, 'X' if maxerr > 1e-6 else '')) # logger.info(i, dvdq[i], FDiffV, np.max(np.abs(dvdq[i]-FDiffV))) if second: h = 1e-5 logger.info("-=# Now checking second derivatives of exponential map w/r.t. quaternion #=-\n") def V_(q_): fac_, _ = calc_fac_dfac(q_[0]) return fac_*q_[1:] for p in range(4): for r in range(4): if p == r: FDiffV2 = V0.copy() q[p] -= h FDiffV2 -= 2*V_(q) q[p] -= h FDiffV2 += V_(q) q[p] += 2*h else: FDiffV2 = V0.copy() q[p] -= h q[r] -= h FDiffV2 += V_(q) q[r] += h FDiffV2 -= V_(q) q[r] -= h q[p] += h FDiffV2 -= V_(q) q[r] += h FDiffV2 /= h**2 maxerr = np.max(np.abs(dvdq2[p, r]-FDiffV2)) if maxerr > 1e-7: logger.info("q %3i %3i : maxerr = %.3e %s\n" % (p, r, maxerr, 'X' if maxerr > 1e-5 else '')) # logger.info("q %3i %3i : analytic %s numerical %s maxerr = %.3e %s" % (i, j, str(dvdq2[i, j]), str(FDiffV2), maxerr, 'X' if maxerr > 1e-4 else '')) # Dimensionality: Number of atoms, number of dimensions (3), number of elements in q (4) if second: dqdx, dqdx2 = get_q_der(x, y, second=True) else: dqdx = get_q_der(x, y) # Dimensionality: Number of atoms, number of dimensions (3), number of elements in v (3) dvdx = np.zeros((x.shape[0], 3, 3), dtype=float) for u in range(x.shape[0]): for w in range(3): dqdx_uw = dqdx[u, w] for p in range(4): dvdx[u, w, :] += dvdq[p, :] * dqdx[u, w, p] if second: if use_loops: # Reference implementation using for loops dvdx2 = np.zeros((x.shape[0], 3, x.shape[0], 3, 3), dtype=float) dvdqx = np.zeros((4, x.shape[0], 3, 3), dtype=float) for p in range(4): for u in range(x.shape[0]): for w in range(3): for i in range(3): for r in range(4): dvdqx[p, u, w, i] += dvdq2[p, r, i] * dqdx[u, w, r] for u in range(x.shape[0]): for w in range(3): for a in range(x.shape[0]): for b in range(3): for i in range(3): for p in range(4): dvdx2[u, w, a, b, i] += dvdqx[p, a, b, i] * dqdx[u, w, p] dvdx2[u, w, a, b, i] += dvdq[p, i] * dqdx2[u, w, a, b, p] else: dvdqx = np.einsum('pri,uwr->puwi', dvdq2, dqdx, optimize=True) dvdx2 = np.einsum('pabi,uwp->uwabi', dvdqx, dqdx, optimize=True) dvdx2 += np.einsum('pi,uwabp->uwabi', dvdq, dqdx2, optimize=True) # LPW 2019-03-09: Using einsum gives 166x speedup over for loops for trp-cage (200 atoms); 0.06 vs. 10.1 s # print(time.time()-t0) if fdcheck: h = 1e-3 logger.info("-=# Now checking first derivatives of exponential map w/r.t. Cartesians #=-\n") FDiffV = np.zeros((x.shape[0], 3, 3), dtype=float) for u in range(x.shape[0]): for w in range(3): x[u, w] += h VPlus = get_expmap(x, y) x[u, w] -= 2*h VMinus = get_expmap(x, y) x[u, w] += h FDiffV[u, w] = (VPlus-VMinus)/(2*h) maxerr = np.max(np.abs(dvdx[u, w]-FDiffV[u, w])) logger.info("atom %3i %s : maxerr = %.3e %s\n" % (u, 'xyz'[w], maxerr, 'X' if maxerr > 1e-6 else '')) dvdx = FDiffV if second: logger.info("-=# Now checking second derivatives of exponential map w/r.t. Cartesians #=-\n") FDiffV2 = np.zeros((x.shape[0], 3, y.shape[0], 3, 3), dtype=float) for u in range(x.shape[0]): for w in range(3): for a in range(x.shape[0]): for b in range(3): if a == u and b == w: x[u, w] += h VPlus = get_expmap(x, y) x[u, w] -= 2*h VMinus = get_expmap(x, y) x[u, w] += h FDiffV2[u, w, a, b] = (VPlus+VMinus-2*V0)/h**2 else: x[u, w] += h x[a, b] += h # (+, +) FDiffV2[u, w, a, b] = get_expmap(x, y) x[u, w] -= 2*h # (-, +) FDiffV2[u, w, a, b] -= get_expmap(x, y) x[a, b] -= 2*h # (-, -) FDiffV2[u, w, a, b] += get_expmap(x, y) x[u, w] += 2*h # (+, -) FDiffV2[u, w, a, b] -= get_expmap(x, y) x[u, w] -= h x[a, b] += h FDiffV2[u, w, a, b] /= (4*h**2) maxerr = np.max(np.abs(dvdx2[u, w, a, b]-FDiffV2[u, w, a, b])) if maxerr > 1e-8: logger.info("atom %3i %s, %3i %s : maxerr = %.3e %s\n" % (u, 'xyz'[w], a, 'xyz'[b], maxerr, 'X' if maxerr > 1e-6 else '')) dvdx2 = FDiffV2 if second: return dvdx, dvdx2 else: return dvdx
def get_q_der(x, y, second=False, fdcheck=False, use_loops=False): """ Calculate the derivatives of the quaternion with respect to the Cartesian coordinates. Parameters ---------- x : numpy.ndarray Trial coordinates, dimensionality (number of atoms) x 3 y : numpy.ndarray Target coordinates, dimensionalty must match trial coordinates second : bool If true, return the second derivative matrix as well. fdcheck : bool If true, perform a finite difference check and return finite difference gradients use_loops : bool If true, use the slower reference implementation that uses for loops (only in second derivs) Returns ------- numpy.ndarray u, w, i: First two dimensions are (n_atoms, 3), the variables being differentiated Third dimension is 4, the elements of the quaternion derivatives with respect to atom u, dimension w numpy.ndarray (if second=True) u, w, a, b, i: First four dimensions are (n_atoms, 3, n_atoms, 3), the variables being differentiated Fifth dimension is 4, the elements of the quaternion second derivatives with respect to atom u, dimension w, atom a, dimension b """ x = x - np.mean(x,axis=0) y = y - np.mean(y,axis=0) q, l = get_quat(x, y, eig=True) F = build_F(x, y) dF = get_F_der(x, y) mat = np.eye(4)*l - F # pinv = np.matrix(np.linalg.pinv(np.eye(4)*l - F)) Minv = invert_svd(np.eye(4)*l - F, thresh=1e-6) dq = np.zeros((x.shape[0], 3, 4), dtype=float) for u in range(x.shape[0]): for w in range(3): # dquw = Minv*np.matrix(dF[u, w])*np.matrix(q).T dquw = multi_dot([Minv,dF[u, w],q.T]) dq[u, w] = np.array(dquw).flatten() if second: if use_loops: # Reference implementation using for loops dl = np.zeros((x.shape[0], 3), dtype=float) dM = np.zeros((x.shape[0], 3, 4, 4), dtype=float) dq2 = np.zeros((x.shape[0], 3, x.shape[0], 3, 4), dtype=float) dinv = np.zeros((x.shape[0], 3, 4, 4), dtype=float) for u in range(x.shape[0]): for w in range(3): dl[u, w] = multi_dot([q, dF[u, w], q.T]) dM[u, w] = np.eye(4)*dl[u, w] - dF[u, w] term1 = -multi_dot([Minv, dM[u, w], Minv]) term2 = multi_dot([Minv, Minv.T, dM[u, w].T, (np.eye(4)-np.dot(mat, Minv))]) term3 = multi_dot([(np.eye(4)-np.dot(Minv, mat)), dM[u, w].T, Minv.T, Minv]) dinv[u, w] = term1 + term2 + term3 for a in range(x.shape[0]): for b in range(3): dq2[u, w, a, b] += multi_dot([dinv[u, w], dF[a, b], q]) dq2[u, w, a, b] += multi_dot([Minv, dF[a, b], dq[u, w]]) else: # t0 = time.time() # LPW 2019-03-09: Setting optimize=True gives a 15x speedup for trp-cage (200 atoms, 0.02 vs. 0.3 s) dl = np.einsum('p,uwpr,r->uw', q, dF, q, optimize=True) dM = np.einsum('uw,pr->uwpr', dl, np.eye(4), optimize=True) - dF dinv = -np.einsum('ps,uwst,tr->uwpr', Minv, dM, Minv, optimize=True) dinv += np.einsum('ps,ts,uwvt,vr->uwpr', Minv, Minv, dM, np.eye(4)-np.dot(mat,Minv), optimize=True) dinv += np.einsum('ps,uwts,vt,vr->uwpr', np.eye(4)-np.dot(Minv, mat), dM, Minv, Minv, optimize=True) dq2 = np.einsum('uwpr,abrs,s->uwabp', dinv, dF, q, optimize=True) dq2 += np.einsum('pr,abrs,uws->uwabp', Minv, dF, dq, optimize=True) # print(time.time()-t0) if fdcheck: # If fdcheck = True, then return finite difference derivatives h = 1e-6 logger.info("-=# Now checking first derivatives of superposition quaternion w/r.t. Cartesians #=-\n") FDiffQ = np.zeros((x.shape[0], 3, 4), dtype=float) for u in range(x.shape[0]): for w in range(3): x[u, w] += h QPlus = get_quat(x, y) x[u, w] -= 2*h QMinus = get_quat(x, y) x[u, w] += h FDiffQ[u, w] = (QPlus-QMinus)/(2*h) maxerr = np.max(np.abs(dq[u, w]-FDiffQ[u, w])) logger.info("atom %3i %s : maxerr = %.3e %s\n" % (u, 'xyz'[w], maxerr, 'X' if maxerr > 1e-6 else '')) dq = FDiffQ if second: h = 1.0e-3 logger.info("-=# Now checking second derivatives of superposition quaternion w/r.t. Cartesians #=-\n") Q0 = get_quat(x, y) FDiffQ2 = np.zeros((x.shape[0], 3, y.shape[0], 3, 4), dtype=float) for u in range(x.shape[0]): for w in range(3): for a in range(x.shape[0]): for b in range(3): if a == u and b == w: x[u, w] += h QPlus = get_quat(x, y) x[u, w] -= 2*h QMinus = get_quat(x, y) x[u, w] += h FDiffQ2[u, w, a, b] = (QPlus+QMinus-2*Q0)/h**2 else: x[u, w] += h x[a, b] += h # (+, +) FDiffQ2[u, w, a, b] = get_quat(x, y) x[u, w] -= 2*h # (-, +) FDiffQ2[u, w, a, b] -= get_quat(x, y) x[a, b] -= 2*h # (-, -) FDiffQ2[u, w, a, b] += get_quat(x, y) x[u, w] += 2*h # (+, -) FDiffQ2[u, w, a, b] -= get_quat(x, y) x[u, w] -= h x[a, b] += h FDiffQ2[u, w, a, b] /= (4*h**2) maxerr = np.max(np.abs(dq2[u, w, a, b]-FDiffQ2[u, w, a, b])) if maxerr > 1e-8: logger.info("atom %3i %s, %3i %s : maxerr = %.3e %s\n" % (u, 'xyz'[w], a, 'xyz'[b], maxerr, 'X' if maxerr > 1e-6 else '')) dq2 = FDiffQ2 if second: return dq, dq2 else: return dq
def get_F_der(x, y): """ Calculate the derivatives of the F-matrix with respect to the Cartesian coordinates. Parameters ---------- x : numpy.ndarray Trial coordinates, dimensionality (number of atoms) x 3 y : numpy.ndarray Target coordinates, dimensionalty must match trial coordinates Returns ------- numpy.ndarray u, w, i, j : First two dimensions are (n_atoms, 3), the variables being differentiated Second two dimensions are (4, 4), the elements of the R-matrix derivatives with respect to atom u, dimension w """ x = x - np.mean(x,axis=0) y = y - np.mean(y,axis=0) dR = get_R_der(x, y) dF = np.zeros((x.shape[0], 3, 4, 4),dtype=float) for u in range(x.shape[0]): for w in range(3): dR11 = dR[u,w,0,0] dR12 = dR[u,w,0,1] dR13 = dR[u,w,0,2] dR21 = dR[u,w,1,0] dR22 = dR[u,w,1,1] dR23 = dR[u,w,1,2] dR31 = dR[u,w,2,0] dR32 = dR[u,w,2,1] dR33 = dR[u,w,2,2] dF[u,w,0,0] = dR11 + dR22 + dR33 dF[u,w,0,1] = dR23 - dR32 dF[u,w,0,2] = dR31 - dR13 dF[u,w,0,3] = dR12 - dR21 dF[u,w,1,0] = dR23 - dR32 dF[u,w,1,1] = dR11 - dR22 - dR33 dF[u,w,1,2] = dR12 + dR21 dF[u,w,1,3] = dR13 + dR31 dF[u,w,2,0] = dR31 - dR13 dF[u,w,2,1] = dR12 + dR21 dF[u,w,2,2] = dR22 - dR33 - dR11 dF[u,w,2,3] = dR23 + dR32 dF[u,w,3,0] = dR12 - dR21 dF[u,w,3,1] = dR13 + dR31 dF[u,w,3,2] = dR23 + dR32 dF[u,w,3,3] = dR33 - dR22 - dR11 fdcheck = False if fdcheck: h = 1e-4 F0 = build_F(x, y) for u in range(x.shape[0]): for w in range(3): x[u, w] += h FPlus = build_F(x, y) x[u, w] -= 2*h FMinus = build_F(x, y) x[u, w] += h FDiffF = (FPlus-FMinus)/(2*h) logger.info("%i %i %12.6f\n" % (u, w, np.max(np.abs(dF[u, w]-FDiffF)))) return dF