class Outer(CompoundTensorOperator): __slots__ = as_native_strings(( "ufl_free_indices", "ufl_index_dimensions", )) def __new__(cls, a, b): ash, bsh = a.ufl_shape, b.ufl_shape if isinstance(a, Zero) or isinstance(b, Zero): fi, fid = merge_nonoverlapping_indices(a, b) return Zero(ash + bsh, fi, fid) if ash == () or bsh == (): return a * b return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi self.ufl_index_dimensions = fid @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape + self.ufl_operands[1].ufl_shape def __str__(self): return "%s (X) %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self))
class MeasureSum(object): """Represents a sum of measures. This is a notational intermediate object to translate the notation f*(ds(1)+ds(3)) into f*ds(1) + f*ds(3) """ __slots__ = as_native_strings(("_measures", )) def __init__(self, *measures): self._measures = measures def __rmul__(self, other): integrals = [other * m for m in self._measures] return sum(integrals) def __add__(self, other): if isinstance(other, Measure): return MeasureSum(*(self._measures + (other, ))) elif isinstance(other, MeasureSum): return MeasureSum(*(self._measures + other._measures)) return NotImplemented def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def __str__(self): return "{\n " + "\n + ".join(map(str, self._measures)) + "\n}"
class Operator(Expr): "Base class for all operators, i.e. non-terminal expression types." __slots__ = as_native_strings(("ufl_operands", )) def __init__(self, operands=None): Expr.__init__(self) # If operands is None, the type sets this itself. This is to # get around some tricky too-fancy __new__/__init__ design in # algebra.py, for now. It would be nicer to make the classes # in algebra.py pass operands here. if operands is not None: self.ufl_operands = operands def _ufl_expr_reconstruct_(self, *operands): "Return a new object of the same type with new operands." return self._ufl_class_(*operands) def _ufl_signature_data_(self): return self._ufl_typecode_ def _ufl_compute_hash_(self): "Compute a hash code for this expression. Used by sets and dicts." return hash((self._ufl_typecode_, ) + tuple(hash(o) for o in self.ufl_operands)) def __repr__(self): "Default repr string construction for operators." # This should work for most cases r = "%s(%s)" % (self._ufl_class_.__name__, ", ".join( repr(op) for op in self.ufl_operands)) return as_native_str(r)
class HCurlElement(FiniteElementBase): """A curl-conforming version of an outer product element, assuming this makes mathematical sense.""" __slots__ = as_native_strings(("_element", )) def __init__(self, element): self._element = element self._repr = as_native_str("HCurlElement(%s)" % repr(element)) family = "TensorProductElement" cell = element.cell() degree = element.degree() quad_scheme = element.quadrature_scheme() cell = element.cell() value_shape = (cell.geometric_dimension(), ) reference_value_shape = (cell.topological_dimension(), ) # TODO: Is this right? # Skipping TensorProductElement constructor! Bad code smell, # refactor to avoid this non-inheritance somehow. FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) def mapping(self): return "covariant Piola" def reconstruct(self, **kwargs): return HCurlElement(self._element.reconstruct(**kwargs)) def __str__(self): return "HCurlElement(%s)" % str(self._element) def shortstr(self): "Format as string for pretty printing." return "HCurlElement(%s)" % str(self._element.shortstr())
class BinaryCondition(Condition): __slots__ = as_native_strings(('_name',)) def __init__(self, name, left, right): left = as_ufl(left) right = as_ufl(right) Condition.__init__(self, (left, right)) self._name = name if name in ('!=', '=='): # Since equals and not-equals are used for comparing # representations, we have to allow any shape here. The # scalar properties must be checked when used in # conditional instead! pass elif name in ('&&', '||'): # Binary operators acting on boolean expressions allow # only conditions for arg in (left, right): if not isinstance(arg, Condition): error("Expecting a Condition, not %s." % ufl_err_str(arg)) else: # Binary operators acting on non-boolean expressions allow # only scalars if left.ufl_shape != () or right.ufl_shape != (): error("Expecting scalar arguments.") if left.ufl_free_indices != () or right.ufl_free_indices != (): error("Expecting scalar arguments.") def __str__(self): return "%s %s %s" % (parstr(self.ufl_operands[0], self), self._name, parstr(self.ufl_operands[1], self))
class Identity(ConstantValue): "UFL literal type: Representation of an identity matrix." __slots__ = as_native_strings(("_dim", "ufl_shape")) def __init__(self, dim): ConstantValue.__init__(self) self._dim = dim self.ufl_shape = (dim, dim) def evaluate(self, x, mapping, component, index_values): "Evaluates the identity matrix on the given components." a, b = component return 1 if a == b else 0 def __getitem__(self, key): if len(key) != 2: error("Size mismatch for Identity.") if all(isinstance(k, (int, FixedIndex)) for k in key): return IntValue(1) if (int(key[0]) == int(key[1])) else Zero() return Expr.__getitem__(self, key) def __str__(self): return "I" def __repr__(self): r = "Identity(%d)" % self._dim return as_native_str(r) def __eq__(self, other): return isinstance(other, Identity) and self._dim == other._dim
class NablaGrad(CompoundDerivative): __slots__ = as_native_strings(("_dim",)) def __new__(cls, f): # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = find_geometric_dimension(f) return Zero((dim,) + f.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): CompoundDerivative.__init__(self, (f,)) self._dim = find_geometric_dimension(f) def _ufl_expr_reconstruct_(self, op): "Return a new object of the same type with new operands." if is_cellwise_constant(op): if op.ufl_shape != self.ufl_operands[0].ufl_shape: error("Operand shape mismatch in NablaGrad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: error("Free index mismatch in NablaGrad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) @property def ufl_shape(self): return (self._dim,) + self.ufl_operands[0].ufl_shape def __str__(self): return "nabla_grad(%s)" % self.ufl_operands[0]
class Cross(CompoundTensorOperator): __slots__ = as_native_strings(( "ufl_free_indices", "ufl_index_dimensions", )) def __new__(cls, a, b): ash = a.ufl_shape bsh = b.ufl_shape # Checks if not (len(ash) == 1 and ash == bsh): error( "Cross product requires arguments of rank 1, got %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(ash, fi, fid) return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi self.ufl_index_dimensions = fid ufl_shape = (3, ) def __str__(self): return "%s x %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self))
class IntegralData(object): """Utility class with the members (domain, integral_type, subdomain_id, integrals, metadata) where metadata is an empty dictionary that may be used for associating metadata with each object. """ __slots__ = as_native_strings( ('domain', 'integral_type', 'subdomain_id', 'integrals', 'metadata', 'integral_coefficients', 'enabled_coefficients')) def __init__(self, domain, integral_type, subdomain_id, integrals, metadata): if 1 != len(set(itg.ufl_domain() for itg in integrals)): error("Multiple domains mismatch in integral data.") if not all(integral_type == itg.integral_type() for itg in integrals): error("Integral type mismatch in integral data.") if not all(subdomain_id == itg.subdomain_id() for itg in integrals): error("Subdomain id mismatch in integral data.") self.domain = domain self.integral_type = integral_type self.subdomain_id = subdomain_id self.integrals = integrals # This is populated in preprocess using data not available at # this stage: self.integral_coefficients = None self.enabled_coefficients = None # TODO: I think we can get rid of this with some refactoring # in ffc: self.metadata = metadata def __lt__(self, other): # To preserve behaviour of extract_integral_data: return ((self.integral_type, self.subdomain_id, self.integrals, self.metadata) < (other.integral_type, other.subdomain_id, other.integrals, other.metadata)) def __eq__(self, other): # Currently only used for tests: return (self.integral_type == other.integral_type and self.subdomain_id == other.subdomain_id and self.integrals == other.integrals and self.metadata == other.metadata) def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def __str__(self): s = "IntegralData over domain(%s, %s), with integrals:\n%s\nand metadata:\n%s" % ( self.integral_type, self.subdomain_id, '\n\n'.join( map(str, self.integrals)), self.metadata) return s
class AbstractCell(object): "Representation of an abstract finite element cell with only the dimensions known." __slots__ = as_native_strings(( "_topological_dimension", "_geometric_dimension", )) def __init__(self, topological_dimension, geometric_dimension): # Validate dimensions if not isinstance(geometric_dimension, numbers.Integral): error("Expecting integer geometric_dimension.") if not isinstance(topological_dimension, numbers.Integral): error("Expecting integer topological_dimension.") if topological_dimension > geometric_dimension: error("Topological dimension cannot be larger than geometric dimension.") # Store validated dimensions self._topological_dimension = topological_dimension self._geometric_dimension = geometric_dimension def topological_dimension(self): "Return the dimension of the topology of this cell." return self._topological_dimension def geometric_dimension(self): "Return the dimension of the space this cell is embedded in." return self._geometric_dimension def is_simplex(self): "Return True if this is a simplex cell." raise NotImplementedError("Implement this to allow important checks and optimizations.") def has_simplex_facets(self): "Return True if all the facets of this cell are simplex cells." raise NotImplementedError("Implement this to allow important checks and optimizations.") def __lt__(self, other): "Define an arbitrarily chosen but fixed sort order for all cells." if not isinstance(other, AbstractCell): return NotImplemented # Sort by gdim first, tdim next, then whatever's left # depending on the subclass s = (self.geometric_dimension(), self.topological_dimension()) o = (other.geometric_dimension(), other.topological_dimension()) if s != o: return s < o return self._ufl_hash_data_() < other._ufl_hash_data_() def __unicode__(self): # Only in python 2 return str(self).decode("utf-8")
class PermutationSymbol(ConstantValue): """UFL literal type: Representation of a permutation symbol. This is also known as the Levi-Civita symbol, antisymmetric symbol, or alternating symbol.""" __slots__ = as_native_strings(("ufl_shape", "_dim")) def __init__(self, dim): ConstantValue.__init__(self) self._dim = dim self.ufl_shape = (dim,)*dim def evaluate(self, x, mapping, component, index_values): "Evaluates the permutation symbol." return self.__eps(component) def __getitem__(self, key): if len(key) != self._dim: error("Size mismatch for PermutationSymbol.") if all(isinstance(k, (int, FixedIndex)) for k in key): return self.__eps(key) return Expr.__getitem__(self, key) def __str__(self): return "eps" def __repr__(self): r = "PermutationSymbol(%d)" % self._dim return as_native_str(r) def __eq__(self, other): return isinstance(other, PermutationSymbol) and self._dim == other._dim def __eps(self, x): """This function body is taken from http://www.mathkb.com/Uwe/Forum.aspx/math/29865/N-integer-Levi-Civita """ result = IntValue(1) for i, x1 in enumerate(x): for j in range(i + 1, len(x)): x2 = x[j] if x1 > x2: result = -result elif x1 == x2: return Zero() return result
class ScalarValue(ConstantValue): "A constant scalar value." __slots__ = as_native_strings(("_value",)) def __init__(self, value): ConstantValue.__init__(self) self._value = value def value(self): return self._value def evaluate(self, x, mapping, component, index_values): return self._value def __eq__(self, other): """This is implemented to allow comparison with python scalars. Note that this will make IntValue(1) != FloatValue(1.0), but ufl-python comparisons like IntValue(1) == 1.0 FloatValue(1.0) == 1 can still succeed. These will however not have the same hash value and therefore not collide in a dict. """ if isinstance(other, self._ufl_class_): return self._value == other._value elif isinstance(other, (int, float)): # FIXME: Disallow this, require explicit 'expr == # IntValue(3)' instead to avoid ambiguities! return other == self._value else: return False def __str__(self): return str(self._value) def __float__(self): return float(self._value) def __int__(self): return int(self._value) def __neg__(self): return type(self)(-self._value) def __abs__(self): return type(self)(abs(self._value))
class BesselFunction(Operator): "Base class for all bessel functions" __slots__ = as_native_strings(("_name", "_classname")) def __init__(self, name, classname, nu, argument): if not is_true_ufl_scalar(nu): error("Expecting scalar nu.") if not is_true_ufl_scalar(argument): error("Expecting scalar argument.") # Use integer representation if suitable fnu = float(nu) inu = int(nu) if fnu == inu: nu = as_ufl(inu) else: nu = as_ufl(fnu) Operator.__init__(self, (nu, argument)) self._classname = classname self._name = name def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[1].evaluate(x, mapping, component, index_values) try: import scipy.special except: error( "You must have scipy installed to evaluate bessel functions in python." ) name = self._name[-1] if isinstance(self.ufl_operands[0], IntValue): nu = int(self.ufl_operands[0]) functype = 'n' if name != 'i' else 'v' else: nu = self.ufl_operands[0].evaluate(x, mapping, component, index_values) functype = 'v' func = getattr(scipy.special, name + functype) return func(nu, a) def __str__(self): return "%s(%s, %s)" % (self._name, self.ufl_operands[0], self.ufl_operands[1])
class ExprTupleKey(object): __slots__ = as_native_strings(('x', )) def __init__(self, x): self.x = x def __lt__(self, other): # Comparing expression first c = cmp_expr(self.x[0], other.x[0]) if c < 0: return True elif c > 0: return False else: # Comparing form compiler data mds = canonicalize_metadata(self.x[1]) mdo = canonicalize_metadata(other.x[1]) return mds < mdo
class ExampleCounted(object): """An example class for classes of objects identified by a global counter. Mimic this class to create globally counted objects within a single type. """ # Store the count for each object __slots__ = as_native_strings(("_count", )) # Store a global counter with the class _globalcount = 0 # Call counted_init with an optional constructor argument and the class def __init__(self, count=None): counted_init(self, count, ExampleCounted) # Make the count accessible def count(self): return self._count
class Dot(CompoundTensorOperator): __slots__ = as_native_strings(( "ufl_free_indices", "ufl_index_dimensions", )) def __new__(cls, a, b): ash = a.ufl_shape bsh = b.ufl_shape ar, br = len(ash), len(bsh) scalar = (ar == 0 and br == 0) # Checks if not ((ar >= 1 and br >= 1) or scalar): error("Dot product requires non-scalar arguments, " "got arguments with ranks %d and %d." % (ar, br)) if not (scalar or ash[-1] == bsh[0]): error("Dimension mismatch in dot product.") # Simplification if isinstance(a, Zero) or isinstance(b, Zero): shape = ash[:-1] + bsh[1:] fi, fid = merge_nonoverlapping_indices(a, b) return Zero(shape, fi, fid) elif scalar: # TODO: Move this to def dot()? return a * b return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi self.ufl_index_dimensions = fid @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape[:-1] + self.ufl_operands[ 1].ufl_shape[1:] def __str__(self): return "%s . %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self))
class ReferenceGrad(CompoundDerivative): __slots__ = as_native_strings(("_dim",)) def __new__(cls, f): # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = f.ufl_domain().topological_dimension() return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): CompoundDerivative.__init__(self, (f,)) self._dim = f.ufl_domain().topological_dimension() def _ufl_expr_reconstruct_(self, op): "Return a new object of the same type with new operands." if is_cellwise_constant(op): if op.ufl_shape != self.ufl_operands[0].ufl_shape: error("Operand shape mismatch in ReferenceGrad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: error("Free index mismatch in ReferenceGrad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): "Get child from mapping and return the component asked for." component, i = component[:-1], component[-1] derivatives = derivatives + (i,) result = self.ufl_operands[0].evaluate(x, mapping, component, index_values, derivatives=derivatives) return result @property def ufl_shape(self): return self.ufl_operands[0].ufl_shape + (self._dim,) def __str__(self): return "reference_grad(%s)" % self.ufl_operands[0]
class Inner(CompoundTensorOperator): __slots__ = as_native_strings(( "ufl_free_indices", "ufl_index_dimensions", )) 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 * b return CompoundTensorOperator.__new__(cls) 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 ufl_shape = () def __str__(self): return "%s : %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self))
class MeasureProduct(object): """Represents a product of measures. This is a notational intermediate object to handle the notation f*(dm1*dm2) This is work in progress and not functional. It needs support in other parts of ufl and the rest of the code generation chain. """ __slots__ = as_native_strings(("_measures", )) def __init__(self, *measures): "Create MeasureProduct from given list of measures." self._measures = measures if len(self._measures) < 2: error("Expecting at least two measures.") def __mul__(self, other): """Flatten multiplication of product measures. This is to ensure that (dm1*dm2)*dm3 is stored as a simple list (dm1,dm2,dm3) in a single MeasureProduct. """ if isinstance(other, Measure): measures = self.sub_measures() + [other] return MeasureProduct(*measures) else: return NotImplemented def __rmul__(self, integrand): error( "TODO: Implement MeasureProduct.__rmul__ to construct integral and form somehow." ) def sub_measures(self): "Return submeasures." return self._measures
class GeometricQuantity(Terminal): __slots__ = as_native_strings(("_domain", )) def __init__(self, domain): Terminal.__init__(self) self._domain = as_domain(domain) def ufl_domains(self): return (self._domain, ) def is_cellwise_constant(self): "Return whether this expression is spatially constant over each cell (or over each facet for facet quantities)." # NB! Geometric quantities are piecewise constant by # default. Override if needed. return True # NB! Geometric quantities are scalar by default. Override if # needed. ufl_shape = () def _ufl_signature_data_(self, renumbering): "Signature data of geometric quantities depend on the domain numbering." return (self._ufl_class_.__name__, ) + self._domain._ufl_signature_data_(renumbering) def __str__(self): return self._ufl_class_.name def __repr__(self): r = "%s(%s)" % (self._ufl_class_.__name__, repr(self._domain)) return as_native_str(r) def _ufl_compute_hash_(self): return hash((type(self).__name__, ) + self._domain._ufl_hash_data_()) def __eq__(self, other): return isinstance(other, self._ufl_class_) and other._domain == self._domain
class Label(Terminal): __slots__ = as_native_strings(("_count", )) _globalcount = 0 def __init__(self, count=None): Terminal.__init__(self) counted_init(self, count, Label) def count(self): return self._count def __str__(self): return "Label(%d)" % self._count def __repr__(self): r = "Label(%d)" % self._count return as_native_str(r) @property def ufl_shape(self): error("Label has no shape (it is not a tensor expression).") @property def ufl_free_indices(self): error("Label has no free indices (it is not a tensor expression).") @property def ufl_index_dimensions(self): error("Label has no free indices (it is not a tensor expression).") def is_cellwise_constant(self): return True def ufl_domains(self): "Return tuple of domains related to this terminal object." return ()
class ReferenceCurl(CompoundDerivative): __slots__ = as_native_strings(("ufl_shape",)) def __new__(cls, f): # Validate input sh = f.ufl_shape if f.ufl_shape not in ((), (2,), (3,)): error("Expecting a scalar, 2D vector or 3D vector.") if f.ufl_free_indices: error("Free indices in the curl argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): sh = {(): (2,), (2,): (), (3,): (3,)}[sh] return Zero(sh) # No free indices asserted above return CompoundDerivative.__new__(cls) def __init__(self, f): global _curl_shapes CompoundDerivative.__init__(self, (f,)) self.ufl_shape = _curl_shapes[f.ufl_shape] def __str__(self): return "reference_curl(%s)" % self.ufl_operands[0]
class MathFunction(Operator): "Base class for all unary scalar math functions." # Freeze member variables for objects in this class __slots__ = as_native_strings(("_name", )) def __init__(self, name, argument): Operator.__init__(self, (argument, )) if not is_true_ufl_scalar(argument): error("Expecting scalar argument.") self._name = name def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) try: res = getattr(math, self._name)(a) except ValueError: warning( 'Value error in evaluation of function %s with argument %s.' % (self._name, a)) raise return res def __str__(self): return "%s(%s)" % (self._name, self.ufl_operands[0])
class VariableDerivative(Derivative): __slots__ = as_native_strings(( "ufl_shape", "ufl_free_indices", "ufl_index_dimensions", )) def __new__(cls, f, v): # Checks if not isinstance(f, Expr): error("Expecting an Expr in VariableDerivative.") if not isinstance(v, (Variable, Coefficient)): error("Expecting a Variable in VariableDerivative.") if v.ufl_free_indices: error("Differentiation variable cannot have free indices.") # Simplification # Return zero if expression is trivially independent of variable if f._ufl_is_terminal_ and f != v: return Zero(f.ufl_shape + v.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions) # Construction return Derivative.__new__(cls) def __init__(self, f, v): Derivative.__init__(self, (f, v)) self.ufl_free_indices = f.ufl_free_indices self.ufl_index_dimensions = f.ufl_index_dimensions self.ufl_shape = f.ufl_shape + v.ufl_shape def __str__(self): if isinstance(self.ufl_operands[0], Terminal): return "d%s/d[%s]" % (self.ufl_operands[0], self.ufl_operands[1]) return "d/d[%s] %s" % (self.ufl_operands[1], parstr(self.ufl_operands[0], self))
class ComponentTensor(Operator): """UFL operator type: Maps the free indices of a scalar valued expression to tensor axes.""" __slots__ = as_native_strings( ("ufl_shape", "ufl_free_indices", "ufl_index_dimensions")) def __new__(cls, expression, indices): # Simplify if isinstance(expression, Zero): fi, fid, sh = remove_indices(expression.ufl_free_indices, expression.ufl_index_dimensions, [ind.count() for ind in indices]) return Zero(sh, fi, fid) # Construct return Operator.__new__(cls) def __init__(self, expression, indices): if not isinstance(expression, Expr): error("Expecting ufl expression.") if expression.ufl_shape != (): error("Expecting scalar valued expression.") if not isinstance(indices, MultiIndex): error("Expecting a MultiIndex.") if not all(isinstance(i, Index) for i in indices): error("Expecting sequence of Index objects, not %s." % indices._ufl_err_str_()) Operator.__init__(self, (expression, indices)) fi, fid, sh = remove_indices(expression.ufl_free_indices, expression.ufl_index_dimensions, [ind.count() for ind in indices]) self.ufl_free_indices = fi self.ufl_index_dimensions = fid self.ufl_shape = sh def _ufl_expr_reconstruct_(self, expressions, indices): # Special case for simplification as_tensor(A[ii], ii) -> A if isinstance(expressions, Indexed): A, ii = expressions.ufl_operands if indices == ii: return A return Operator._ufl_expr_reconstruct_(self, expressions, indices) def indices(self): return self.ufl_operands[1] def evaluate(self, x, mapping, component, index_values): indices = self.ufl_operands[1] a = self.ufl_operands[0] if len(indices) != len(component): error("Expecting a component matching the indices tuple.") # Map component to indices for i, c in zip(indices, component): index_values.push(i, c) a = a.evaluate(x, mapping, (), index_values) for _ in component: index_values.pop() return a def __str__(self): return "{ A | A_{%s} = %s }" % (self.ufl_operands[1], self.ufl_operands[0])
class TensorProductElement(FiniteElementBase): r"""The tensor product of :math:`d` element spaces: .. math:: V = V_1 \otimes V_2 \otimes ... \otimes V_d Given bases :math:`\{\phi_{j_i}\}` of the spaces :math:`V_i` for :math:`i = 1, ...., d`, :math:`\{ \phi_{j_1} \otimes \phi_{j_2} \otimes \cdots \otimes \phi_{j_d} \}` forms a basis for :math:`V`. """ __slots__ = as_native_strings(("_sub_elements", "_cell")) def __init__(self, *elements, **kwargs): "Create TensorProductElement from a given list of elements." if not elements: error("Cannot create TensorProductElement from empty list.") keywords = list(kwargs.keys()) if keywords and keywords != ["cell"]: raise ValueError( "TensorProductElement got an unexpected keyword argument '%s'" % keywords[0]) cell = kwargs.get("cell") family = "TensorProductElement" if cell is None: # Define cell as the product of each elements cell cell = TensorProductCell(*[e.cell() for e in elements]) else: cell = as_cell(cell) # Define polynomial degree as a tuple of sub-degrees degree = tuple(e.degree() for e in elements) # No quadrature scheme defined quad_scheme = None # match FIAT implementation value_shape = tuple(chain(*[e.value_shape() for e in elements])) reference_value_shape = tuple( chain(*[e.reference_value_shape() for e in elements])) if len(value_shape) > 1: error("Product of vector-valued elements not supported") if len(reference_value_shape) > 1: error("Product of vector-valued elements not supported") FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) self._sub_elements = elements self._cell = cell self._repr = "TensorProductElement(%s, cell=%s)" % (", ".join( repr(e) for e in elements), repr(cell)) def mapping(self): if all(e.mapping() == "identity" for e in self._sub_elements): return "identity" else: return "undefined" def num_sub_elements(self): "Return number of subelements." return len(self._sub_elements) def sub_elements(self): "Return subelements (factors)." return self._sub_elements def reconstruct(self, cell=None): return TensorProductElement(*self.sub_elements(), cell=cell) def __str__(self): "Pretty-print." return "TensorProductElement(%s, cell=%s)" \ % (', '.join([str(e) for e in self._sub_elements]), str(self._cell)) def shortstr(self): "Short pretty-print." return "TensorProductElement(%s, cell=%s)" \ % (', '.join([e.shortstr() for e in self._sub_elements]), str(self._cell))
class Integral(object): "An integral over a single domain." __slots__ = as_native_strings(( "_integrand", "_integral_type", "_ufl_domain", "_subdomain_id", "_metadata", "_subdomain_data", )) def __init__(self, integrand, integral_type, domain, subdomain_id, metadata, subdomain_data): if not isinstance(integrand, Expr): error("Expecting integrand to be an Expr instance.") self._integrand = integrand self._integral_type = integral_type self._ufl_domain = domain self._subdomain_id = subdomain_id self._metadata = metadata self._subdomain_data = subdomain_data def reconstruct(self, integrand=None, integral_type=None, domain=None, subdomain_id=None, metadata=None, subdomain_data=None): """Construct a new Integral object with some properties replaced with new values. Example: <a = Integral instance> b = a.reconstruct(expand_compounds(a.integrand())) c = a.reconstruct(metadata={'quadrature_degree':2}) """ if integrand is None: integrand = self.integrand() if integral_type is None: integral_type = self.integral_type() if domain is None: domain = self.ufl_domain() if subdomain_id is None: subdomain_id = self.subdomain_id() if metadata is None: metadata = self.metadata() if subdomain_data is None: subdomain_data = self._subdomain_data return Integral(integrand, integral_type, domain, subdomain_id, metadata, subdomain_data) def integrand(self): "Return the integrand expression, which is an ``Expr`` instance." return self._integrand def integral_type(self): "Return the domain type of this integral." return self._integral_type #def domain(self): # "Deprecated, please use .ufl_domain() instead." # deprecate("Integral.domain() is deprecated, please use .ufl_domain() instead.") # return self.ufl_domain() def ufl_domain(self): "Return the integration domain of this integral." return self._ufl_domain def subdomain_id(self): "Return the subdomain id of this integral." return self._subdomain_id def metadata(self): "Return the compiler metadata this integral has been annotated with." return self._metadata def subdomain_data(self): "Return the domain data of this integral." return self._subdomain_data def __neg__(self): return self.reconstruct(-self._integrand) def __mul__(self, scalar): if not is_python_scalar(scalar): error("Cannot multiply an integral with non-constant values.") return self.reconstruct(scalar * self._integrand) def __rmul__(self, scalar): if not is_scalar_constant_expression(scalar): error("An integral can only be multiplied by a " "globally constant scalar expression.") return self.reconstruct(scalar * self._integrand) def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def __str__(self): fmt = "{ %s } * %s(%s[%s], %s)" mname = ufl.measure.integral_type_to_measure_name[self._integral_type] s = fmt % (self._integrand, mname, self._ufl_domain, self._subdomain_id, self._metadata) return s def __repr__(self): r = "Integral(%s, %s, %s, %s, %s, %s)" % ( repr(self._integrand), repr(self._integral_type), repr(self._ufl_domain), repr(self._subdomain_id), repr(self._metadata), repr(self._subdomain_data), ) return as_native_str(r) def __eq__(self, other): return (isinstance(other, Integral) and self._integral_type == other._integral_type and self._ufl_domain == other._ufl_domain and self._subdomain_id == other._subdomain_id and self._integrand == other._integrand and self._metadata == other._metadata and id_or_none( self._subdomain_data) == id_or_none(other._subdomain_data)) def __hash__(self): # Assuming few collisions by ignoring hash(self._metadata) # (a dict is not hashable but we assume it is immutable in practice) hashdata = (hash(self._integrand), self._integral_type, hash(self._ufl_domain), self._subdomain_id, id_or_none(self._subdomain_data)) return hash(hashdata)
# # Modified by Anders Logg, 2008-2009 # Modified by Massimiliano Leoni, 2016. # import six import ufl from ufl.log import error from ufl.core.expr import Expr from ufl.checks import is_python_scalar, is_scalar_constant_expression from ufl.measure import Measure # noqa from ufl.protocols import id_or_none from ufl.utils.py23 import as_native_str from ufl.utils.py23 import as_native_strings # Export list for ufl.classes __all_classes__ = as_native_strings(["Integral"]) # @six.python_2_unicode_compatible class Integral(object): "An integral over a single domain." __slots__ = as_native_strings(( "_integrand", "_integral_type", "_ufl_domain", "_subdomain_id", "_metadata", "_subdomain_data", )) def __init__(self, integrand, integral_type, domain, subdomain_id,
__all__ = as_native_strings([ 'product', 'get_handler', 'get_logger', 'set_handler', 'set_level', 'add_logfile', 'UFLException', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'as_cell', 'AbstractCell', 'Cell', 'TensorProductCell', 'as_domain', 'AbstractDomain', 'Mesh', 'MeshView', 'TensorProductMesh', 'L2', 'H1', 'H2', 'HCurl', 'HDiv', 'SpatialCoordinate', 'CellVolume', 'Circumradius', 'MinCellEdgeLength', 'MaxCellEdgeLength', 'FacetArea', 'MinFacetEdgeLength', 'MaxFacetEdgeLength', 'FacetNormal', 'CellNormal', 'Jacobian', 'JacobianDeterminant', 'JacobianInverse', 'FiniteElementBase', 'FiniteElement', 'MixedElement', 'VectorElement', 'TensorElement', 'EnrichedElement', 'NodalEnrichedElement', 'RestrictedElement', 'TensorProductElement', 'HDivElement', 'HCurlElement', 'BrokenElement', 'FacetElement', 'InteriorElement', 'register_element', 'show_elements', 'FunctionSpace', 'Argument', 'TestFunction', 'TrialFunction', 'Arguments', 'TestFunctions', 'TrialFunctions', 'Coefficient', 'Coefficients', 'Constant', 'VectorConstant', 'TensorConstant', 'split', 'PermutationSymbol', 'Identity', 'zero', 'as_ufl', 'Index', 'indices', 'as_tensor', 'as_vector', 'as_matrix', 'relabel', 'unit_vector', 'unit_vectors', 'unit_matrix', 'unit_matrices', 'rank', 'shape', 'outer', 'inner', 'dot', 'cross', 'perp', 'det', 'inv', 'cofac', 'transpose', 'tr', 'diag', 'diag_vector', 'dev', 'skew', 'sym', 'sqrt', 'exp', 'ln', 'erf', 'cos', 'sin', 'tan', 'acos', 'asin', 'atan', 'atan_2', 'cosh', 'sinh', 'tanh', 'bessel_J', 'bessel_Y', 'bessel_I', 'bessel_K', 'eq', 'ne', 'le', 'ge', 'lt', 'gt', 'And', 'Or', 'Not', 'conditional', 'sign', 'max_value', 'min_value', 'Max', 'Min', 'variable', 'diff', 'Dx', 'grad', 'div', 'curl', 'rot', 'nabla_grad', 'nabla_div', 'Dn', 'exterior_derivative', 'jump', 'avg', 'cell_avg', 'facet_avg', 'elem_mult', 'elem_div', 'elem_pow', 'elem_op', 'Form', 'Integral', 'Measure', 'register_integral_type', 'integral_types', 'custom_integral_types', 'replace', 'replace_integral_domains', 'derivative', 'action', 'energy_norm', 'rhs', 'lhs', 'block_split', 'system', 'functional', 'adjoint', 'sensitivity_rhs', 'dx', 'ds', 'dS', 'dP', 'dc', 'dC', 'dO', 'dI', 'dX', 'ds_b', 'ds_t', 'ds_tb', 'ds_v', 'dS_h', 'dS_v', 'vertex', 'interval', 'triangle', 'tetrahedron', 'quadrilateral', 'hexahedron', 'facet', 'i', 'j', 'k', 'l', 'p', 'q', 'r', 's', 'e', 'pi', ])
class Expr(object): """Base class for all UFL expression types. *Instance properties* Every ``Expr`` instance will have certain properties. The most important ones are ``ufl_operands``, ``ufl_shape``, ``ufl_free_indices``, and ``ufl_index_dimensions`` properties. Expressions are immutable and hashable. *Type traits* The ``Expr`` API defines a number of type traits that each subclass needs to provide. Most of these are specified indirectly via the arguments to the ``ufl_type`` class decorator, allowing UFL to do some consistency checks and automate most of the traits for most types. Type traits are accessed via a class or instance object of the form ``obj._ufl_traitname_``. See the source code for description of each type trait. *Operators* Some Python special functions are implemented in this class, some are implemented in subclasses, and some are attached to this class in the ``ufl_type`` class decorator. *Defining subclasses* To define a new expression class, inherit from either ``Terminal`` or ``Operator``, and apply the ``ufl_type`` class decorator with suitable arguments. See the docstring of ``ufl_type`` for details on its arguments. Looking at existing classes similar to the one you wish to add is a good idea. Looking through the comments in the ``Expr`` class and ``ufl_type`` to understand all the properties that may need to be specified is also a good idea. Note that many algorithms in UFL and form compilers will need handlers implemented for each new type::. .. code-block:: python @ufl_type() class MyOperator(Operator): pass *Type collections* All ``Expr`` subclasses are collected by ``ufl_type`` in global variables available via ``Expr``. *Profiling* Object creation statistics can be collected by doing .. code-block:: python Expr.ufl_enable_profiling() # ... run some code initstats, delstats = Expr.ufl_disable_profiling() Giving a list of creation and deletion counts for each typecode. """ # --- Each Expr subclass must define __slots__ or _ufl_noslots_ at # --- the top --- # This is to freeze member variables for objects of this class and # save memory by skipping the per-instance dict. __slots__ = as_native_strings(("_hash", )) # _ufl_noslots_ = True # --- Basic object behaviour --- def __getnewargs__(self): """The tuple returned here is passed to as args to cls.__new__(cls, *args). This implementation passes the operands, which is () for terminals. May be necessary to override if __new__ is implemented in a subclass. """ return self.ufl_operands def __init__(self): self._hash = None def __del__(self): pass # This shows the principal behaviour of the hash function attached # in ufl_type: # def __hash__(self): # if self._hash is None: # self._hash = self._ufl_compute_hash_() # return self._hash # --- Type traits are added to subclasses by the ufl_type class # --- decorator --- # Note: Some of these are modified after the Expr class definition # because Expr is not defined yet at this point. Note: Boolean # type traits that categorize types are mostly set to None for # Expr but should be True or False for any non-abstract type. # A reference to the UFL class itself. This makes it possible to # do type(f)._ufl_class_ and be sure you get the actual UFL class # instead of a subclass from another library. _ufl_class_ = None # The handler name. This is the name of the handler function you # implement for this type in a multifunction. _ufl_handler_name_ = "expr" # The integer typecode, a contiguous index different for each # type. This is used for fast lookup into e.g. multifunction # handler tables. _ufl_typecode_ = 0 # Number of operands, "varying" for some types, or None if not # applicable for abstract types. _ufl_num_ops_ = None # Type trait: If the type is abstract. An abstract class cannot # be instantiated and does not need all properties specified. _ufl_is_abstract_ = True # Type trait: If the type is terminal. _ufl_is_terminal_ = None # Type trait: If the type is a literal. _ufl_is_literal_ = None # Type trait: If the type is classified as a 'terminal modifier', # for form compiler use. _ufl_is_terminal_modifier_ = None # Type trait: If the type is a shaping operator. Shaping # operations include indexing, slicing, transposing, i.e. not # introducing computation of a new value. _ufl_is_shaping_ = False # Type trait: If the type is in reference frame. _ufl_is_in_reference_frame_ = None # Type trait: If the type is a restriction to a geometric entity. _ufl_is_restriction_ = None # Type trait: If the type is evaluation in a particular way. _ufl_is_evaluation_ = None # Type trait: If the type is a differential operator. _ufl_is_differential_ = None # Type trait: If the type is purely scalar, having no shape or # indices. _ufl_is_scalar_ = None # Type trait: If the type never has free indices. _ufl_is_index_free_ = False # --- All subclasses must define these object attributes --- # Each subclass of Expr is checked to have these properties in # ufl_type _ufl_required_properties_ = ( # A tuple of operands, all of them Expr instances. "ufl_operands", # A tuple of ints, the value shape of the expression. "ufl_shape", # A tuple of free index counts. "ufl_free_indices", # A tuple providing the int dimension for each free index. "ufl_index_dimensions", ) # Each subclass of Expr is checked to have these methods in # ufl_type # FIXME: Add more and enable all _ufl_required_methods_ = ( # To compute the hash on demand, this method is called. "_ufl_compute_hash_", # The data returned from this method is used to compute the # signature of a form "_ufl_signature_data_", # The == operator must be implemented to compare for identical # representation, used by set() and dict(). The __hash__ # operator is added by ufl_type. "__eq__", # To reconstruct an object of the same type with operands or # properties changed. "_ufl_expr_reconstruct_", # Implemented in Operator and Terminal so this should never fail "ufl_domains", # "ufl_cell", # "ufl_domain", # "__str__", # "__repr__", # TODO: Add checks for methods/properties of terminals only? # Required for terminals: # "is_cellwise_constant", # TODO: Rename to ufl_is_cellwise_constant? ) # --- Global variables for collecting all types --- # A global counter of the number of typecodes assigned _ufl_num_typecodes_ = 1 # A global set of all handler names added _ufl_all_handler_names_ = set() # A global array of all Expr subclasses, indexed by typecode _ufl_all_classes_ = [] # A global dict mapping language_operator_name to the type it # produces _ufl_language_operators_ = {} # List of all terminal modifier types _ufl_terminal_modifiers_ = [] # --- Mechanism for profiling object creation and deletion --- # A global array of the number of initialized objects for each # typecode _ufl_obj_init_counts_ = [0] # A global array of the number of deleted objects for each # typecode _ufl_obj_del_counts_ = [0] # Backup of default init and del _ufl_regular__init__ = __init__ _ufl_regular__del__ = __del__ def _ufl_profiling__init__(self): "Replacement constructor with object counting." Expr._ufl_regular__init__(self) Expr._ufl_obj_init_counts_[self._ufl_typecode_] += 1 def _ufl_profiling__del__(self): "Replacement destructor with object counting." Expr._ufl_regular__del__(self) Expr._ufl_obj_del_counts_[self._ufl_typecode_] -= 1 @staticmethod def ufl_enable_profiling(): "Turn on the object counting mechanism and reset counts to zero." Expr.__init__ = Expr._ufl_profiling__init__ Expr.__del__ = Expr._ufl_profiling__del__ for i in range(len(Expr._ufl_obj_init_counts_)): Expr._ufl_obj_init_counts_[i] = 0 Expr._ufl_obj_del_counts_[i] = 0 @staticmethod def ufl_disable_profiling(): "Turn off the object counting mechanism. Return object init and del counts." Expr.__init__ = Expr._ufl_regular__init__ Expr.__del__ = Expr._ufl_regular__del__ return (Expr._ufl_obj_init_counts_, Expr._ufl_obj_del_counts_) # === Abstract functions that must be implemented by subclasses === # --- Functions for reconstructing expression --- def _ufl_expr_reconstruct_(self, *operands): "Return a new object of the same type with new operands." raise NotImplementedError(self.__class__._ufl_expr_reconstruct_) # --- Functions for geometric properties of expression --- def ufl_domains( self): # TODO: Deprecate this and use extract_domains(expr) "Return all domains this expression is defined on." from ufl.domain import extract_domains return extract_domains(self) def ufl_domain( self): # TODO: Deprecate this and use extract_unique_domain(expr) "Return the single unique domain this expression is defined on, or throw an error." from ufl.domain import extract_unique_domain return extract_unique_domain(self) #def is_cellwise_constant(self): # TODO: Deprecate this and use is_cellwise_constant(expr) # "Return whether this expression is spatially constant over each cell." # from ufl.checks import is_cellwise_constant # deprecate("Expr.is_cellwise_constant() is deprecated, please use is_cellwise_constant(expr) instead.") # return is_cellwise_constant(self) # --- Functions for float evaluation --- def evaluate(self, x, mapping, component, index_values): """Evaluate expression at given coordinate with given values for terminals.""" error("Symbolic evaluation of %s not available." % self._ufl_class_.__name__) def _ufl_evaluate_scalar_(self): if self.ufl_shape or self.ufl_free_indices: raise TypeError( "Cannot evaluate a nonscalar expression to a scalar value.") return self(()) # No known x def __float__(self): "Try to evaluate as scalar and cast to float." try: v = float(self._ufl_evaluate_scalar_()) except: v = NotImplemented return v def __bool__(self): "By default, all Expr are nonzero/False." return True def __nonzero__(self): "By default, all Expr are nonzero/False." return self.__bool__() @staticmethod def _ufl_coerce_(value): "Convert any value to a UFL type." # Quick skip for most types if isinstance(value, Expr): return value # Conversion from non-ufl types # (the _ufl_from_*_ functions are attached to Expr by ufl_type) ufl_from_type = "_ufl_from_{0}_".format(value.__class__.__name__) return getattr(Expr, ufl_from_type)(value) # if hasattr(Expr, ufl_from_type): # return getattr(Expr, ufl_from_type)(value) # Fail gracefully if no valid type conversion found # raise TypeError("Cannot convert a {0.__class__.__name__} to UFL type.".format(value)) # --- Special functions for string representations --- # All subclasses must implement _ufl_signature_data_ def _ufl_signature_data_(self, renumbering): "Return data that uniquely identifies form compiler relevant aspects of this object." raise NotImplementedError(self.__class__._ufl_signature_data_) # All subclasses must implement __repr__ def __repr__(self): "Return string representation this object can be reconstructed from." raise NotImplementedError(self.__class__.__repr__) # All subclasses must implement __str__ def __str__(self): "Return pretty print string representation of this object." raise NotImplementedError(self.__class__.__str__) def __unicode__(self): # Only in python 2 return str(self).decode("utf-8") def _ufl_err_str_(self): "Return a short string to represent this Expr in an error message." return "<%s id=%d>" % (self._ufl_class_.__name__, id(self)) def _repr_latex_(self): from ufl.algorithms import ufl2latex return "$%s$" % ufl2latex(self) def _repr_png_(self): from IPython.lib.latextools import latex_to_png return latex_to_png(self._repr_latex_()) # --- Special functions used for processing expressions --- def __eq__(self, other): """Checks whether the two expressions are represented the exact same way. This does not check if the expressions are mathematically equal or equivalent! Used by sets and dicts.""" raise NotImplementedError(self.__class__.__eq__) def __len__(self): "Length of expression. Used for iteration over vector expressions." s = self.ufl_shape if len(s) == 1: return s[0] raise NotImplementedError( "Cannot take length of non-vector expression.") def __iter__(self): "Iteration over vector expressions." for i in range(len(self)): yield self[i] def __floordiv__(self, other): "UFL does not support integer division." raise NotImplementedError(self.__class__.__floordiv__) def __pos__(self): "Unary + is a no-op." return self def __round__(self, n=None): "Round to nearest integer or to nearest nth decimal." return round(float(self), n) # --- Deprecated functions #def reconstruct(self, *operands): # """Return a new object of the same type with new operands. # Deprecated, please use Expr._ufl_expr_reconstruct_() instead.""" # deprecate("Expr.reconstruct() is deprecated, please use Expr._ufl_expr_reconstruct_() instead.") # return self._ufl_expr_reconstruct_(*operands) def geometric_dimension(self): "Return the geometric dimension this expression lives in." from ufl.domain import find_geometric_dimension return find_geometric_dimension(self)