Beispiel #1
0
 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) 
Beispiel #2
0
 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) 
Beispiel #3
0
 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 
Beispiel #4
0
def calc_V_matrix_adf(x):
    """
    Calculate the lower triangle (row order)
    of the diabatic matrix given adf input
    """
    a0 = n2.constants.a0
    
    RCC = x[0] / a0 # C--C bond length, convert angstroms to bohr
    RCH = x[1] / a0 # C--H bond length, convert angstroms to bohr
    theta = x[2]    # C--C--H angle, degrees
    
    n = Cijk.shape[0] # The number of expansion terms 
    
    # For each surface function
    v = []
    for p in range(4):
        
        dCC = RCC - RCC_ref[p]
        dCH = RCH - RCH_ref[p]
        dtheta = (theta - theta_ref[p]) * (n2.pi/180.0) # convert to radians
        
        dCC_pow = pows(dCC, round(max(Cijk[:,0])))
        dCH_pow = pows(dCH, round(max(Cijk[:,1])))
        dtheta_pow = pows(dtheta, round(max(Cijk[:,2])))
        
        vp = 0
        for i in range(n):
            
            c = Cijk[i,p + 3] # The coefficient of this surface
            
            vp += c * dCC_pow[round(Cijk[i,0])] * \
                      dCH_pow[round(Cijk[i,1])] * \
                      dtheta_pow[round(Cijk[i,2])]
                
        
        
        if p == 3: # The V12 surface
            vp *= adf.sin( dtheta ) * \
                  (1.0 - adf.tanh(dCC)) * (1.0 - adf.tanh(dCH))
        
        # Convert from Hartree to cm^-1
        vp *= n2.constants.Eh 
        
        v.append(vp)
    
    # v contains [V11, V22, V33, V12]
    # Now build the lower triangle
    # in row major order: 
    # V11
    # V21 = V12,  V22
    # V31 = 0, V32 = 0, V33
    #
    V31 = adf.const_like(0.0,v[0])
    V32 = adf.const_like(0.0,v[0])
    
    lower = [v[0], v[3], v[1], V31, V32, v[2]]
    
    return lower
Beispiel #5
0
    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
Beispiel #6
0
 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 
Beispiel #7
0
 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 
Beispiel #8
0
    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