def accumulate_integrands_with_same_metadata(integrals): """ Taking input on the form: integrals = [integral0, integral1, ...] Return result on the form: integrands_by_id = [(integrand0, metadata0), (integrand1, metadata1), ...] where integrand0 < integrand1 by the canonical ufl expression ordering criteria. """ # Group integrals by compiler data hash by_cdid = {} for itg in integrals: cd = itg.metadata() cdid = hash(canonicalize_metadata(cd)) if cdid not in by_cdid: by_cdid[cdid] = ([], cd) by_cdid[cdid][0].append(itg) # Accumulate integrands separately for each compiler data object # id for cdid in by_cdid: integrals, cd = by_cdid[cdid] # Ensure canonical sorting of more than two integrands integrands = sorted_expr((itg.integrand() for itg in integrals)) integrands_sum = sum(integrands[1:], integrands[0]) by_cdid[cdid] = (integrands_sum, cd) # Sort integrands canonically by integrand first then compiler # data return sorted(by_cdid.values(), key=ExprTupleKey)
def canonicalize_sub_integral_data(sub_integrals): for did in sub_integrals: # Group integrals by compiler data object id by_cdid = {} for itg in sub_integrals[did]: cd = itg.compiler_data() cdid = id(cd) if cdid in by_cdid: by_cdid[cdid][0].append(itg) else: by_cdid[cdid] = ([itg], cd) # Accumulate integrands separately for each compiler data object id for cdid in by_cdid: integrals, cd = by_cdid[cdid] # Ensure canonical sorting of more than two integrands integrands = sorted_expr((itg.integrand() for itg in integrals)) integrands_sum = sum(integrands[1:], integrands[0]) by_cdid[cdid] = (integrands_sum, cd) # Sort integrands canonically by integrand first then compiler data sub_integrals[did] = sorted(by_cdid.values(), key=expr_tuple_key) # i.e. the result is on the form: #sub_integrals[did][:] = [(integrand0, compiler_data0), (integrand1, compiler_data1), ...] # where integrand0 < integrand1 by the canonical ufl expression ordering criteria. return sub_integrals
def derivative(form, coefficient, argument=None, coefficient_derivatives=None): """UFL form operator: Compute the Gateaux derivative of *form* w.r.t. *coefficient* in direction of *argument*. If the argument is omitted, a new ``Argument`` is created in the same space as the coefficient, with argument number one higher than the highest one in the form. The resulting form has one additional ``Argument`` in the same finite element space as the coefficient. A tuple of ``Coefficient`` s may be provided in place of a single ``Coefficient``, in which case the new ``Argument`` argument is based on a ``MixedElement`` created from this tuple. An indexed ``Coefficient`` from a mixed space may be provided, in which case the argument should be in the corresponding subspace of the coefficient space. If provided, *coefficient_derivatives* should be a mapping from ``Coefficient`` instances to their derivatives w.r.t. *coefficient*. """ coefficients, arguments = _handle_derivative_arguments(form, coefficient, argument) if coefficient_derivatives is None: coefficient_derivatives = ExprMapping() else: cd = [] for k in sorted_expr(coefficient_derivatives.keys()): cd += [as_ufl(k), as_ufl(coefficient_derivatives[k])] coefficient_derivatives = ExprMapping(*cd) # Got a form? Apply derivatives to the integrands in turn. if isinstance(form, Form): integrals = [] for itg in form.integrals(): if not isinstance(coefficient, SpatialCoordinate): fd = CoefficientDerivative(itg.integrand(), coefficients, arguments, coefficient_derivatives) else: fd = CoordinateDerivative(itg.integrand(), coefficients, arguments, coefficient_derivatives) integrals.append(itg.reconstruct(fd)) return Form(integrals) elif isinstance(form, Expr): # What we got was in fact an integrand if not isinstance(coefficient, SpatialCoordinate): return CoefficientDerivative(form, coefficients, arguments, coefficient_derivatives) else: return CoordinateDerivative(form, coefficients, arguments, coefficient_derivatives) error("Invalid argument type %s." % str(type(form)))
def __init__(self, a, b): # sort operands for unique representation, must be independent # of various counts etc. as explained in cmp_expr a, b = sorted_expr((a, b)) CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi self.ufl_index_dimensions = fid
def __init__(self, a, b): CompoundTensorOperator.__init__(self) # sort operands for unique representation, # must be independent of various counts etc. # as explained in cmp_expr a, b = sorted_expr((a,b)) # old version, slow and unsafe: #a, b = sorted((a,b), key = lambda x: repr(x)) self._a = a self._b = b self._free_indices, self._index_dimensions = merge_indices(a, b)
def __new__(cls, a, b): # Make sure everything is an Expr a = as_ufl(a) b = as_ufl(b) # Assert consistent tensor properties sh = a.ufl_shape fi = a.ufl_free_indices fid = a.ufl_index_dimensions if b.ufl_shape != sh: error("Can't add expressions with different shapes.") if b.ufl_free_indices != fi: error("Can't add expressions with different free indices.") if b.ufl_index_dimensions != fid: error("Can't add expressions with different index dimensions.") # Skip adding zero if isinstance(a, Zero): return b elif isinstance(b, Zero): return a # Handle scalars specially and sort operands sa = isinstance(a, ScalarValue) sb = isinstance(b, ScalarValue) if sa and sb: # Apply constant propagation return as_ufl(a._value + b._value) elif sa: # Place scalar first # operands = (a, b) pass # a, b = a, b elif sb: # Place scalar first # operands = (b, a) a, b = b, a # elif a == b: # # Replace a+b with 2*foo # return 2*a else: # Otherwise sort operands in a canonical order # operands = (b, a) a, b = sorted_expr((a, b)) # construct and initialize a new Sum object self = Operator.__new__(cls) self._init(a, b) return self
def __new__(cls, a, b): # Conversion a = as_ufl(a) b = as_ufl(b) # Type checking # Make sure everything is scalar if a.ufl_shape or b.ufl_shape: error("Product can only represent products of scalars, " "got\n\t%s\nand\n\t%s" % (ufl_err_str(a), ufl_err_str(b))) # Simplification if isinstance(a, Zero) or isinstance(b, Zero): # Got any zeros? Return zero. fi, fid = merge_unique_indices(a.ufl_free_indices, a.ufl_index_dimensions, b.ufl_free_indices, b.ufl_index_dimensions) return Zero((), fi, fid) sa = isinstance(a, ScalarValue) sb = isinstance(b, ScalarValue) if sa and sb: # const * const = const # FIXME: Handle free indices like with zero? I think # IntValue may be index annotated now? return as_ufl(a._value * b._value) elif sa: # 1 * b = b if a._value == 1: return b # a, b = a, b elif sb: # a * 1 = a if b._value == 1: return a a, b = b, a # elif a == b: # a * a = a**2 # TODO: Why? Maybe just remove this? # if not a.ufl_free_indices: # return a**2 else: # a * b = b * a # Sort operands in a semi-canonical order # (NB! This is fragile! Small changes here can have large effects.) a, b = sorted_expr((a, b)) # Construction self = Operator.__new__(cls) self._init(a, b) return self
def __new__(cls, a, b): # Checks ash, bsh = a.ufl_shape, b.ufl_shape if ash != bsh: error("Shapes do not match: %s and %s." % (ufl_err_str(a), ufl_err_str(b))) # Simplification if isinstance(a, Zero) or isinstance(b, Zero): fi, fid = merge_nonoverlapping_indices(a, b) return Zero((), fi, fid) elif ash == (): return a*Conj(b) # sort operands for unique representation, # must be independent of various counts etc. # as explained in cmp_expr if (a, b) != tuple(sorted_expr((a, b))): return Conj(Inner(b, a)) return CompoundTensorOperator.__new__(cls)
def __new__(cls, a, b): # Checks ash, bsh = a.ufl_shape, b.ufl_shape if ash != bsh: error("Shapes do not match: %s and %s." % (ufl_err_str(a), ufl_err_str(b))) # Simplification if isinstance(a, Zero) or isinstance(b, Zero): fi, fid = merge_nonoverlapping_indices(a, b) return Zero((), fi, fid) elif ash == (): return a * Conj(b) # sort operands for unique representation, # must be independent of various counts etc. # as explained in cmp_expr if (a, b) != tuple(sorted_expr((a, b))): return Conj(Inner(b, a)) return CompoundTensorOperator.__new__(cls)
def __new__(cls, *operands): # TODO: This whole thing seems a bit complicated... Can it be simplified? Maybe we can merge some loops for efficiency? ufl_assert(operands, "Can't take sum of nothing.") #if not operands: # return Zero() # Allowing this leads to zeros with invalid type information in other places, need indices and shape # make sure everything is an Expr operands = [as_ufl(o) for o in operands] # Got one operand only? Do nothing then. if len(operands) == 1: return operands[0] # assert consistent tensor properties sh = operands[0].shape() fi = operands[0].free_indices() fid = operands[0].index_dimensions() #ufl_assert(all(sh == o.shape() for o in operands[1:]), # "Shape mismatch in Sum.") #ufl_assert(not any((set(fi) ^ set(o.free_indices())) for o in operands[1:]), # "Can't add expressions with different free indices.") if any(sh != o.shape() for o in operands[1:]): error("Shape mismatch in Sum.") if any((set(fi) ^ set(o.free_indices())) for o in operands[1:]): error("Can't add expressions with different free indices.") # sort operands in a canonical order operands = sorted_expr(operands) # purge zeros operands = [o for o in operands if not isinstance(o, Zero)] # sort scalars to beginning and merge them scalars = [o for o in operands if isinstance(o, ScalarValue)] if scalars: # exploiting Pythons built-in coersion rules f = as_ufl(sum(f._value for f in scalars)) nonscalars = [o for o in operands if not isinstance(o, ScalarValue)] if not nonscalars: return f if isinstance(f, Zero): operands = nonscalars else: operands = [f] + nonscalars # have we purged everything? if not operands: return Zero(sh, fi, fid) # left with one operand only? if len(operands) == 1: return operands[0] # Replace n-repeated operands foo with n*foo newoperands = [] op = operands[0] n = 1 for o in operands[1:] + [None]: if o == op: n += 1 else: newoperands.append(op if n == 1 else n*op) op = o n = 1 operands = newoperands # left with one operand only? if len(operands) == 1: return operands[0] # construct and initialize a new Sum object self = AlgebraOperator.__new__(cls) self._init(*operands) return self
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