Exemplo n.º 1
0
 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]
Exemplo n.º 2
0
 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
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
 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]
Exemplo n.º 6
0
 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]
Exemplo n.º 7
0
    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
Exemplo n.º 8
0
 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]
Exemplo n.º 10
0
 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
Exemplo n.º 11
0
 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
Exemplo n.º 12
0
    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
Exemplo n.º 13
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
Exemplo n.º 14
0
 def __ne__(self, other):
     return Tensor.__ne__(self, other)
Exemplo n.º 15
0
 def __eq__(self, other):
     return Tensor.__eq__(self, other)
Exemplo n.º 16
0
 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
Exemplo n.º 17
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
Exemplo n.º 18
0
 def __eq__(self, other):
     return Tensor.__eq__(self, other)
Exemplo n.º 19
0
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)))
Exemplo n.º 20
0
 def __ne__(self, other):
     return Tensor.__ne__(self, other)