Example #1
0
 def b_vector_for_positions(cls, *args):
     if sanity_checking_enabled:
         if len(args) != 4:
             raise TypeError
     #--------------------------------------------------------------------------------#
     # Check the cache first
     cache_resp = SimpleInternalCoordinate._check_b_tensor_cache(cls, args)
     if cache_resp:
         return cache_resp
     #--------------------------------------------------------------------------------#
     # Not in the cache...so compute it
     def e(j, k):
         ev = LightVector.l_sub(args[k-1], args[j-1]); ev.normalize()
         return ev
     def r(j, k):
         return LightVector.l_sub(args[k-1], args[j-1]).magnitude()
     e12, e23, e43 = e(1,2), e(2,3), e(4,3)
     e32 = -e23
     r12, r23, r43 = r(1,2), r(2, 3), r(3, 4)
     r32 = r23
     sin2phi2 = sin(angle_between_vectors(e12, e32))**2
     cosphi2 = cos(angle_between_vectors(e12, e32))
     sin2phi3 = sin(angle_between_vectors(e23, e43))**2
     cosphi3 = cos(angle_between_vectors(e23, e43))
     ret_val = [0,0,0,0]
     ret_val[0] = cross(e12, e23) * (-1.0 / (r12 * sin2phi2))
     ret_val[1] = ((r23 - r12 * cosphi2)/(r23*r12*sin2phi2)) * cross(e12, e23)\
                     + (cosphi3/(r23*sin2phi3)) * cross(e43, e32)
     ret_val[2] = ((r32 - r43 * cosphi3)/(r32*r43*sin2phi3)) * cross(e43, e32)\
                     + (cosphi2/(r32*sin2phi2)) * cross(e12, e23)
     ret_val[3] = cross(e43, e32) * (-1.0 / (r43 * sin2phi3))
     SimpleInternalCoordinate.b_tensor_cache[(cls,) + tuple(args)] = ret_val
     return ret_val
Example #2
0
    def b_vector_for_positions(cls, *args):
        if sanity_checking_enabled:
            if len(args) != 3:
                raise TypeError

        # Check the cache first
        cache_resp = SimpleInternalCoordinate._check_b_tensor_cache(cls, args)
        if cache_resp:
            return cache_resp

        # not in the cache, so compute it
        def e(j, k):
            ev = LightVector.l_sub(args[k - 1], args[j - 1])
            ev.normalize()
            return ev

        def r(j, k):
            return LightVector.l_sub(args[k - 1], args[j - 1]).magnitude()

        phi = cls.value_for_xyz(list(args))
        cosph = cos(phi)
        sinph = sin(phi)
        e21, e23 = e(2, 1), e(2, 3)
        s1 = (e21 * cosph - e23) / (r(2, 1) * sinph)
        s3 = (e23 * cosph - e21) / (r(2, 3) * sinph)
        ret_val = [s1, -s1 - s3, s3]
        SimpleInternalCoordinate.b_tensor_cache[(cls, ) +
                                                tuple(args)] = ret_val
        return s1, -s1 - s3, s3
Example #3
0
    def b_vector_for_positions(cls, *args):
        if sanity_checking_enabled:
            if len(args) != 3:
                raise TypeError

        # Check the cache first
        cache_resp = SimpleInternalCoordinate._check_b_tensor_cache(cls, args)
        if cache_resp:
            return cache_resp

        # not in the cache, so compute it
        def e(j, k):
            ev = LightVector.l_sub(args[k-1], args[j-1]); ev.normalize()
            return ev
        def r(j, k):
            return LightVector.l_sub(args[k-1], args[j-1]).magnitude()
        phi = cls.value_for_xyz(list(args))
        cosph = cos(phi)
        sinph = sin(phi)
        e21, e23 = e(2,1), e(2,3)
        s1 = (e21 * cosph - e23)/(r(2,1) * sinph)
        s3 = (e23 * cosph - e21)/(r(2,3) * sinph)
        ret_val = [s1, -s1-s3, s3]
        SimpleInternalCoordinate.b_tensor_cache[(cls,) + tuple(args)] = ret_val
        return s1, -s1-s3, s3
