def test_coordinates_never_cellwise_constant_vertex(): # The only exception here: domains = Mesh(Cell("vertex", 3)) assert domains.ufl_cell().cellname() == "vertex" e = SpatialCoordinate(domains) assert is_cellwise_constant(e) e = CellCoordinate(domains) assert is_cellwise_constant(e)
def test_coefficient_sometimes_cellwise_constant(domains_not_linear): e = Constant(domains_not_linear) assert is_cellwise_constant(e) V = FiniteElement("DG", domains_not_linear.ufl_cell(), 0) e = Coefficient(V) assert is_cellwise_constant(e) V = FiniteElement("R", domains_not_linear.ufl_cell(), 0) e = Coefficient(V) assert is_cellwise_constant(e)
def test_always_cellwise_constant_geometric_quantities(domains): "Test geometric quantities that are always constant over a cell." e = CellVolume(domains) assert is_cellwise_constant(e) e = Circumradius(domains) assert is_cellwise_constant(e) e = FacetArea(domains) assert is_cellwise_constant(e) e = MinFacetEdgeLength(domains) assert is_cellwise_constant(e) e = MaxFacetEdgeLength(domains) assert is_cellwise_constant(e)
def construct_modified_terminal(mt, terminal): """Construct a modified terminal given terminal modifiers from an analysed modified terminal and a terminal.""" expr = terminal if mt.reference_value: expr = ReferenceValue(expr) dim = expr.ufl_domain().topological_dimension() for n in range(mt.local_derivatives): # Return zero if expression is trivially constant. This has to # happen here because ReferenceGrad has no access to the # topological dimension of a literal zero. if is_cellwise_constant(expr): expr = Zero(expr.ufl_shape + (dim, ), expr.ufl_free_indices, expr.ufl_index_dimensions) else: expr = ReferenceGrad(expr) # No need to apply restrictions to ConstantValue terminals if not isinstance(expr, ConstantValue): if mt.restriction == '+': expr = PositiveRestricted(expr) elif mt.restriction == '-': expr = NegativeRestricted(expr) return expr
def Dn(f): """UFL operator: Take the directional derivative of *f* in the facet normal direction, Dn(f) := dot(grad(f), n).""" f = as_ufl(f) if is_cellwise_constant(f): return Zero(f.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions) return dot(grad(f), FacetNormal(f.ufl_domain()))
def geometric_quantity(self, v): "Some geometric quantities are cellwise constant. Others are nonpolynomial and thus hard to estimate." if is_cellwise_constant(v): return 0 else: # As a heuristic, just returning domain degree to bump up degree somewhat return v.ufl_domain().ufl_coordinate_element().degree()
def construct_modified_terminal(mt, terminal): """Construct a modified terminal given terminal modifiers from an analysed modified terminal and a terminal.""" expr = terminal if mt.reference_value: expr = ReferenceValue(expr) dim = expr.ufl_domain().topological_dimension() for n in range(mt.local_derivatives): # Return zero if expression is trivially constant. This has to # happen here because ReferenceGrad has no access to the # topological dimension of a literal zero. if is_cellwise_constant(expr): expr = Zero(expr.ufl_shape + (dim,), expr.ufl_free_indices, expr.ufl_index_dimensions) else: expr = ReferenceGrad(expr) if mt.averaged == "cell": expr = CellAvg(expr) elif mt.averaged == "facet": expr = FacetAvg(expr) # No need to apply restrictions to ConstantValue terminals if not isinstance(expr, ConstantValue): if mt.restriction == '+': expr = PositiveRestricted(expr) elif mt.restriction == '-': expr = NegativeRestricted(expr) return expr
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 __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 geometric_quantity(self, o): "dg/dX = 0 if piecewise constant, otherwise ReferenceGrad(g)" if is_cellwise_constant(o): return self.independent_terminal(o) else: # TODO: Which types does this involve? I don't think the # form compilers will handle this. return ReferenceGrad(o)
def jacobian_inverse(self, o): # grad(K) == K_ji rgrad(K)_rj if is_cellwise_constant(o): return self.independent_terminal(o) if not o._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") Do = grad_to_reference_grad(o, o) return Do
def geometric_quantity(self, o): """Default for geometric quantities is dg/dx = 0 if piecewise constant, otherwise keep Grad(g). Override for specific types if other behaviour is needed.""" if is_cellwise_constant(o): return self.independent_terminal(o) else: # TODO: Which types does this involve? I don't think the # form compilers will handle this. return Grad(o)
def __new__(cls, f): if f.ufl_free_indices: error("Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): return Zero(f.ufl_shape[1:]) # No free indices asserted above return CompoundDerivative.__new__(cls)
def jacobian_inverse(self, o): # grad(K) == K_ji rgrad(K)_rj if is_cellwise_constant(o): return self.independent_terminal(o) if not o._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") r = indices(len(o.ufl_shape)) i, j = indices(2) Do = as_tensor(o[j, i] * ReferenceGrad(o)[r + (j, )], r + (i, )) return Do
def jacobian_inverse(self, o): # grad(K) == K_ji rgrad(K)_rj if is_cellwise_constant(o): return self.independent_terminal(o) if not o._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") r = indices(len(o.ufl_shape)) i, j = indices(2) Do = as_tensor(o[j, i]*ReferenceGrad(o)[r + (j,)], r + (i,)) return Do
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)
def analyse_dependencies(F, mt_unique_table_reference): # Sets 'status' of all nodes to either: 'inactive', 'piecewise' or 'varying' # Children of 'target' nodes are either 'piecewise' or 'varying'. # All other nodes are 'inactive'. # Varying nodes are identified by their tables ('tr'). All their parent # nodes are also set to 'varying' - any remaining active nodes are 'piecewise'. # Set targets, and dependencies to 'active' targets = [i for i, v in F.nodes.items() if v.get('target')] for i, v in F.nodes.items(): v['status'] = 'inactive' while targets: s = targets.pop() F.nodes[s]['status'] = 'active' for j in F.out_edges[s]: if F.nodes[j]['status'] == 'inactive': targets.append(j) # Build piecewise/varying markers for factorized_vertices varying_ttypes = ("varying", "uniform", "quadrature") varying_indices = [] for i, v in F.nodes.items(): if v.get('mt') is None: continue tr = v.get('tr') if tr is not None: ttype = tr.ttype # Check if table computations have revealed values varying over points # Note: uniform means entity-wise uniform, varying over points if ttype in varying_ttypes: varying_indices.append(i) else: if ttype not in ("fixed", "piecewise", "ones", "zeros"): raise RuntimeError("Invalid ttype %s" % (ttype, )) elif not is_cellwise_constant(v['expression']): raise RuntimeError("Error") # Keeping this check to be on the safe side, # not sure which cases this will cover (if any) # varying_indices.append(i) # Set all parents of active varying nodes to 'varying' while varying_indices: s = varying_indices.pop() if F.nodes[s]['status'] == 'active': F.nodes[s]['status'] = 'varying' for j in F.in_edges[s]: varying_indices.append(j) # Any remaining active nodes must be 'piecewise' for i, v in F.nodes.items(): if v['status'] == 'active': v['status'] = 'piecewise'
def geometric_quantity(self, o): """Default for geometric quantities is do/dx = 0 if piecewise constant, otherwise transform derivatives to reference derivatives. Override for specific types if other behaviour is needed.""" if is_cellwise_constant(o): return self.independent_terminal(o) else: domain = o.ufl_domain() K = JacobianInverse(domain) Do = grad_to_reference_grad(o, K) return Do
def default_partition_seed(expr, rank): """ Partition 0: Piecewise constant on each cell (including Real and DG0 coefficients) Partition 1: Depends on x Partition 2: Depends on x and coefficients Partitions [3,3+rank): depend on argument with count partition-3 """ # TODO: Use named constants for the partition numbers here modifiers = (Grad, Restricted, Indexed ) # FIXME: Add CellAvg, FacetAvg types here, others? if isinstance(expr, modifiers): return default_partition_seed(expr.ufl_operands[0], rank) elif isinstance(expr, Argument): ac = expr.number() assert 0 <= ac < rank poffset = 3 p = poffset + ac return p elif isinstance(expr, Coefficient): if is_cellwise_constant( expr): # This is crap, doesn't include grad modifier return 0 else: return 2 elif isinstance(expr, GeometricQuantity): if is_cellwise_constant( expr): # This is crap, doesn't include grad modifier return 0 else: return 1 elif isinstance(expr, ConstantValue): return 0 else: error("Don't know how to handle %s" % expr)
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 analyse_dependencies(V, V_deps, V_targets, modified_terminal_indices, modified_terminals, mt_unique_table_reference): # Count the number of dependencies every subexpr has V_depcount = compute_dependency_count(V_deps) # Build the 'inverse' of the sparse dependency matrix inv_deps = invert_dependencies(V_deps, V_depcount) # Mark subexpressions of V that are actually needed for final result active, num_active = mark_active(V_deps, V_targets) # Build piecewise/varying markers for factorized_vertices varying_ttypes = ("varying", "uniform", "quadrature") varying_indices = [] for i, mt in zip(modified_terminal_indices, modified_terminals): tr = mt_unique_table_reference.get(mt) if tr is not None: ttype = tr.ttype # Check if table computations have revealed values varying over points # Note: uniform means entity-wise uniform, varying over points if ttype in varying_ttypes: varying_indices.append(i) else: if ttype not in ("fixed", "piecewise", "ones", "zeros"): error("Invalid ttype %s" % (ttype,)) elif not is_cellwise_constant(V[i]): # Keeping this check to be on the safe side, # not sure which cases this will cover (if any) varying_indices.append(i) # Mark every subexpression that is computed # from the spatially dependent terminals varying, num_varying = mark_image(inv_deps, varying_indices) # The rest of the subexpressions are piecewise constant (1-1=0, 1-0=1) piecewise = 1 - varying # Unmark non-active subexpressions varying *= active piecewise *= active # TODO: Skip literals in both varying and piecewise # nonliteral = ... # varying *= nonliteral # piecewise *= nonliteral return inv_deps, active, piecewise, varying
def default_partition_seed(expr, rank): """ Partition 0: Piecewise constant on each cell (including Real and DG0 coefficients) Partition 1: Depends on x Partition 2: Depends on x and coefficients Partitions [3,3+rank): depend on argument with count partition-3 """ # TODO: Use named constants for the partition numbers here modifiers = (Grad, Restricted, Indexed) # FIXME: Add CellAvg, FacetAvg types here, others? if isinstance(expr, modifiers): return default_partition_seed(expr.ufl_operands[0], rank) elif isinstance(expr, Argument): ac = expr.number() assert 0 <= ac < rank poffset = 3 p = poffset + ac return p elif isinstance(expr, Coefficient): if is_cellwise_constant(expr): # This is crap, doesn't include grad modifier return 0 else: return 2 elif isinstance(expr, GeometricQuantity): if is_cellwise_constant(expr): # This is crap, doesn't include grad modifier return 0 else: return 1 elif isinstance(expr, ConstantValue): return 0 else: error("Don't know how to handle %s" % expr)
def analyse_dependencies(V, V_deps, V_targets, modified_terminal_indices, modified_terminals, mt_unique_table_reference): # Count the number of dependencies every subexpr has V_depcount = compute_dependency_count(V_deps) # Build the 'inverse' of the sparse dependency matrix inv_deps = invert_dependencies(V_deps, V_depcount) # Mark subexpressions of V that are actually needed for final result active, num_active = mark_active(V_deps, V_targets) # Build piecewise/varying markers for factorized_vertices varying_ttypes = ("varying", "uniform", "quadrature") varying_indices = [] for i, mt in zip(modified_terminal_indices, modified_terminals): tr = mt_unique_table_reference.get(mt) if tr is not None: ttype = tr.ttype # Check if table computations have revealed values varying over points # Note: uniform means entity-wise uniform, varying over points if ttype in varying_ttypes: varying_indices.append(i) else: if ttype not in ("fixed", "piecewise", "ones", "zeros"): error("Invalid ttype %s" % (ttype, )) elif not is_cellwise_constant(V[i]): # Keeping this check to be on the safe side, # not sure which cases this will cover (if any) varying_indices.append(i) # Mark every subexpression that is computed # from the spatially dependent terminals varying, num_varying = mark_image(inv_deps, varying_indices) # The rest of the subexpressions are piecewise constant (1-1=0, 1-0=1) piecewise = 1 - varying # Unmark non-active subexpressions varying *= active piecewise *= active # TODO: Skip literals in both varying and piecewise # nonliteral = ... # varying *= nonliteral # piecewise *= nonliteral return inv_deps, active, piecewise, varying
def test_always_cellwise_constant_geometric_quantities(domains): "Test geometric quantities that are always constant over a cell." e = CellVolume(domains) assert is_cellwise_constant(e) e = CellDiameter(domains) assert is_cellwise_constant(e) e = Circumradius(domains) assert is_cellwise_constant(e) e = FacetArea(domains) assert is_cellwise_constant(e) e = MinFacetEdgeLength(domains) assert is_cellwise_constant(e) e = MaxFacetEdgeLength(domains) assert is_cellwise_constant(e)
def mappings_are_cellwise_constant(domain, test): e = Jacobian(domain) assert is_cellwise_constant(e) == test e = JacobianDeterminant(domain) assert is_cellwise_constant(e) == test e = JacobianInverse(domain) assert is_cellwise_constant(e) == test if domain.topological_dimension() != 1: e = FacetJacobian(domain) assert is_cellwise_constant(e) == test e = FacetJacobianDeterminant(domain) assert is_cellwise_constant(e) == test e = FacetJacobianInverse(domain) assert is_cellwise_constant(e) == test
def test_coordinates_never_cellwise_constant(domains): e = SpatialCoordinate(domains) assert not is_cellwise_constant(e) e = CellCoordinate(domains) assert not is_cellwise_constant(e)
def facetnormal_cellwise_constant(domain, test): e = FacetNormal(domain) assert is_cellwise_constant(e) == test
def test_coefficient_mostly_not_cellwise_constant(domains_not_linear): V = FiniteElement("DG", domains_not_linear.ufl_cell(), 1) e = Coefficient(V) assert not is_cellwise_constant(e) e = TestFunction(V) assert not is_cellwise_constant(e)
def grad(self, o): # Peel off the Grads and count them, and get restriction if # it's between the grad and the terminal ngrads = 0 restricted = '' rv = False while not o._ufl_is_terminal_: if isinstance(o, Grad): o, = o.ufl_operands ngrads += 1 elif isinstance(o, Restricted): restricted = o.side() o, = o.ufl_operands elif isinstance(o, ReferenceValue): rv = True o, = o.ufl_operands else: error("Invalid type %s" % o._ufl_class_.__name__) f = o if rv: f = ReferenceValue(f) # Get domain and create Jacobian inverse object domain = o.ufl_domain() Jinv = JacobianInverse(domain) if is_cellwise_constant(Jinv): # Optimise slightly by turning Grad(Grad(...)) into # J^(-T)J^(-T)RefGrad(RefGrad(...)) # rather than J^(-T)RefGrad(J^(-T)RefGrad(...)) # Create some new indices ii = indices(len(f.ufl_shape)) # Indices to get to the scalar component of f jj = indices(ngrads) # Indices to sum over the local gradient axes with the inverse Jacobian kk = indices(ngrads) # Indices for the leftover inverse Jacobian axes # Preserve restricted property if restricted: Jinv = Jinv(restricted) f = f(restricted) # Apply the same number of ReferenceGrad without mappings lgrad = f for i in range(ngrads): lgrad = ReferenceGrad(lgrad) # Apply mappings with scalar indexing operations (assumes # ReferenceGrad(Jinv) is zero) jinv_lgrad_f = lgrad[ii + jj] for j, k in zip(jj, kk): jinv_lgrad_f = Jinv[j, k] * jinv_lgrad_f # Wrap back in tensor shape, derivative axes at the end jinv_lgrad_f = as_tensor(jinv_lgrad_f, ii + kk) else: # J^(-T)RefGrad(J^(-T)RefGrad(...)) # Preserve restricted property if restricted: Jinv = Jinv(restricted) f = f(restricted) jinv_lgrad_f = f for foo in range(ngrads): ii = indices(len(jinv_lgrad_f.ufl_shape)) # Indices to get to the scalar component of f j, k = indices(2) lgrad = ReferenceGrad(jinv_lgrad_f) jinv_lgrad_f = Jinv[j, k] * lgrad[ii + (j,)] # Wrap back in tensor shape, derivative axes at the end jinv_lgrad_f = as_tensor(jinv_lgrad_f, ii + (k,)) return jinv_lgrad_f
def coefficient(self, o): if is_cellwise_constant(o): return self.independent_terminal(o) return Grad(o)