def test_2_torsions_4_dry_run(self): mol = Molecule.from_z_matrix(''' O H 1 0.93 C 2 1.23 1 104.5 B 3 1.35 2 89.5 1 83.2 O 4 1.25 3 89.5 2 -23.2 ''', create_representation=True) mol.recenter() order = 4 coord = mol.internal_representation[5] natoms = mol.natoms btens = coord.parent_representation.b_tensor() analytic_tensor = Tensor(shape=(3 * natoms, ) * order) for cartcoords in product(coord.variables, repeat=order): analytic_tensor[tuple( c.index for c in cartcoords)] = btens[(coord, ) + cartcoords] coord = mol.internal_representation[8] natoms = mol.natoms btens = coord.parent_representation.b_tensor() analytic_tensor = Tensor(shape=(3 * natoms, ) * order) for cartcoords in product(coord.variables, repeat=order): analytic_tensor[tuple( c.index for c in cartcoords)] = btens[(coord, ) + cartcoords]
def __new__(cls, indices, tensor=None, shape=None, coeff=None, subidxs=None, known_indices=False, index_range_set=None): """ Initialize a EinsumTensor with `indices` and a reference to `tensor`, or create a new Tensor to refer to with the give shape """ self = object.__new__(cls) if coeff is None: self.coeff = 1.0 else: self.coeff = coeff #----------------------------------------# # Indices determine slices, sub_indices determine einstein summation (if sub_indices # are not used, then the two tuples are identical) if subidxs is None: self.indices, self.sub_indices = EinsumTensor.split_indices( indices, include_sub=True) else: # Mostly for calling from __copy__ self.indices, self.sub_indices = indices, subidxs #----------------------------------------# if tensor is not None: self._tensor = tensor elif known_indices: idx_rng_set = index_range_set if index_range_set is not None else IndexRange.global_index_range_set self._tensor = Tensor(indices=self.sub_indices, index_range_set=idx_rng_set) elif shape is not None: self._tensor = Tensor(shape=shape) else: raise TypeError( "EinsumTensor initialization must have either a tensor or a shape to proceed." ) #----------------------------------------# # See if it's fully internally contracted. If so, perform the internal contraction # and return a float if all(self.sub_indices.count(i) == 2 for i in self.sub_indices): idxs = list(set(self.sub_indices)) idxmap = {} for n, i in enumerate(idxs): idxmap[i] = n return np.einsum(self.sliced_tensor, [idxmap[i] for i in self.sub_indices], []) #----------------------------------------# if self.indices != self.sub_indices and not self._has_only_known_indices( ): raise ValueError( 'sub-indices can only be used with main indices from declared ranges' ) #----------------------------------------# return self
def __new__(cls, *args, **kwargs): """ For now, just passes up to Tensor and view casts Note: vectors can only be 1-dimensional. No matter what. If a multi-dimensional Tensor is view-cast as a vector, it will be flattened in row-major order (try not to do this, though) """ # Resolve and remove any special args/kwargs here... # Then move on up and view cast ret_val = None if len(kwargs) == 0: ret_val = Tensor(*args) else: ret_val = Tensor(*args, **kwargs) ret_val = ret_val.view(cls) return ret_val
def __new__(cls, *args, **kwargs): """ For now, just passes up to Tensor and view casts Note: vectors can only be 1-dimensional. No matter what. If a multi-dimensional Tensor is view-cast as a vector, it will be flattened in row-major order (try not to do this, though) """ # Resolve and remove any special args/kwargs here... # Then move on up and view cast ret_val = None if len(kwargs) == 0: ret_val = Tensor(*args) else: ret_val = Tensor(*args, **kwargs) ret_val = ret_val.view(cls) return ret_val
def test_torsion_4_dry_run(self): self.setUpForValue(84.3) order = 5 coord = self.coord natoms = self.mol.natoms btens = coord.parent_representation.b_tensor() analytic_tensor = Tensor(shape=(3 * natoms, ) * order) for cartcoords in product(coord.variables, repeat=order): analytic_tensor[tuple( c.index for c in cartcoords)] = btens[(coord, ) + cartcoords]
def test_bond_length_7_dry_run(self): self.setUp() order = 7 mol = Molecule.from_z_matrix('H\nO 1 0.9', create_representation=True) mol.recenter() coord = mol.internal_representation[0] natoms = mol.natoms btens = coord.parent_representation.b_tensor() analytic_tensor = Tensor(shape=(3 * natoms, ) * order) for cartcoords in product(coord.variables, repeat=order): analytic_tensor[tuple( c.index for c in cartcoords)] = btens[(coord, ) + cartcoords]
def __new__(cls, *args, **kwargs): """ For now, just passes up to ``Tensor`` and view casts. When passed a ``str`` as the first argument, the ``numpy.matrix`` constructor is called, and the Tensor constructor is called with the result and any remaining arguments. :Examples: >>> Matrix([[1,2,3],[4,5,6]]) Matrix([[ 1., 2., 3.], [ 4., 5., 6.]]) >>> Matrix((1,2,3),[4,5,6]) Matrix([[ 1., 2., 3.], [ 4., 5., 6.]]) >>> Matrix(shape=(4,3),default_val=17.5) Matrix([[ 17.5, 17.5, 17.5], [ 17.5, 17.5, 17.5], [ 17.5, 17.5, 17.5], [ 17.5, 17.5, 17.5]]) """ # Resolve and remove any special args/kwargs here... # (nothing to do yet...) # Then move on up and view cast ret_val = None if len(args) >= 1 and isinstance(args[0], basestring): ret_val = np.matrix(args[0]) newargs = args[1:] if len(args) > 1 else () ret_val = Tensor(ret_val, *newargs, **kwargs) else: ret_val = Tensor(*args, **kwargs) ret_val = ret_val.view(cls) if len(ret_val.shape) == 1: ret_val = ret_val.reshape(1, ret_val.shape[0]) elif len(ret_val.shape) > 2: raise StandardError("Matrix objects cannot have more than two dimensions. Use the Tensor class instead") return ret_val
def compute(self): #TODO handle units (efficiently!!!) if self._value is not None: return self._value total = None for increments, coeff in self.formula.coefficients.items(): if self._value_function: tmp = self._value_function(zip(self.variables, increments)) else: tmp = self.function.value_for_displacements(zip(self.variables, increments)) if tmp is None: raise ValueError("the value_for_displacements method of FiniteDifferenceFunction" " '{}' returned `None` for increments {}".format( self.function, increments )) if hasattr(tmp, 'value'): val = tmp.value * coeff else: val = tmp * coeff if total is not None: total += val else: total = val if self._delta is not None: deltas = (self._delta,) * len(set(self.variables)) elif self._delta_function: deltas = self._delta_function(self.variables) else: deltas = self.function.deltas_for_variables(self.variables) if isinstance(total, Fraction): # Try and keep it that way denom = reduce(mul, [d**exp for d, exp in zip(deltas, self.orders)]) total /= denom else: total /= reduce(mul, Tensor(deltas)**Tensor(self.orders)) self._value = total
def for_order(self, order): """ Return the Tensor object corresponding to the `order`th order derivative. """ if sanity_checking_enabled: if order < 0: raise ValueError( "don't know about derivatives of order {0}".format(order)) #--------------------------------------------------------------------------------# if order in self.tensors: return self.tensors[order] else: #----------------------------------------# # Representation dependent form if self.representation is not None: if self.first_dimension_different: shape = (len(self.representation), ) + (len( self.secondary_representation), ) * order else: shape = (len(self.representation), ) * order self.tensors[order] = DerivativeTensor(collection=self, shape=shape, **self.tensor_kwargs) return self.tensors[order] #----------------------------------------# # Coordinate-dependent form else: # self.coordinate is not None shape = (3 * len(self.coordinate.atoms), ) * order if self.einsum_index is not None: self.tensors[order] = Tensor(indices=','.join( (self.einsum_index, ) * order), **self.tensor_kwargs) else: self.tensors[order] = Tensor(shape=shape, **self.tensor_kwargs) return self.tensors[order]
def finite_difference_b_tensor( self, order, using_order=None, robustness=None, forward=False, use_parent_indices=True): """ Computes the B tensor for order `order` by finite difference of B tensors of order `order-1`, taking into account permutational symmetry. If `use_parent_indices` is `False`, a `Tensor` is returned that is indexed by the coordinate's internal indices rather than the parent molecule's atom indices. Otherwise, a `DerivativeTensor` is returned with its `representation` attribute set to the coordinate's parent molecule's `cartesian_representation` attribute. """ if using_order is None: using_order = order - 1 else: raise NotImplementedError robustness = robustness or self.b_tensor_finite_difference_rigor if use_parent_indices: ret_val = DerivativeTensor( representation=self.molecule.cartesian_representation, shape=(3*self.molecule.natoms,) * order ) else: ret_val = Tensor( shape=(len(self.atoms) * 3,) * order ) for coords_and_indices in symmetric_product(self.iter_cart_coords(with_index=True), order): coords, indices = zip(*coords_and_indices) val_to_spread = self.finite_difference_b_tensor_element( *coords, robustness=robustness, forward=forward ) if use_parent_indices: for perm in permutations(indices): ret_val[tuple(perm)] = val_to_spread else: for perm in permutations(coords): perm_indices = self.internal_indices_for_coordinates(*perm) ret_val[perm_indices] = val_to_spread return ret_val
def fill_b_tensor_analytically(self, B=None, order=None): """ Fill the coordinate's own `_btensor` attribute for a given order. (The `_btensor` attribute should be retrieved using the `get_b_tensor()` method, which calls this if the _btensor has not already been computed.) If the b tensor cannot be computed analytically at the given order, this method returns `NotImplemented`. Otherwise, it computes the B tensor to the given order and returns `None`. If the optional `B` argument is given, this Coordinate's part of the `Representation`-based `TensorCollection` that `B` refers to is filled as well. """ if order is None: raise TypeError if self._btensor is None: self._init_btensor() if not self._btensor.has_order(order): fill_val = self.analytic_b_tensor_for_order(order) if fill_val is not NotImplemented: self._btensor[...] = fill_val else: return NotImplemented if B is not None: # Fill in the parts of the collection corresponding to the atoms in self # This unwrapping needs to be done because within the coordinate itself, # the atoms are indexed by number within the coordinate, whereas in the # representation, the atoms are indexed by number within the molecule. # This is somewhat confusing, but necessary to allow "detached coordinates" # to have B tensors associated with them (which is necessary, for instance, # if a BondAngle(a, b, c) coordinate is given with one of BondLength(a,b) or # BondLength(b, c) not being a coordinate in the parent representation) bfill = Tensor(shape=(3*B.representation.molecule.natoms,) * order) for grp in product(enumerate(self.iter_molecule_indices()), repeat=order): # 'Unzip' the index groups my_idxs, mol_idxs = zip(*grp) bfill[mol_idxs] = self._btensor[my_idxs] B.for_order(order)[self] = bfill # Return nothing, signaling success return
def __new__(cls, *args, **kwargs): """ For now, just passes up to ``Tensor`` and view casts. When passed a ``str`` as the first argument, the ``numpy.matrix`` constructor is called, and the Tensor constructor is called with the result and any remaining arguments. :Examples: >>> Matrix([[1,2,3],[4,5,6]]) Matrix([[ 1., 2., 3.], [ 4., 5., 6.]]) >>> Matrix((1,2,3),[4,5,6]) Matrix([[ 1., 2., 3.], [ 4., 5., 6.]]) >>> Matrix(shape=(4,3),default_val=17.5) Matrix([[ 17.5, 17.5, 17.5], [ 17.5, 17.5, 17.5], [ 17.5, 17.5, 17.5], [ 17.5, 17.5, 17.5]]) """ # Resolve and remove any special args/kwargs here... # (nothing to do yet...) # Then move on up and view cast ret_val = None if len(args) >= 1 and isinstance(args[0], basestring): ret_val = np.matrix(args[0]) newargs = args[1:] if len(args) > 1 else () ret_val = Tensor(ret_val, *newargs, **kwargs) else: ret_val = Tensor(*args, **kwargs) ret_val = ret_val.view(cls) if len(ret_val.shape) == 1: ret_val = ret_val.reshape(1, ret_val.shape[0]) elif len(ret_val.shape) > 2: raise StandardError( "Matrix objects cannot have more than two dimensions. Use the Tensor class instead" ) return ret_val
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 __ne__(self, other): return Tensor.__ne__(self, other)
def __eq__(self, other): return Tensor.__eq__(self, other)
def sum_into(self, dest, accumulate=False): # Copy so as to avoid overwriting data that might be on the right hand side. dst_slice = copy(dest.sliced_tensor) if not accumulate: dst_slice[...] = 0.0 for tidx, term in enumerate(self.summands): # Contract if it is a contraction rather than a normal EinsumTensor if isinstance(term, EinsumContraction): dst_idxs = [idx for idx in dest.sub_indices if idx in term.external_indices] dst_shape = tuple(dst_slice.shape[i] for i, idx in enumerate(dest.sub_indices) if idx in term.external_indices) to_add = EinsumTensor(dst_idxs, shape=dst_shape) to_add._tensor.name = "(term {} of {})".format(tidx+1, dest.name) term.contract(dest=to_add) # Now continue in the normal fashion, treating the contracted tensor as a regular tensor term = to_add # Add the contribution from the given term if len(term.sub_indices) <= len(dest.sub_indices): # the term is not internally contracted # TODO: fix the following edge case # This could fail if an internally contracted term is being broadcast back into the # destaination, e.g. T[i,j,k,l] += k[j,c,c]. I can't think of an instance in which this # would be used, but it should be at least recognized as a weakness if sanity_checking_enabled and not all(t in dest.sub_indices for t in term.sub_indices): raise IndexError("index mismatch: can't map '{}' to '{}'".format( term.sub_indices, dest.sub_indices)) out_axes = [np.newaxis] * len(dst_slice.shape) srt_idxs = sorted(term.sub_indices, key=dest.sub_indices.index) out_perm = list(term.sub_indices.index(i) for i in srt_idxs) srt_term = np.transpose(term.sliced_tensor, axes=out_perm) for idx in term.sub_indices: out_axes[dest.sub_indices.index(idx)] = slice(None) to_add = term.coeff * srt_term.view(Tensor)[tuple(out_axes)] dst_slice += to_add #========================================# # printing for debugging purposes if EinsumSum._termwise_printing: p = lambda x: print(x, file=EinsumSum._termwise_handler) def tban(x): p('+-' + ('-' * len(x)) + '-+') p('| ' + x + ' |') p('+-' + ('-' * len(x)) + '-+') if EinsumSum._termwise_names is not None: name = EinsumSum._termwise_names[tidx] elif EinsumSum._termwise_result_name is not None: name = "Term #{} of {}".format(tidx+1, EinsumSum._termwise_result_name) elif dest._tensor.name is not None: name = "Term #{} of {}".format(tidx+1, dest._tensor.name) else: name = "Term #{}".format(tidx+1) tban(name) if not isinstance(to_add, Tensor): p(Tensor(to_add).formatted_string(**EinsumSum._termwise_formatting_keywords)) else: p(to_add.formatted_string(**EinsumSum._termwise_formatting_keywords)) p("\n") if EinsumSum._termwise_print_cummulative: if EinsumSum._termwise_result_name is not None: name = EinsumSum._termwise_result_name elif dest._tensor.name is not None: name = dest._tensor.name else: name = "(unlabeled tensor)" tban("Sum of first {} term{} of {}".format( tidx + 1, '' if tidx == 0 else 's', name )) p(dest.formatted_string(**EinsumSum._termwise_formatting_keywords)) else: # Add the internal contraction to dest idxs = list(set(dest.sub_indices + term.sub_indices)) idxmap = {} for n, i in enumerate(idxs): idxmap[i] = n dst_slice += term.coeff * np.einsum( term.sliced_tensor, [idxmap[i] for i in term.sub_indices], [idxmap[i] for i in dest.sub_indices]) dest.sliced_tensor[...] = dst_slice return dest
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 __eq__(self, other): return Tensor.__eq__(self, other)
def assert_has_valid_b_tensor(coord, order, robustness=4, places=7, print_precision=4, forward=False, highlight_char='*', show_vals=False, dry_run=False, use_coordinate_indices=False, print_line_width=120): #--------------------------------------------------------------------------------# # output helper functions def numbered(instr): width = len(str(len(instr.splitlines()))) return ''.join(('#%' + str(width) + 'd') % (i + 1) + "| " + line + '\n' for i, line in enumerate(instr.splitlines())) #----------------------------------------# def difference_string(tens, diff_from): ret_val = '' tstr = numbered(str(tens)) dstr = numbered(str(diff_from)) for line, should_be in zip(tstr.splitlines(), dstr.splitlines()): ret_val += line + "\n" num_re = r'-?\d+\.\d*|inf|nan' diff_line = ' ' * print_line_width for m, sbm in zip(re.finditer(num_re, line), re.finditer(num_re, should_be)): num, num_sb = float(m.group(0)), float(sbm.group(0)) if abs(num - num_sb) > 10.**(-float(places)): width = m.end() - m.start() if show_vals: corr_val = ('{:' + str(width) + 's}').format( str(num_sb)) diff_line = diff_line[:m.start()-1] + highlight_char + corr_val + highlight_char \ + diff_line[m.end()+1:] else: diff_line = diff_line[:m.start( )] + highlight_char * width + diff_line[m.end():] diff_line = diff_line.rstrip() if any(c != ' ' for c in diff_line): ret_val += diff_line + "\n" return ret_val #----------------------------------------# np.set_printoptions(precision=print_precision, edgeitems=300, linewidth=print_line_width, suppress=True) #--------------------------------------------------------------------------------# if not use_coordinate_indices: btens = coord.parent_representation.b_tensor() else: btens = coord.get_b_tensor(order) if not dry_run: fdiff_b_tens = coord.finite_difference_b_tensor( order, robustness=robustness, forward=forward, use_parent_indices=not use_coordinate_indices) natoms = coord.molecule.natoms if not use_coordinate_indices else len( coord.atoms) # TODO use the coordinate's internal indexing rather than the parent molecule's fdiff_tensor = Tensor(shape=(3 * natoms, ) * order) analytic_tensor = Tensor(shape=(3 * natoms, ) * order) for cartcoords in product(coord.variables, repeat=order): if not use_coordinate_indices: indices = tuple(c.index for c in cartcoords) else: indices = coord.internal_indices_for([c.index for c in cartcoords]) if not use_coordinate_indices: analytic_tensor[indices] = btens[(coord, ) + cartcoords] else: analytic_tensor[indices] = btens[indices] if not dry_run: fdiff_tensor[indices] = fdiff_b_tens[indices] #--------------------------------------------------------------------------------# #noinspection PyTypeChecker assert_array_almost_equal(fdiff_tensor, analytic_tensor, decimal=places, err_msg="" "\nArrays not equal to {} decimals" "\n" "\nfinite difference version:\n{}" "\n" "\nanalytic version:\n{}" "\n".format( places, numbered(str(fdiff_tensor)), difference_string(analytic_tensor, fdiff_tensor)))
def __ne__(self, other): return Tensor.__ne__(self, other)