def _csSpherical_q2x(self, Q, deriv = 0, out = None, var = None): # Use adf routines to compute derivatives # q = dfun.X2adf(Q, deriv, var) # q[0] is r, q[1] = theta, q[2] = phi if self.angle == 'rad': sintheta = adf.sin(q[1]) costheta = adf.cos(q[1]) sinphi = adf.sin(q[2]) cosphi = adf.cos(q[2]) else: # degree deg2rad = np.pi/180.0 sintheta = adf.sin(deg2rad * q[1]) costheta = adf.cos(deg2rad * q[1]) sinphi = adf.sin(deg2rad * q[2]) cosphi = adf.cos(deg2rad * q[2]) r = q[0] x = r * sintheta * cosphi y = r * sintheta * sinphi z = r * costheta return dfun.adf2array([x,y,z], out)
def _csPolar_q2x(self, Q, deriv = 0, out = None, var = None): # Use adf routines to compute derivatives # q = dfun.X2adf(Q, deriv, var) # q[0] is r, q[1] = phi (in deg or rad) if self.angle == 'rad': x = q[0] * adf.cos(q[1]) y = q[0] * adf.sin(q[1]) else: # degree deg2rad = np.pi/180.0 x = q[0] * adf.cos(deg2rad * q[1]) y = q[0] * adf.sin(deg2rad * q[1]) return dfun.adf2array([x,y], out)
def _ftheta(self, X, deriv = 0, out = None, var = None): """ basis evaluation function Use recursion relations for associated Legendre polynomials. """ nd,nvar = dfun.ndnvar(deriv, var, self.nx) if out is None: base_shape = X.shape[1:] out = np.ndarray( (nd, self.nf) + base_shape, dtype = X.dtype) # Create adf object for theta theta = dfun.X2adf(X, deriv, var)[0] m = abs(self.m) # |m| sinm = adf.powi(adf.sin(theta), m) cos = adf.cos(theta) # Calculate Legendre polynomials # with generic algebra F = _leg_gen(sinm, cos, m, np.max(self.l)) # Convert adf objects to a single # derivative array dfun.adf2array(F, out) return out
def calcsurf(q): max_pow = [5, 5, 5, 6, 6, 6] # max_pow[5] is really the max freq. of dihedral qpow = [] for i in range(5): qi = [adf.const_like(1.0, q[i]), q[i]] for p in range(2, max_pow[i] + 1): qi.append(qi[1] * qi[p - 1]) # qi ** p qpow.append(qi) # Calculate cos(n*q6) cosq = [adf.cos(n * q[5]) for n in range(max_pow[5] + 1)] qpow.append(cosq) v = 0.0 nterms = powers.shape[0] for i in range(nterms): c = coeffs[i] v += c * \ qpow[0][powers[i,0]] * \ qpow[1][powers[i,1]] * \ qpow[2][powers[i,2]] * \ qpow[3][powers[i,3]] * \ qpow[4][powers[i,4]] * \ qpow[5][powers[i,5]] return v
def _csv3_q2x(self, Q, deriv = 0, out = None, var = None): """ Triatomic valence coordinate system Q2X instance method. See :meth:`CoordSys.Q2X` for details. Parameters ---------- Q : ndarray Shape (self.nQ, ...) deriv, out, var : See :meth:`CoordSys.Q2X` for details. Returns ------- out : ndarray Shape (nd, self.nX, ...) """ natoms = 3 base_shape = Q.shape[1:] if var is None: var = [0, 1, 2] # Calculate derivatives for all Q nvar = len(var) # nd = adf.nck(deriv + nvar, min(deriv, nvar)) # The number of derivatives nd = dfun.nderiv(deriv, nvar) # Create adf symbols/constants for each coordinate q = [] for i in range(self.nQ): if i in var: # Derivatives requested for this variable q.append(adf.sym(Q[i], var.index(i), deriv, nvar)) else: # Derivatives not requested, treat as constant q.append(adf.const(Q[i], deriv, nvar)) # q = r1, r2, theta if out is None: out = np.ndarray( (nd, 3*natoms) + base_shape, dtype = Q.dtype) out.fill(0) # Initialize out to 0 # Calculate Cartesian coordinates if self.angle == 'deg': q[2] = (np.pi / 180.0) * q[2] # q[2] is now in radians if self.supplementary: q[2] = np.pi - q[2] # theta <-- pi - theta if self.embedding_mode == 0: np.copyto(out[:,2], (-q[0]).d ) # -r1 np.copyto(out[:,7], (q[1] * adf.sin(q[2])).d ) # r2 * sin(theta) np.copyto(out[:,8], (-q[1] * adf.cos(q[2])).d ) # -r2 * cos(theta) elif self.embedding_mode == 1: np.copyto(out[:,0], (q[0] * adf.cos(q[2]/2)).d ) # r1 * cos(theta/2) np.copyto(out[:,2], (q[0] * adf.sin(q[2]/2)).d ) # r1 * sin(theta/2) np.copyto(out[:,6], (q[1] * adf.cos(q[2]/2)).d ) # r2 * cos(theta/2) np.copyto(out[:,8], (-q[1] * adf.sin(q[2]/2)).d ) # -r2 * sin(theta/2) else: raise RuntimeError("Unexpected embedding_mode") return out
def _Philm(self, X, deriv = 0, out = None, var = None): # Setup nd,nvar = dfun.ndnvar(deriv, var, self.nx) if out is None: base_shape = X.shape[1:] out = np.ndarray( (nd, self.nf) + base_shape, dtype = X.dtype) # Make adf objects for theta and phi x = dfun.X2adf(X, deriv, var) theta = x[0] phi = x[1] ######################################### # # Calculate F and f factors first # sinth = adf.sin(theta) costh = adf.cos(theta) lmax = np.max(self.l) # Calculate the associated Legendre factors Flm = [] amuni = np.unique(abs(self.m)) # list of unique |m| for aM in amuni: # Order aM = |M| sinM = adf.powi(sinth, aM) # Calculate F^M up to l <= lmax Flm.append(_leg_gen(sinM, costh, aM, lmax)) #Flm is now a nested list # Calculate the phi factors smuni = np.unique(self.m) # list of unique signed m fm = [] for sM in smuni: if sM == 0: fm.append(adf.const_like(1/np.sqrt(2*np.pi), phi)) elif sM < 0: fm.append(1/np.sqrt(np.pi) * adf.sin(abs(sM) * phi)) else: #sM > 0 fm.append(1/np.sqrt(np.pi) * adf.cos(sM * phi)) # ############################################## ########################################### # Now calculate the list of real spherical harmonics Phi = [] for i in range(self.nf): l = self.l[i] m = self.m[i] # signed m # Gather the associated Legendre polynomial (theta factor) aM_idx = np.where(amuni == abs(m))[0][0] l_idx = l - abs(m) F_i = Flm[aM_idx][l_idx] # Gather the sine/cosine (phi factor) sM_idx = np.where(smuni == m)[0][0] f_i = fm[sM_idx] # Add their product to the list of functions Phi.append(F_i * f_i) # ########################################### # Convert the list to a single # DFun derivative array dfun.adf2array(Phi, out) # and return return out
def _chinell(self, X, deriv = 0, out = None, var = None): # Setup nd,nvar = dfun.ndnvar(deriv, var, self.nx) if out is None: base_shape = X.shape[1:] out = np.ndarray( (nd, self.nf) + base_shape, dtype = X.dtype) # Make adf objects for theta and phi x = dfun.X2adf(X, deriv, var) r = x[0] # Cylindrical radius phi = x[1] if self._rad else x[1] * np.pi/180.0 # Angular coordinate ######################################### # # Calculate R and f factors first # # Calculate the radial wavefunctions Rnell = [] abs_ell_uni = np.unique(abs(self.ell)) # list of unique |ell| expar2 = adf.exp(-0.5 * self.alpha * (r*r)) # The exponential radial factor for aELL in abs_ell_uni: nmax = round(np.floor(self.vmax - aELL) / 2) Rnell.append(_radialHO_gen(r, expar2, nmax, aELL, 2, self.alpha)) # Calculate the phi factors, f_ell(phi) sig_ell_uni = np.unique(self.ell) # list of unique signed ell fell = [] if self._rad: # Normalization assuming radians for sELL in sig_ell_uni: if sELL == 0: fell.append(adf.const_like(1/np.sqrt(2*np.pi), phi)) elif sELL < 0: fell.append(1/np.sqrt(np.pi) * adf.sin(abs(sELL) * phi)) else: #sELL > 0 fell.append(1/np.sqrt(np.pi) * adf.cos(sELL * phi)) else: # Normalization for degrees for sELL in sig_ell_uni: if sELL == 0: fell.append(adf.const_like(1/np.sqrt(360.00), phi)) elif sELL < 0: fell.append(1/np.sqrt(180.0) * adf.sin(abs(sELL) * phi)) else: #sELL > 0 fell.append(1/np.sqrt(180.0) * adf.cos(sELL * phi)) # ############################################## ########################################### # Now calculate the list of real 2-D HO wavefunctions chi = [] for i in range(self.nf): ell = self.ell[i] n = self.n[i] # Gather the radial factor abs_ELL_idx = np.where(abs_ell_uni == abs(ell))[0][0] R_i = Rnell[abs_ELL_idx][n] # Gather the angular factor sig_ELL_idx = np.where(sig_ell_uni == ell)[0][0] f_i = fell[sig_ELL_idx] # Add their product to the list of functions chi.append(R_i * f_i) # ########################################### # Convert the list to a single # DFun derivative array dfun.adf2array(chi, out) # and return return out
def _zmat_q2x(self, Q, deriv=0, out=None, var=None): """ ZMAT X(Q) function. Parameters ---------- Q : ndarray Input coordinate array (`self.nQ`, ...) deriv : int, optional Maximum derivative order. The default is 0. out : ndarray, optional Output location (nd, `self.nX`, ...) . The default is None. var : list of int, optional Requested ordered derivative variables. If None, all are used. Returns ------- out : ndarray The X coordinates. """ natoms = self.natoms # Number of non-dummy atoms base_shape = Q.shape[1:] if var is None: var = [i for i in range(self.nQ)] nvar = len(var) # The number of derivative variables nd = adf.nderiv(deriv, nvar) # The number of derivatives # Set up output location if out is None: out = np.ndarray((nd, 3 * natoms) + base_shape, dtype=Q.dtype) out.fill(0) # Initialize out to 0 # Create adf symbols/constants for each CS coordinate q = [] for i in range(self.nQ): if i in var: # Derivatives requested for this variable q.append(adf.sym(Q[i], var.index(i), deriv, nvar)) else: # Derivatives not requested, treat as constant q.append(adf.const(Q[i], deriv, nvar)) # Calculate ZMAT atomic Cartesian positions, A # nA = len( self._atomLabels) # The number of ZMAT entries, including dummy A = np.ndarray((nA, 3), dtype=adf.adarray) # ZMAT atom positions Ci = np.ndarray((3, ), dtype=adf.adarray) # ZMAT coordinate for row i # For each atom (dummy + non-dummy) in the ZMAT for i in range(nA): # First, calculate the current row's ZMAT coordinates # C = [r, theta, tau] for j in range(3): if j - i >= 0: # ZMAT coordinate not defined; will not be referenced continue if self._coordID[i][j] is None: # C[i,j] is a constant literal value Ci[j] = adf.const(np.full(base_shape, self._val[i][j]), deriv, nvar) else: # A value*coordinate entry # Use coordID[i,j] to lookup the correct CS coordinate # adarray in q Ci[j] = self._val[i][j] * q[self._coordID[i][j]] # Convert angular coordinates to correct unit if j == 1 or j == 2: if self.angles == 'rad': pass # already correct elif self.angles == 'deg': Ci[j] = Ci[j] * (np.pi / 180.0) # convert deg to rad else: raise ValueError("Invalid angle units") if j == 1 and self.supplementary: # Convert from supplementary to interior angle Ci[j] = np.pi - Ci[j] # Now calculate the position of atom i, A[i] # # There are several special cases: # Case 1: the first atom is at the origin if i == 0: for j in range(3): A[0, j] = adf.const(np.zeros(base_shape), deriv, nvar) # Case 2: the second atom is on the +z axis elif i == 1: # x and y are 0 A[1, 0] = adf.const(np.zeros(base_shape), deriv, nvar) # x A[1, 1] = adf.const(np.zeros(base_shape), deriv, nvar) # y # # The z-value is given by r = Ci[0] A[1, 2] = Ci[0].copy() # Case 3: the third atom is in the z/+x plane elif i == 2: # There are two possible sub-cases: # a) References are 1, then 2 # b) References are 2, then 1 # # In both cases, y = 0 and # x = r * sin(theta * pi/180 ) A[2, 1] = adf.const(np.zeros(base_shape), deriv, nvar) # y A[2, 0] = Ci[0] * adf.sin(Ci[1]) # x # # the z coordinate depends on the sub-case if self._ref[2] == (1, 2, None): # Case 3a) # 1---2 +---> z # / | # @ x v # # z = r * cos(theta * pi/180) A[2, 2] = Ci[0] * adf.cos(Ci[1]) elif self._ref[2] == (2, 1, None): # Case 3b) # 1---2 +---> z # \ | # @ x v # # z = z2 - r*cos(theta*pi/180) A[2, 2] = A[1, 2] - Ci[0] * adf.cos(Ci[1]) else: # Should not reach here, the references are invalid raise ValueError("Invalid ZMAT reference evaluation") # Case 4: the general case for the fourth and later atoms else: # # # This atom (A[i]) is connected sequentially # to its references: i -> ref[0] -> ref[1] -> ref[2] A1 = A[self._ref[i][0] - 1] #`ref` contains 1-indexed values A2 = A[self._ref[i][1] - 1] A3 = A[self._ref[i][2] - 1] r21 = A2 - A1 r32 = A3 - A2 rC = np.cross(r21, r32) # Construct the local coordinate system axes U = r21 / adf.sqrt(r21[0] * r21[0] + r21[1] * r21[1] + r21[2] * r21[2]) V = rC / adf.sqrt(rC[0] * rC[0] + rC[1] * rC[1] + rC[2] * rC[2]) W = np.cross(U, V) r = Ci[0] th = Ci[1] phi = Ci[2] r_sth = r * adf.sin(th) t1 = r * adf.cos(th) # r cos(th) t2 = -r_sth * adf.sin(phi) # -r sin(th) sin(phi) t3 = -r_sth * adf.cos(phi) # -r sin(th) cos(phi) for j in range(3): A[i, j] = A1[j] + t1 * U[j] + t2 * V[j] + t3 * W[j] ################################################## # # All atom positions A are now calculated # # Place non-dummy derivative arrays in output k = 0 for i in range(nA): if self._notDummy[i]: # not a dummy # output atom k <-- ZMAT atom i for j in range(3): np.copyto(out[:, 3 * k + j], A[i, j].d) k += 1 # done! return out