Example #4
0
    def analytic_b_tensor_for_order(self, order):
        #--------------------------------------------------------------------------------#
        # Now check the cache
        cache_key = (self.__class__, order) + tuple(a.pos for a in self.atoms)
        cache_resp = SimpleInternalCoordinate._check_b_tensor_cache(*cache_key)
        if cache_resp is not None:
            return cache_resp
        #--------------------------------------------------------------------------------#
        B = partial(SimpleInternalCoordinate.b_tensor_element_reindexed, self)
        #--------------------------------------------------------------------------------#
        # First order is already done elsewhere...
        if order == 1:
            # We can't use the `b_vector` attribute since that vector is indexed in the parent
            #   molecule's indexing scheme
            my_b = Vector(
                self.__class__.b_vector_for_positions(
                    *[a.pos for a in self.atoms]))
            # Return now, to avoid double-caching
            return my_b
        #--------------------------------------------------------------------------------#
        # Second order terms
        if order == 2:
            # BondAngles are composed of 3 atoms (3 CartesianCoordinates each), and the
            #   order is 2, so the output Tensor will be a 9x9 Tensor
            my_b = np.ndarray(shape=(9, ) * 2)
            # some precomputed values
            phi = self.value * self.units.to(Radians)
            cotphi = 1.0 / tan(phi)
            cscphi = 1.0 / sin(phi)
            #========================================#
            # see comments in BondLength version
            # First, handle the terminal atom entries
            for (a_idx, a), (c_idx, c) in product(zip([0, 2],
                                                      self.terminal_atoms),
                                                  repeat=2):
                # Generate the temporary coordinates
                if a_idx == 0:
                    Rab = BondLength(self.atoms[0], self.atoms[1])
                    Rbc = BondLength(self.atoms[1], self.atoms[2])
                else:  # a_idx == 2
                    Rab = BondLength(self.atoms[2], self.atoms[1])
                    Rbc = BondLength(self.atoms[1], self.atoms[0])
                #----------------------------------------#
                # Now iterate over the possible sets of cartesian coordinates
                for alpha, beta in product([X, Y, Z], repeat=2):
                    a_alpha, c_beta = a_idx * 3 + alpha, c_idx * 3 + beta
                    if a_idx == c_idx:
                        # From equation 19 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221
                        a_beta = c_beta
                        other_terminal_index = 2 if a_idx == 0 else 0
                        my_b[a_alpha, a_beta] = (
                            -cotphi * B(self, a_alpha) * B(self, a_beta) -
                            cscphi * sum(
                                B(Rab, a_idx * 3 + sigma, a_alpha, a_beta) *
                                B(Rbc, other_terminal_index * 3 + sigma)
                                for sigma in [X, Y, Z]))
                    else:
                        # From equation 20 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221
                        my_b[a_alpha, c_beta] = (
                            -cotphi * B(self, a_alpha) * B(self, c_beta) -
                            cscphi * sum(
                                B(Rab, a_idx * 3 + sigma, a_alpha) *
                                B(Rbc, c_idx * 3 + sigma, c_beta)
                                for sigma in [X, Y, Z]))
            # Now fill in the middle atom entries utilizing translational invariance
            # From equation 32 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221
            for alpha, beta in product([X, Y, Z], repeat=2):
                b_beta = 3 + beta
                for a_idx, a in zip([0, 2], self.terminal_atoms):
                    a_alpha = 3 * a_idx + alpha
                    my_b[b_beta, a_alpha] = my_b[a_alpha, b_beta] = \
                        -sum(my_b[a_alpha, t + beta]
                            for t in [0, 6] # 0 and 6 are the offsets for the terminal atoms
                        )
                # Now the b_alpha, b_beta entry:
                my_b[3 + alpha, 3 + beta] = sum(
                    my_b[atom1 * 3 + alpha, atom2 * 3 + beta]
                    for atom1, atom2 in product([0, 2], repeat=2))
        #--------------------------------------------------------------------------------#
        else:
            # behold, the general formula!
            # BondAngles are composed of 3 atoms (3 CartesianCoordinates each),
            #   so the output will be a 9x9x...x9 (`order`-dimensional) tensor
            my_b = Tensor(indices=','.join('v' * order),
                          index_range_set=self.__class__._btens_idx_range_set)
            #Bphi = self._btensor
            Rab = self.get_coord(BondLength, self.atoms[0], self.atoms[1])
            Rbc = self.get_coord(BondLength, self.atoms[1], self.atoms[2])
            #Brab = Rab._btensor
            #Brbc = Rbc._btensor
            #for o in xrange(1, order):
            #    t = Bphi.for_order(o)
            #    rab = Brab.for_order(o)
            #    rbc = Rbc._btensor.for_order(o)
            #    if isinstance(t, ComputableTensor): t.fill()
            #    if isinstance(rab, ComputableTensor): rab.fill()
            #    if isinstance(rbc, ComputableTensor): rab.fill()
            #Brab.for_order(order).fill()
            #Brbc.for_order(order).fill()
            #remap_set = IndexRangeSet()
            #DeclareIndexRange('v', 6, index_range_set=remap_set).with_subranges(
            #    IndexRange('b', 3, 6, index_range_set=remap_set),
            #    IndexRange('c', 0, 3, index_range_set=remap_set)
            #)
            ##Brbc = DerivativeCollection(coordinate=Rbc, einsum_index='v', index_range_set=remap_set)
            #ba = Vector([Brab[(0,) + (Ellipsis,)*order]])
            #for o in xrange(1, order+1):
            #    Brbc.for_order(o).index_range_set = remap_set
            ## some precomputed values
            phi = self.value * self.units.to(Radians)
            cotphi = 1.0 / tan(phi)
            cscphi = 1.0 / sin(phi)

            #========================================#
            # First take care of the terminal atoms...
            def f_K(k):
                if k % 2 == 1:
                    if (k + 1) / 2 % 2 == 0:
                        return 1
                    else:  # (k+1)/2 % 2 == 1
                        return -1
                elif k / 2 % 2 == 0:
                    return cotphi
                else:  # k/2 % 2 == 1
                    return -cotphi

            #----------------------------------------#
            a_idx, c_idx = 0, 2
            for num_a_coords in xrange(0, order + 1):
                # outer loop over possible alphas
                for alphas in product([X, Y, Z], repeat=order):
                    a_alphas = tuple(3 * a_idx + alpha
                                     for alpha in alphas[:num_a_coords])
                    c_alphas = tuple(3 * c_idx + alpha
                                     for alpha in alphas[num_a_coords:])
                    # Now we have all of the specifics for the left-hand side, so compute the
                    #   right-hand side to go with it...
                    cum_sum = 0.0
                    for sigma in [X, Y, Z]:
                        cum_sum += B(Rab, 3 * a_idx + sigma, *a_alphas) * B(
                            Rbc, 3 * c_idx + sigma, *c_alphas)
                    cum_sum *= -cscphi
                    for k in range(2, order + 1):
                        inner_sum = 0.0
                        for s in I_lubar(order, k, a_alphas + c_alphas):
                            prod = 1.0
                            for i in range(k):
                                prod *= B(self, *s[i])
                            inner_sum += prod
                        cum_sum += f_K(k) * inner_sum
                    # Spread over permutations
                    for idxs in permutations(a_alphas + c_alphas):
                        my_b[idxs] = cum_sum
            #========================================#
            # now compute the terms involving the middle atom
            a1_idx, a2_idx, a3_idx = 0, 1, 2
            for num_a2s in xrange(1, order + 1):
                # Now fill in the middle atom entries utilizing translational invariance
                # From equation 32 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221
                for first_a2_position in xrange(0, order - num_a2s + 1):
                    for alphas in product([X, Y, Z], repeat=order):
                        a1_alphas = tuple(
                            3 * a1_idx + alpha
                            for alpha in alphas[:first_a2_position])
                        middle_alphas = alphas[
                            first_a2_position:first_a2_position + num_a2s]
                        a2_alphas = tuple(3 * a2_idx + alpha
                                          for alpha in middle_alphas)
                        a3_alphas = tuple(
                            3 * a3_idx + alpha
                            for alpha in alphas[first_a2_position + num_a2s:])
                        val_to_spread = 0.0
                        for midatoms in product([a1_idx, a3_idx],
                                                repeat=num_a2s):
                            idxs = a1_alphas
                            idxs += tuple(ai * 3 + middle_alphas[i]
                                          for i, ai in enumerate(midatoms))
                            idxs += a3_alphas
                            val_to_spread += my_b[idxs]
                        if num_a2s % 2 == 1:
                            val_to_spread *= -1.0
                        for perm in permutations(a1_alphas + a2_alphas +
                                                 a3_alphas):
                            my_b[perm] = val_to_spread
        #--------------------------------------------------------------------------------#
        # Cache the value we got
        SimpleInternalCoordinate._set_b_tensor_cache_entry(my_b, *cache_key)
        #--------------------------------------------------------------------------------#
        return my_b
