def __init__(self, expression, indices): WrapperType.__init__(self) ufl_assert(isinstance(expression, Expr), "Expecting ufl expression.") ufl_assert(expression.shape() == (), "Expecting scalar valued expression.") self._expression = expression ufl_assert( all(isinstance(i, Index) for i in indices), "Expecting sequence of Index objects, not %s." % repr(indices)) dims = expression.index_dimensions() if not isinstance(indices, MultiIndex): # if constructed from repr indices = MultiIndex(indices, subdict(dims, indices)) self._indices = indices eset = set(expression.free_indices()) iset = set(self._indices) freeset = eset - iset self._free_indices = tuple(freeset) missingset = iset - eset if missingset: error("Missing indices %s in expression %s." % (missingset, expression)) self._index_dimensions = dict( (i, dims[i]) for i in self._free_indices) or EmptyDict self._shape = tuple(dims[i] for i in self._indices)
def __new__(cls, f): # Return zero if expression is trivially constant dim = f.geometric_dimension() if f.is_cellwise_constant(): free_indices = f.free_indices() index_dimensions = subdict(f.index_dimensions(), free_indices) return Zero((dim,) + f.shape(), free_indices, index_dimensions) return CompoundDerivative.__new__(cls)
def __new__(cls, f, v): # Return zero if expression is trivially independent of variable if isinstance(f, Terminal): free_indices = tuple(set(f.free_indices()) ^ set(v.free_indices())) index_dimensions = mergedicts([f.index_dimensions(), v.index_dimensions()]) index_dimensions = subdict(index_dimensions, free_indices) return Zero(f.shape() + v.shape(), free_indices, index_dimensions) return Derivative.__new__(cls)
def conditional(self, o, c, t, f): o = self.reuse_if_possible(o, c[0], t[0], f[0]) if isinstance(t[1], Zero) and isinstance(f[1], Zero): tp = t[1] # Assuming t[1] and f[1] have the same indices here, which should be the case fi = tp.free_indices() fid = subdict(tp.index_dimensions(), fi) op = Zero(tp.shape(), fi, fid) else: op = conditional(c[0], 1, 0)*t[1] + conditional(c[0], 0, 1)*f[1] return (o, op)
def conditional(self, o, c, t, f): o = self.reuse_if_possible(o, c[0], t[0], f[0]) if isinstance(t[1], Zero) and isinstance(f[1], Zero): tp = t[ 1] # Assuming t[1] and f[1] have the same indices here, which should be the case fi = tp.free_indices() fid = subdict(tp.index_dimensions(), fi) op = Zero(tp.shape(), fi, fid) else: op = conditional(c[0], 1, 0) * t[1] + conditional(c[0], 0, 1) * f[1] return (o, op)
def _getitem(self, key): # Analyse key, getting rid of slices and the ellipsis r = self.rank() indices, axis_indices = analyse_key(key, r) # Special case for foo[...] => foo if len(indices) == len(axis_indices): return self # Special case for simplifying ({ai}_i)[i] -> ai if isinstance(self, ComponentTensor): if tuple(indices) == tuple(self._indices): return self._expression # Index self, yielding scalar valued expressions a = Indexed(self, indices) # Make a tensor from components designated by axis indices if axis_indices: a = as_tensor(a, axis_indices) # TODO: Should we apply IndexSum or as_tensor first? # Apply sum for each repeated index ri = repeated_indices(self.free_indices() + indices) for i in ri: a = IndexSum(a, i) # Check for zero (last so we can get indices etc from a) if isinstance(self, Zero): shape = a.shape() fi = a.free_indices() idims = subdict(a.index_dimensions(), fi) a = Zero(shape, fi, idims) return a
def __new__(cls, *operands): # Make sure everything is an Expr operands = [as_ufl(o) for o in operands] # Make sure everything is scalar #ufl_assert(not any(o.shape() for o in operands), # "Product can only represent products of scalars.") if any(o.shape() for o in operands): error("Product can only represent products of scalars.") # No operands? Return one. if not operands: return IntValue(1) # Got one operand only? Just return it. if len(operands) == 1: return operands[0] # Got any zeros? Return zero. if any(isinstance(o, Zero) for o in operands): free_indices = unique_indices(tuple(chain(*(o.free_indices() for o in operands)))) index_dimensions = subdict(mergedicts([o.index_dimensions() for o in operands]), free_indices) return Zero((), free_indices, index_dimensions) # Merge scalars, but keep nonscalars sorted scalars = [] nonscalars = [] for o in operands: if isinstance(o, ScalarValue): scalars.append(o) else: nonscalars.append(o) if scalars: # merge scalars p = as_ufl(product(s._value for s in scalars)) # only scalars? if not nonscalars: return p # merged scalar is unity? if p == 1: scalars = [] # Left with one nonscalar operand only after merging scalars? if len(nonscalars) == 1: return nonscalars[0] else: scalars = [p] # Sort operands in a canonical order (NB! This is fragile! Small changes here can have large effects.) operands = scalars + sorted_expr(nonscalars) # Replace n-repeated operands foo with foo**n newoperands = [] op, nop = operands[0], 1 for o in operands[1:] + [None]: if o == op: # op is repeated, count number of repetitions nop += 1 else: if nop == 1: # op is not repeated newoperands.append(op) elif op.free_indices(): # We can't simplify products to powers if the operands has # free indices, because of complications in differentiation. # op repeated, but has free indices, so we don't simplify newoperands.extend([op]*nop) else: # op repeated, make it a power newoperands.append(op**nop) # Reset op as o op, nop = o, 1 operands = newoperands # Left with one operand only after simplifications? if len(operands) == 1: return operands[0] # Construct and initialize a new Product object self = AlgebraOperator.__new__(cls) self._init(*operands) return self
def _mult(a, b): # Discover repeated indices, which results in index sums ai = a.free_indices() bi = b.free_indices() ii = ai + bi ri = repeated_indices(ii) # Pick out valid non-scalar products here (dot products): # - matrix-matrix (A*B, M*grad(u)) => A . B # - matrix-vector (A*v) => A . v s1, s2 = a.shape(), b.shape() r1, r2 = len(s1), len(s2) if r1 == 2 and r2 in (1, 2): ufl_assert(not ri, "Not expecting repeated indices in non-scalar product.") # Check for zero, simplifying early if possible if isinstance(a, Zero) or isinstance(b, Zero): shape = s1[:-1] + s2[1:] fi = single_indices(ii) idims = mergedicts((a.index_dimensions(), b.index_dimensions())) idims = subdict(idims, fi) return Zero(shape, fi, idims) # Return dot product in index notation ai = indices(a.rank() - 1) bi = indices(b.rank() - 1) k = indices(1) # Create an IndexSum over a Product s = a[ai + k] * b[k + bi] return as_tensor(s, ai + bi) elif not (r1 == 0 and r2 == 0): # Scalar - tensor product if r2 == 0: a, b = b, a s1, s2 = s2, s1 # Check for zero, simplifying early if possible if isinstance(a, Zero) or isinstance(b, Zero): shape = s2 fi = single_indices(ii) idims = mergedicts((a.index_dimensions(), b.index_dimensions())) idims = subdict(idims, fi) return Zero(shape, fi, idims) # Repeated indices are allowed, like in: #v[i]*M[i,:] # Apply product to scalar components ii = indices(b.rank()) p = Product(a, b[ii]) # Wrap as tensor again p = as_tensor(p, ii) # TODO: Should we apply IndexSum or as_tensor first? # Apply index sums for i in ri: p = IndexSum(p, i) return p # Scalar products use Product and IndexSum for implicit sums: p = Product(a, b) for i in ri: p = IndexSum(p, i) return p