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
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
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
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
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