Example #5
0
 def analytic_b_tensor_for_order(self, order):
     # First check the cache
     cache_key = (self.__class__, order) + tuple(a.pos for a in self.atoms)
     cache_resp = SimpleInternalCoordinate._check_b_tensor_cache(*cache_key)
     if cache_resp is not None:
         return cache_resp
     #--------------------------------------------------------------------------------#
     B = partial(SimpleInternalCoordinate.b_tensor_element_reindexed, self)
     #--------------------------------------------------------------------------------#
     # First order is already done elsewhere...
     if order == 1:
         # We can't use the `b_vector` attribute since that vector is indexed in the parent
         #   molecule's indexing scheme
         my_b = Vector(self.__class__.b_vector_for_positions(*[a.pos for a in self.atoms]))
         # Return now, to avoid double-caching
         return my_b
     #--------------------------------------------------------------------------------#
     # TODO Explicit (matrix/tensor based) implementations of 2nd, 3rd, and 4th order to speed things up substantially
     else:
         # We only have general formulas for terminal atoms as of now...
         # So we can save a little bit of time by computing these terms and
         #   then doing only finite difference for everything else
         # Torsions are composed of 4 atoms (3 CartesianCoordinates each),
         #   so the output will be a 12x12x...x12 (`order`-dimensional) tensor
         my_b = np.ndarray(shape=(12,)*order)
         if sanity_checking_enabled:
             my_b = np.ones(shape=(12,)*order) * float('inf')
         # some precomputed values
         #========================================#
         # Helper function needed for derivative:
         def Bsin2phi(phi, *idx_alphas):
             # some precomputed values
             twophi = 2.0*phi.value*phi.units.to(Radians)
             sin2phi = sin(twophi)
             cos2phi = cos(twophi)
             def h_K(k):
                 if k % 2 == 1:
                     if ((k-1) / 2) % 2 == 0:
                         return cos2phi
                     else:
                         return -cos2phi
                 else: # k is even
                     if k/2 % 2 == 0:
                         return sin2phi
                     else:
                         return -sin2phi
             cumsum = 0.0
             n = len(idx_alphas)
             for k in xrange(1, n + 1):
                 inner_sum = 0.0
                 for s in I_lubar(n, k, idx_alphas):
                     inner_prod = 1.0
                     for i in range(k):
                         inner_prod *= B(phi, *s[i])
                     inner_sum += inner_prod
                 cumsum += 2.0**k * h_K(k) * inner_sum
             return cumsum
         #========================================#
         # The aaaa...bbbbb...cccc... terms
         phis = []
         rbcs = []
         for a_idx, a in zip([0, 3], self.terminal_atoms):
             # Determine whihc terminal atom we're handling
             if a_idx == 0:
                 b_idx, c_idx, d_idx = 1, 2, 3
             else: # a_idx == 3
                 b_idx, c_idx, d_idx = 2, 1, 0
             #----------------------------------------#
             phi_abc = self.get_coord(BondAngle,
                 self.atoms[a_idx],
                 self.atoms[b_idx],
                 self.atoms[c_idx],
             )
             # units of Radians
             ang_conv = phi_abc.units.to(Radians)
             # TODO figure out what 'units' should be here
             rbc = self.get_coord(BondLength, self.atoms[b_idx], self.atoms[c_idx])
             # Keep them for later
             phis.append(phi_abc)
             rbcs.append(rbc)
             #----------------------------------------#
             # some precomputed values
             sin2phi = sin(2.0 * phi_abc.value * ang_conv)
             #----------------------------------------#
             for num_a, num_b, num_c in unlabeled_balls_in_labeled_boxes(order-1, [order-1]*3):
                 num_a += 1
                 alph_iter = product(*map(lambda n: symmetric_product([X,Y,Z], n), (num_a, num_b, num_c)))
                 for alphas_a, alphas_b, alphas_c in alph_iter:
                     a_alphas = tuple(3*a_idx + alpha for alpha in alphas_a)
                     b_alphas = tuple(3*b_idx + alpha for alpha in alphas_b)
                     c_alphas = tuple(3*c_idx + alpha for alpha in alphas_c)
                     all_alphas = a_alphas + b_alphas + c_alphas
                     cum_sum = 0.0
                     for t1, t2 in I_ll(num_b + num_c, 2, b_alphas + c_alphas):
                         bphi = LightVector([
                             B(phi_abc, 3*a_idx + sigma, *(a_alphas[1:] + t1))
                                 for sigma in [X, Y, Z]
                         ])
                         brbc = LightVector([
                             B(rbc, 3*c_idx + sigma, *t2) for sigma in [X, Y, Z]]
                         )
                         cum_sum += cross(bphi, brbc)[alphas_a[0]]
                     cum_sum *= 2.0
                     cum_sum -= B(self, a_alphas[0]) * Bsin2phi(phi_abc, *all_alphas[1:])
                     for s1, s2 in I_llbar(num_a - 1 + num_b + num_c, 2, all_alphas[1:]):
                         cum_sum -= Bsin2phi(phi_abc, *s1) * B(self, *((a_alphas[0],) + s2))
                     cum_sum /= sin2phi
                     for perm in permutations(all_alphas):
                         my_b[perm] = cum_sum
         #========================================#
         # Note that the terminal-atom cross-derivatives are 0
         if sanity_checking_enabled:
             # Fill in the explicity zeros now, since we had infinity there to make sure
             #   uncomputed values weren't being used for something else.
             for a_idx, d_idx in permutations([0, 3]):
                 for remaining_idxs in product([0, 1, 2, 3], repeat=order-2):
                     for alphas in product([X, Y, Z], repeat=order):
                         idx_alphas = tuple(3*atom_idx + alpha
                                 for atom_idx, alpha in zip((a_idx, d_idx) + remaining_idxs, alphas))
                         for perm in permutations(idx_alphas):
                             my_b[perm] = 0.0
         #========================================#
         # Arbitrary order bbbbb.... terms
         a_idx, b_idx, c_idx, d_idx = 0, 1, 2, 3
         phi_abc = phis[0]
         phi_bcd = self.get_coord(BondAngle,
             self.atoms[b_idx],
             self.atoms[c_idx],
             self.atoms[d_idx],
         )
         ang_conv = phi_bcd.units.to(Radians)
         # TODO figure out what 'units' should be here
         r_ba = self.get_coord(BondLength,
             self.atoms[b_idx],
             self.atoms[a_idx]
         )
         r_cd = self.get_coord(BondLength,
             self.atoms[c_idx],
             self.atoms[d_idx]
         )
         #----------------------------------------#
         def Bcscphi(phi, *b_alphas):
             phi_val = phi.value * phi.units.to(Radians)
             sinphi = sin(phi_val)
             cscphi = 1.0 / sinphi
             if len(b_alphas) == 0:
                 return cscphi
             cotphi = cos(phi_val) / sinphi
             #------------------------------------#
             def dcsc_n(n):
                 def t(n_t, k):
                     if k == 0:
                         return 1
                     elif k <= n_t//2:
                         return (2*k + 1) * t(n_t-1, k) + (n_t - 2*k + 1) * t(n_t-1, k-1)
                     else:
                         return 0
                 #--------------------------------#
                 ret_val = 0.0
                 for kk in xrange(n//2 + 1):
                     ret_val += t(n, kk) * cotphi**(n - 2*kk) * cscphi**(2*kk + 1)
                 if n % 2 == 1:
                     return -ret_val
                 else:
                     return ret_val
             #------------------------------------#
             outer_sum = 0.0
             for k in xrange(1, len(b_alphas) + 1):
                 inner_sum = 0.0
                 for idx_sets in labeled_balls_in_unlabeled_boxes(len(b_alphas), [len(b_alphas)]*k):
                     if any(len(st) == 0 for st in idx_sets):
                         continue
                     b_idx_sets = tuple(tuple(b_alphas[i] for i in idxset) for idxset in idx_sets)
                     product = 1.0
                     for b_idxs in b_idx_sets:
                         product *= B(phi, *b_idxs)
                     inner_sum += product
                 outer_sum += dcsc_n(k) * inner_sum
             return outer_sum
         #----------------------------------------#
         for alphas in symmetric_product([X, Y, Z], order):
             # here we go...
             term_sum = first_term = second_term = third_term = 0.0
             iter_alphas = alphas[1:]
             b_alphas = tuple(3*b_idx + alpha for alpha in alphas)
             #----------------------------------------#
             for b_alphas1, b_alphas2, b_alphas3 in I_ll(order-1, 3, b_alphas[1:]):
                 #----------------------------------------#
                 # preconstruct the vectors we need for the cross products
                 b_a_phi_abc = LightVector([
                     B(phi_abc, 3*a_idx + sigma, *b_alphas2) for sigma in [X, Y, Z]
                 ])
                 b_c_phi_abc = LightVector([
                     B(phi_abc, 3*c_idx + sigma, *b_alphas2) for sigma in [X, Y, Z]
                 ])
                 b_a_r_ba = LightVector([
                     B(r_ba, 3*a_idx + sigma, *b_alphas3) for sigma in [X, Y, Z]
                 ])
                 #----------------------------------------#
                 # save a little bit of time by only computing this part once per iteration
                 Bcscphi_abc = Bcscphi(phi_abc, *b_alphas1)
                 #----------------------------------------#
                 # now add the contribution from this set of indices
                 first_term += Bcscphi_abc * cross(b_a_phi_abc, b_a_r_ba)[alphas[0]]
                 second_term += Bcscphi_abc * cross(b_c_phi_abc, b_a_r_ba)[alphas[0]]
             #----------------------------------------#
             term_sum -= first_term + second_term
             b_d_r_cd = LightVector([
                 B(r_cd, 3*d_idx + sigma) for sigma in [X, Y, Z]
             ])
             for b_alphas1, b_alphas2 in I_ll(order-1, 2, b_alphas[1:]):
                 #----------------------------------------#
                 b_b_phi_bcd = LightVector([
                     B(phi_bcd, 3*b_idx + sigma, *b_alphas2) for sigma in [X, Y, Z]
                 ])
                 #----------------------------------------#
                 third_term += Bcscphi(phi_bcd, *b_alphas1) * cross(b_b_phi_bcd, b_d_r_cd)[alphas[0]]
             term_sum += third_term
             #----------------------------------------#
             # and spread it across permutations
             for perm in permutations(tuple(3*b_idx + alpha for alpha in alphas)):
                 my_b[perm] = term_sum
         #========================================#
         # and fill in the bbbb...cccc... derivatives by translational invariance
         # Range from one c index to all
         for num_c in xrange(1, order+1):
             num_b = order - num_c
             alph_iter = product(*map(lambda n: symmetric_product([X,Y,Z], n), (num_b, num_c)))
             for alphas_b, alphas_c in alph_iter:
                 b_alphas = tuple(3*b_idx + alph for alph in alphas_b)
                 c_alphas = tuple(3*c_idx + alph for alph in alphas_c)
                 alphas_all = alphas_b + alphas_c
                 currsum = 0.0
                 for repl_atoms in product([a_idx, b_idx, d_idx], repeat=num_c):
                     repl_alphas = tuple(3*repl_atom + alph
                         for repl_atom, alph in zip(repl_atoms, alphas_c))
                     currsum += my_b[repl_alphas + b_alphas]
                     if sanity_checking_enabled and math.isinf(my_b[b_alphas + repl_alphas]):
                         raise IndexError("indices not filled in: {}, needed for {}".format(
                             b_alphas + repl_alphas,
                             b_alphas + c_alphas
                         ))
                 if num_c % 2 == 1:
                     currsum *= -1.0
                 #----------------------------------------#
                 # and spread this value over all permutations...
                 for perm in permutations(b_alphas + c_alphas):
                     my_b[perm] = currsum
     #--------------------------------------------------------------------------------#
     # Cache the value we got
     SimpleInternalCoordinate._set_b_tensor_cache_entry(my_b, *cache_key)
     #--------------------------------------------------------------------------------#
     return my_b
Example #6
0
 def analytic_b_tensor_for_order(self, order):
     # First check the cache
     cache_key = (self.__class__, order) + tuple(a.pos for a in self.atoms)
     cache_resp = SimpleInternalCoordinate._check_b_tensor_cache(*cache_key)
     if cache_resp is not None:
         return cache_resp
     #--------------------------------------------------------------------------------#
     # Define a function that acts like the B tensor for getting lower-order terms
     B = partial(SimpleInternalCoordinate.b_tensor_element_reindexed, self)
     #--------------------------------------------------------------------------------#
     # First order is trivial...
     if order == 1:
         # We can't use the `b_vector` attribute since that vector is indexed in the parent
         #   molecule's indexing scheme
         my_b = Vector(
             self.__class__.b_vector_for_positions(
                 *[a.pos for a in self.atoms]))
         # return early to avoid double-caching, since b_vector_for_positions caches
         #   the result in the B tensor cache already
         return my_b
     #--------------------------------------------------------------------------------#
     # Second order terms
     elif order == 2:
         # BondLengths are composed of 2 atoms (3 CartesianCoordinates each), and the
         #   order is 2, so the output Tensor will be a 6x6 Tensor
         my_b = np.ndarray(shape=(6, ) * 2)
         Rabinv = 1.0 / self.value
         # This uses the itertools.product function, which acts like
         #   a nested loop (with depth given by the 'repeat' parameter).
         #   Mostly, I used this here just to keep the code indentation
         #   from getting out of hand (particularly in the case of the
         #   Torsion coordinate).  Also, though, this is useful for the
         #   general coordinate formulas.
         # First do the "same atom" derivatives
         for a_idx, a in enumerate(self.atoms):
             # Now iterate over the possible sets of cartesian coordinates
             for alpha, beta in symmetric_product([X, Y, Z], 2):
                 a_alpha, a_beta = 3 * a_idx + alpha, 3 * a_idx + beta
                 if alpha != beta:
                     my_b[a_alpha, a_beta] = -Rabinv * (B(self, a_alpha) *
                                                        B(self, a_beta))
                     my_b[a_beta, a_alpha] = -Rabinv * (B(self, a_alpha) *
                                                        B(self, a_beta))
                 else:
                     my_b[a_alpha, a_beta] = -Rabinv * (
                         B(self, a_alpha) * B(self, a_beta) - 1)
         # Now get the "different atom" derivatives from the "same atom ones"
         for alpha in [X, Y, Z]:
             for beta in [X, Y, Z]:
                 a_alpha = alpha
                 b_beta = 3 + beta
                 my_b[a_alpha,
                      b_beta] = my_b[b_beta,
                                     a_alpha] = (-1) * my_b[alpha, beta]
     #--------------------------------------------------------------------------------#
     else:
         # Behold, the general formula!
         # BondLengths are composed of 2 atoms (3 CartesianCoordinates each),
         #   so the output Tensor will be a 6x6x...x6 (`order`-dimensional) Tensor
         my_b = Tensor(indices=','.join('v' * order),
                       index_range_set=self.__class__._btens_idx_range_set)
         # New, fancy way with einstein summation (if I can get it to work)
         #B = self._btensor
         #for o in xrange(1, order):
         #    t = B.for_order(o)
         #    if isinstance(t, ComputableTensor):
         #        t.fill()
         # First do the "same atom" derivatives
         #def idxs(letter, num): return tuple(letter + '_' + str(x) for x in xrange(num))
         #aa = idxs('a', order)
         #bb = idxs('b', order)
         #for s in I_lubar(order, 2, aa):
         #    my_b[aa] += B[s[0]] * B[s[1]]
         #for s in I_lubar(order, 2, bb):
         #    my_b[bb] += B[s[0]] * B[s[1]]
         #Rabinv = 1.0 / self.value
         #my_b[aa] *= -Rabinv
         #my_b[bb] *= -Rabinv
         ## Now get the "different atom" derivatives from the "same atom ones"
         #for nacarts in xrange(1, order):
         #    aprefactor =  -1.0 if (order - nacarts) % 2 == 1 else 1.0
         #    for a_b_set in set(permutations("a"*nacarts + "b"*(order-nacarts))):
         #        ab = tuple(map(lambda x: x[0] + "_" + str(x[1]), zip(a_b_set, xrange(order))))
         #        my_b[ab] = aprefactor * my_b[aa]
         # old way...
         Rabinv = 1.0 / self.value
         for a_idx, a in enumerate(self.atoms):
             # Now iterate over the possible sets of cartesian coordinates
             for alphas in symmetric_product([X, Y, Z], order):
                 a_alphas = tuple(3 * a_idx + alpha for alpha in alphas)
                 cumval = 0.0
                 for a_alps, a_bets in I_lubar(order, 2, a_alphas):
                     cumval += B(self, *a_alps) * B(self, *a_bets)
                 val_to_spread = -Rabinv * cumval
                 # and spread over permutations
                 for idxs in permutations(a_alphas):
                     my_b[idxs] = val_to_spread
         # Now get the "different atom" derivatives from the "same atom ones"
         #   (From eq. 31 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221)
         for alphas in product([X, Y, Z], repeat=order):
             for nacarts in xrange(1, order):
                 acoords = tuple(acart for acart in alphas[:nacarts])
                 bcoords = tuple(3 + bcart for bcart in alphas[nacarts:])
                 aprefactor = -1.0 if (order - nacarts) % 2 == 1 else 1.0
                 val_to_spread = my_b[alphas]
                 for perm in permutations(acoords + bcoords):
                     my_b[perm] = aprefactor * val_to_spread
     #--------------------------------------------------------------------------------#
     # Whew...that was tough.  Let's cache the value we got
     SimpleInternalCoordinate._set_b_tensor_cache_entry(my_b, *cache_key)
     #--------------------------------------------------------------------------------#
     return my_b
Example #7
0
 def analytic_b_tensor_for_order(self, order):
     #--------------------------------------------------------------------------------#
     # Now check the cache
     cache_key = (self.__class__, order) + tuple(a.pos for a in self.atoms)
     cache_resp = SimpleInternalCoordinate._check_b_tensor_cache(*cache_key)
     if cache_resp is not None:
         return cache_resp
     #--------------------------------------------------------------------------------#
     B = partial(SimpleInternalCoordinate.b_tensor_element_reindexed, self)
     #--------------------------------------------------------------------------------#
     # First order is already done elsewhere...
     if order == 1:
         # We can't use the `b_vector` attribute since that vector is indexed in the parent
         #   molecule's indexing scheme
         my_b = Vector(self.__class__.b_vector_for_positions(*[a.pos for a in self.atoms]))
         # Return now, to avoid double-caching
         return my_b
     #--------------------------------------------------------------------------------#
     # Second order terms
     if order == 2:
         # BondAngles are composed of 3 atoms (3 CartesianCoordinates each), and the
         #   order is 2, so the output Tensor will be a 9x9 Tensor
         my_b = np.ndarray(shape=(9,)*2)
         # some precomputed values
         phi = self.value * self.units.to(Radians)
         cotphi = 1.0 / tan(phi)
         cscphi = 1.0 / sin(phi)
         #========================================#
         # see comments in BondLength version
         # First, handle the terminal atom entries
         for (a_idx, a), (c_idx, c) in product(zip([0, 2], self.terminal_atoms), repeat=2):
             # Generate the temporary coordinates
             if a_idx == 0:
                 Rab = BondLength(self.atoms[0], self.atoms[1])
                 Rbc = BondLength(self.atoms[1], self.atoms[2])
             else: # a_idx == 2
                 Rab = BondLength(self.atoms[2], self.atoms[1])
                 Rbc = BondLength(self.atoms[1], self.atoms[0])
             #----------------------------------------#
             # Now iterate over the possible sets of cartesian coordinates
             for alpha, beta in product([X, Y, Z], repeat=2):
                 a_alpha, c_beta = a_idx*3 + alpha, c_idx*3 + beta
                 if a_idx == c_idx:
                     # From equation 19 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221
                     a_beta = c_beta
                     other_terminal_index = 2 if a_idx == 0 else 0
                     my_b[a_alpha, a_beta] = (
                         -cotphi * B(self, a_alpha) * B(self, a_beta)
                         - cscphi * sum(
                             B(Rab, a_idx*3 + sigma, a_alpha, a_beta)
                                     * B(Rbc, other_terminal_index*3 + sigma)
                                 for sigma in [X, Y, Z]
                         )
                     )
                 else:
                     # From equation 20 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221
                     my_b[a_alpha, c_beta] = (
                         -cotphi * B(self, a_alpha) * B(self, c_beta)
                         - cscphi * sum(
                             B(Rab, a_idx*3 + sigma, a_alpha)
                                     * B(Rbc, c_idx*3 + sigma, c_beta)
                                 for sigma in [X, Y, Z]
                         )
                     )
         # Now fill in the middle atom entries utilizing translational invariance
         # From equation 32 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221
         for alpha, beta in product([X, Y, Z], repeat=2):
             b_beta = 3 + beta
             for a_idx, a in zip([0, 2], self.terminal_atoms):
                 a_alpha = 3*a_idx + alpha
                 my_b[b_beta, a_alpha] = my_b[a_alpha, b_beta] = \
                     -sum(my_b[a_alpha, t + beta]
                         for t in [0, 6] # 0 and 6 are the offsets for the terminal atoms
                     )
             # Now the b_alpha, b_beta entry:
             my_b[3 + alpha, 3 + beta] = sum(my_b[atom1*3 + alpha, atom2*3 + beta]
                                             for atom1, atom2 in product([0,2], repeat=2)
                                         )
     #--------------------------------------------------------------------------------#
     else:
         # behold, the general formula!
         # BondAngles are composed of 3 atoms (3 CartesianCoordinates each),
         #   so the output will be a 9x9x...x9 (`order`-dimensional) tensor
         my_b = Tensor(
             indices=','.join('v'*order),
             index_range_set=self.__class__._btens_idx_range_set
         )
         #Bphi = self._btensor
         Rab = self.get_coord(BondLength, self.atoms[0], self.atoms[1])
         Rbc = self.get_coord(BondLength, self.atoms[1], self.atoms[2])
         #Brab = Rab._btensor
         #Brbc = Rbc._btensor
         #for o in xrange(1, order):
         #    t = Bphi.for_order(o)
         #    rab = Brab.for_order(o)
         #    rbc = Rbc._btensor.for_order(o)
         #    if isinstance(t, ComputableTensor): t.fill()
         #    if isinstance(rab, ComputableTensor): rab.fill()
         #    if isinstance(rbc, ComputableTensor): rab.fill()
         #Brab.for_order(order).fill()
         #Brbc.for_order(order).fill()
         #remap_set = IndexRangeSet()
         #DeclareIndexRange('v', 6, index_range_set=remap_set).with_subranges(
         #    IndexRange('b', 3, 6, index_range_set=remap_set),
         #    IndexRange('c', 0, 3, index_range_set=remap_set)
         #)
         ##Brbc = DerivativeCollection(coordinate=Rbc, einsum_index='v', index_range_set=remap_set)
         #ba = Vector([Brab[(0,) + (Ellipsis,)*order]])
         #for o in xrange(1, order+1):
         #    Brbc.for_order(o).index_range_set = remap_set
         ## some precomputed values
         phi = self.value * self.units.to(Radians)
         cotphi = 1.0 / tan(phi)
         cscphi = 1.0 / sin(phi)
         #========================================#
         # First take care of the terminal atoms...
         def f_K(k):
             if k % 2 == 1:
                 if (k+1)/2 % 2 == 0:
                     return 1
                 else: # (k+1)/2 % 2 == 1
                     return -1
             elif k/2 % 2 == 0:
                 return cotphi
             else: # k/2 % 2 == 1
                 return -cotphi
         #----------------------------------------#
         a_idx, c_idx = 0, 2
         for num_a_coords in xrange(0, order+1):
             # outer loop over possible alphas
             for alphas in product([X, Y, Z], repeat=order):
                 a_alphas = tuple(3*a_idx + alpha for alpha in alphas[:num_a_coords])
                 c_alphas = tuple(3*c_idx + alpha for alpha in alphas[num_a_coords:])
                 # Now we have all of the specifics for the left-hand side, so compute the
                 #   right-hand side to go with it...
                 cum_sum = 0.0
                 for sigma in [X, Y, Z]:
                     cum_sum += B(Rab, 3*a_idx + sigma, *a_alphas) * B(Rbc, 3*c_idx+sigma, *c_alphas)
                 cum_sum *= -cscphi
                 for k in range(2, order+1):
                     inner_sum = 0.0
                     for s in I_lubar(order, k, a_alphas + c_alphas):
                         prod = 1.0
                         for i in range(k):
                             prod *= B(self, *s[i])
                         inner_sum += prod
                     cum_sum += f_K(k) * inner_sum
                 # Spread over permutations
                 for idxs in permutations(a_alphas + c_alphas):
                     my_b[idxs] = cum_sum
         #========================================#
         # now compute the terms involving the middle atom
         a1_idx, a2_idx, a3_idx = 0, 1, 2
         for num_a2s in xrange(1, order+1):
             # Now fill in the middle atom entries utilizing translational invariance
             # From equation 32 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221
             for first_a2_position in xrange(0, order - num_a2s + 1):
                 for alphas in product([X, Y, Z], repeat=order):
                     a1_alphas = tuple(3*a1_idx + alpha for alpha in alphas[:first_a2_position])
                     middle_alphas = alphas[first_a2_position:first_a2_position+num_a2s]
                     a2_alphas = tuple(3*a2_idx + alpha for alpha in middle_alphas)
                     a3_alphas = tuple(3*a3_idx + alpha
                                           for alpha in alphas[first_a2_position+num_a2s:])
                     val_to_spread = 0.0
                     for midatoms in product([a1_idx, a3_idx], repeat=num_a2s):
                         idxs = a1_alphas
                         idxs += tuple(ai*3 + middle_alphas[i] for i, ai in enumerate(midatoms))
                         idxs += a3_alphas
                         val_to_spread += my_b[idxs]
                     if num_a2s % 2 == 1:
                         val_to_spread *= -1.0
                     for perm in permutations(a1_alphas + a2_alphas + a3_alphas):
                         my_b[perm] = val_to_spread
     #--------------------------------------------------------------------------------#
     # Cache the value we got
     SimpleInternalCoordinate._set_b_tensor_cache_entry(my_b, *cache_key)
     #--------------------------------------------------------------------------------#
     return my_b
Example #8
0
 def analytic_b_tensor_for_order(self, order):
     # First check the cache
     cache_key = (self.__class__, order) + tuple(a.pos for a in self.atoms)
     cache_resp = SimpleInternalCoordinate._check_b_tensor_cache(*cache_key)
     if cache_resp is not None:
         return cache_resp
     #--------------------------------------------------------------------------------#
     # Define a function that acts like the B tensor for getting lower-order terms
     B = partial(SimpleInternalCoordinate.b_tensor_element_reindexed, self)
     #--------------------------------------------------------------------------------#
     # First order is trivial...
     if order == 1:
         # We can't use the `b_vector` attribute since that vector is indexed in the parent
         #   molecule's indexing scheme
         my_b = Vector(self.__class__.b_vector_for_positions(*[a.pos for a in self.atoms]))
         # return early to avoid double-caching, since b_vector_for_positions caches
         #   the result in the B tensor cache already
         return my_b
     #--------------------------------------------------------------------------------#
     # Second order terms
     elif order == 2:
         # BondLengths are composed of 2 atoms (3 CartesianCoordinates each), and the
         #   order is 2, so the output Tensor will be a 6x6 Tensor
         my_b = np.ndarray(shape=(6,)*2)
         Rabinv = 1.0 / self.value
         # This uses the itertools.product function, which acts like
         #   a nested loop (with depth given by the 'repeat' parameter).
         #   Mostly, I used this here just to keep the code indentation
         #   from getting out of hand (particularly in the case of the
         #   Torsion coordinate).  Also, though, this is useful for the
         #   general coordinate formulas.
         # First do the "same atom" derivatives
         for a_idx, a in enumerate(self.atoms):
             # Now iterate over the possible sets of cartesian coordinates
             for alpha, beta in symmetric_product([X, Y, Z], 2):
                 a_alpha, a_beta = 3*a_idx + alpha, 3*a_idx + beta
                 if alpha != beta:
                     my_b[a_alpha, a_beta] = - Rabinv * (B(self, a_alpha) * B(self, a_beta))
                     my_b[a_beta, a_alpha] = - Rabinv * (B(self, a_alpha) * B(self, a_beta))
                 else:
                     my_b[a_alpha, a_beta] = - Rabinv * (B(self, a_alpha) * B(self, a_beta) - 1)
         # Now get the "different atom" derivatives from the "same atom ones"
         for alpha in [X, Y, Z]:
             for beta in [X, Y, Z]:
                 a_alpha = alpha
                 b_beta = 3 + beta
                 my_b[a_alpha, b_beta] = my_b[b_beta, a_alpha] = (-1) * my_b[alpha, beta]
     #--------------------------------------------------------------------------------#
     else:
         # Behold, the general formula!
         # BondLengths are composed of 2 atoms (3 CartesianCoordinates each),
         #   so the output Tensor will be a 6x6x...x6 (`order`-dimensional) Tensor
         my_b = Tensor(
             indices=','.join('v'*order),
             index_range_set=self.__class__._btens_idx_range_set
         )
         # New, fancy way with einstein summation (if I can get it to work)
         #B = self._btensor
         #for o in xrange(1, order):
         #    t = B.for_order(o)
         #    if isinstance(t, ComputableTensor):
         #        t.fill()
         # First do the "same atom" derivatives
         #def idxs(letter, num): return tuple(letter + '_' + str(x) for x in xrange(num))
         #aa = idxs('a', order)
         #bb = idxs('b', order)
         #for s in I_lubar(order, 2, aa):
         #    my_b[aa] += B[s[0]] * B[s[1]]
         #for s in I_lubar(order, 2, bb):
         #    my_b[bb] += B[s[0]] * B[s[1]]
         #Rabinv = 1.0 / self.value
         #my_b[aa] *= -Rabinv
         #my_b[bb] *= -Rabinv
         ## Now get the "different atom" derivatives from the "same atom ones"
         #for nacarts in xrange(1, order):
         #    aprefactor =  -1.0 if (order - nacarts) % 2 == 1 else 1.0
         #    for a_b_set in set(permutations("a"*nacarts + "b"*(order-nacarts))):
         #        ab = tuple(map(lambda x: x[0] + "_" + str(x[1]), zip(a_b_set, xrange(order))))
         #        my_b[ab] = aprefactor * my_b[aa]
         # old way...
         Rabinv = 1.0 / self.value
         for a_idx, a in enumerate(self.atoms):
             # Now iterate over the possible sets of cartesian coordinates
             for alphas in symmetric_product([X, Y, Z], order):
                 a_alphas = tuple(3*a_idx + alpha for alpha in alphas)
                 cumval = 0.0
                 for a_alps, a_bets in I_lubar(order, 2, a_alphas):
                     cumval += B(self, *a_alps) * B(self, *a_bets)
                 val_to_spread = -Rabinv * cumval
                 # and spread over permutations
                 for idxs in permutations(a_alphas):
                     my_b[idxs] = val_to_spread
         # Now get the "different atom" derivatives from the "same atom ones"
         #   (From eq. 31 in Allen, et al. Mol. Phys. 89 (1996), 1213-1221)
         for alphas in product([X, Y, Z], repeat=order):
             for nacarts in xrange(1, order):
                 acoords = tuple(acart for acart in alphas[:nacarts])
                 bcoords = tuple(3 + bcart for bcart in alphas[nacarts:])
                 aprefactor =  -1.0 if (order - nacarts) % 2 == 1 else 1.0
                 val_to_spread = my_b[alphas]
                 for perm in permutations(acoords + bcoords):
                     my_b[perm] = aprefactor * val_to_spread
     #--------------------------------------------------------------------------------#
     # Whew...that was tough.  Let's cache the value we got
     SimpleInternalCoordinate._set_b_tensor_cache_entry(my_b, *cache_key)
     #--------------------------------------------------------------------------------#
     return my_b