def reference_value(self, o): # grad(o) == grad(rv(f)) -> K_ji*rgrad(rv(f))_rj f = o.ufl_operands[0] if f.ufl_element().mapping() == "physical": # TODO: Do we need to be more careful for immersed things? return ReferenceGrad(o) if not f._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") domain = f.ufl_domain() K = JacobianInverse(domain) r = indices(len(o.ufl_shape)) i, j = indices(2) Do = as_tensor(K[j, i] * ReferenceGrad(o)[r + (j, )], r + (i, )) return Do
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 jacobian(self, o): # d (grad_X(x))/d x => grad_X(Argument(x.function_space()) for (w, v) in zip(self._w, self._v): if o.ufl_domain() == w.ufl_domain() and isinstance( v.ufl_operands[0], FormArgument): return ReferenceGrad(v) return self.independent_terminal(o)
def reference_grad(self, o): "Represent ref_grad(ref_grad(f)) as RefGrad(RefGrad(f))." # Check that o is a "differential terminal" if not isinstance(o.ufl_operands[0], (ReferenceGrad, ReferenceValue, Terminal)): error("Expecting only grads applied to a terminal.") return ReferenceGrad(o)
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 test_index_simplification_reference_grad(self): mesh = Mesh(VectorElement("P", quadrilateral, 1)) i, = indices(1) A = as_tensor(Indexed(Jacobian(mesh), MultiIndex((i, i))), (i,)) expr = apply_derivatives(apply_geometry_lowering( apply_algebra_lowering(A[0]))) assert expr == ReferenceGrad(SpatialCoordinate(mesh))[0, 0] assert expr.ufl_free_indices == () assert expr.ufl_shape == ()
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 reference_value(self, o): # grad(o) == grad(rv(f)) -> K_ji*rgrad(rv(f))_rj f = o.ufl_operands[0] if not f._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") domain = f.ufl_domain() K = JacobianInverse(domain) r = indices(len(o.ufl_shape)) i, j = indices(2) Do = as_tensor(K[j, i] * ReferenceGrad(o)[r + (j, )], r + (i, )) return Do
def jacobian(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if domain.ufl_coordinate_element().mapping() != "identity": error("Piola mapped coordinates are not implemented.") # Note: No longer supporting domain.coordinates(), always # preserving SpatialCoordinate object. However if Jacobians # are not preserved, using # ReferenceGrad(SpatialCoordinate(domain)) to represent them. x = self.spatial_coordinate(SpatialCoordinate(domain)) return ReferenceGrad(x)
def reference_grad(self, o): # grad(o) == grad(rgrad(rv(f))) -> K_ji*rgrad(rgrad(rv(f)))_rj f = o.ufl_operands[0] valid_operand = f._ufl_is_in_reference_frame_ or isinstance(f, (JacobianInverse, SpatialCoordinate)) if not valid_operand: error("ReferenceGrad can only wrap a reference frame type!") domain = f.ufl_domain() K = JacobianInverse(domain) r = indices(len(o.ufl_shape)) i, j = indices(2) Do = as_tensor(K[j, i]*ReferenceGrad(o)[r + (j,)], r + (i,)) return Do
def reference_value(self, o): # grad(o) == grad(rv(f)) -> K_ji*rgrad(rv(f))_rj f = o.ufl_operands[0] if f.ufl_element().mapping() == "physical": # TODO: Do we need to be more careful for immersed things? return ReferenceGrad(o) if not f._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") domain = f.ufl_domain() K = JacobianInverse(domain) Do = grad_to_reference_grad(o, K) return Do
def grad_to_reference_grad(o, K): """Relates grad(o) to reference_grad(o) using the Jacobian inverse. Args ---- o: Operand K: Jacobian inverse Returns ------- Do: grad(o) written in terms of reference_grad(o) and K """ r = indices(len(o.ufl_shape)) i, j = indices(2) # grad(o) == K_ji rgrad(o)_rj Do = as_tensor(K[j, i] * ReferenceGrad(o)[r + (j,)], r + (i,)) return Do
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) for n in range(mt.local_derivatives): 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 reference_value(self, o): if not o.ufl_operands[0]._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") return ReferenceGrad(o)
def spatial_coordinate(self, o): "dx/dX = J" # Don't convert back to J, otherwise we get in a loop return ReferenceGrad(o)
def _mapped(self, t): # Check that we have a valid input object if not isinstance(t, Terminal): error("Expecting a Terminal.") # Get modifiers accumulated by previous handler calls ngrads = self._ngrads restricted = self._restricted avg = self._avg if avg != "": error("Averaging not implemented.") # FIXME # These are the global (g) and reference (r) values if isinstance(t, FormArgument): g = t r = ReferenceValue(g) elif isinstance(t, GeometricQuantity): g = t r = g else: error("Unexpected type {0}.".format(type(t).__name__)) # Some geometry mapping objects we may need multiple times below domain = t.ufl_domain() J = Jacobian(domain) detJ = JacobianDeterminant(domain) K = JacobianInverse(domain) # Restrict geometry objects if applicable if restricted: J = J(restricted) detJ = detJ(restricted) K = K(restricted) # Create Hdiv mapping from possibly restricted geometry objects Mdiv = (1.0 / detJ) * J # Get component indices of global and reference terminal objects gtsh = g.ufl_shape # rtsh = r.ufl_shape gtcomponents = compute_indices(gtsh) # rtcomponents = compute_indices(rtsh) # Create core modified terminal, with eventual # layers of grad applied directly to the terminal, # then eventual restriction applied last for i in range(ngrads): g = Grad(g) r = ReferenceGrad(r) if restricted: g = g(restricted) r = r(restricted) # Get component indices of global and reference objects with # grads applied gsh = g.ufl_shape # rsh = r.ufl_shape # gcomponents = compute_indices(gsh) # rcomponents = compute_indices(rsh) # Get derivative component indices dsh = gsh[len(gtsh):] dcomponents = compute_indices(dsh) # Create nested array to hold expressions for global # components mapped from reference values def ndarray(shape): if len(shape) == 0: return [None] elif len(shape) == 1: return [None] * shape[-1] else: return [ndarray(shape[1:]) for i in range(shape[0])] global_components = ndarray(gsh) # Compute mapping from reference values for each global component for gtc in gtcomponents: if isinstance(t, FormArgument): # Find basic subelement and element-local component # ec, element, eoffset = t.ufl_element().extract_component2(gtc) # FIXME: Translate this correctly eoffset = 0 ec, element = t.ufl_element().extract_reference_component(gtc) # Select mapping M from element, pick row emapping = # M[ec,:], or emapping = [] if no mapping if isinstance(element, MixedElement): error("Expecting a basic element here.") mapping = element.mapping() if mapping == "contravariant Piola": # S == HDiv: # Handle HDiv elements with contravariant piola # mapping contravariant_hdiv_mapping = (1/det J) * # J * PullbackOf(o) ec, = ec emapping = Mdiv[ec, :] elif mapping == "covariant Piola": # S == HCurl: # Handle HCurl elements with covariant piola mapping # covariant_hcurl_mapping = JinvT * PullbackOf(o) ec, = ec emapping = K[:, ec] # Column of K is row of K.T elif mapping == "identity": emapping = None else: error("Unknown mapping {0}".format(mapping)) elif isinstance(t, GeometricQuantity): eoffset = 0 emapping = None else: error("Unexpected type {0}.".format(type(t).__name__)) # Create indices # if rtsh: # i = Index() if len(dsh) != ngrads: error("Mismatch between derivative shape and ngrads.") if ngrads: ii = indices(ngrads) else: ii = () # Apply mapping row to reference object if emapping: # Mapped, always nonscalar terminal Not # using IndexSum for the mapping row dot product to # keep it simple, because we don't have a slice type emapped_ops = [emapping[s] * Indexed(r, MultiIndex((FixedIndex(eoffset + s),) + ii)) for s in range(len(emapping))] emapped = sum(emapped_ops[1:], emapped_ops[0]) elif gtc: # Nonscalar terminal, unmapped emapped = Indexed(r, MultiIndex((FixedIndex(eoffset),) + ii)) elif ngrads: # Scalar terminal, unmapped, with derivatives emapped = Indexed(r, MultiIndex(ii)) else: # Scalar terminal, unmapped, no derivatives emapped = r for di in dcomponents: # Multiply derivative mapping rows, parameterized by # free column indices dmapping = as_ufl(1) for j in range(ngrads): dmapping *= K[ii[j], di[j]] # Row of K is column of JinvT # Compute mapping from reference values for this # particular global component global_value = dmapping * emapped # Apply index sums # if rtsh: # global_value = IndexSum(global_value, MultiIndex((i,))) # for j in range(ngrads): # Applied implicitly in the dmapping * emapped above # global_value = IndexSum(global_value, MultiIndex((ii[j],))) # This is the component index into the full object # with grads applied gc = gtc + di # Insert in nested list comp = global_components for i in gc[:-1]: comp = comp[i] comp[0 if gc == () else gc[-1]] = global_value # Wrap nested list in as_tensor unless we have a scalar # expression if gsh: tensor = as_tensor(global_components) else: tensor, = global_components return tensor
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 apply_grads(f): for i in range(ngrads): f = ReferenceGrad(f) return f
def test_change_to_reference_grad(): cell = triangle domain = Mesh(cell) U = FunctionSpace(domain, FiniteElement("CG", cell, 1)) V = FunctionSpace(domain, VectorElement("CG", cell, 1)) u = Coefficient(U) v = Coefficient(V) Jinv = JacobianInverse(domain) i, j, k = indices(3) q, r, s = indices(3) t, = indices(1) # Single grad change on a scalar function expr = grad(u) actual = change_to_reference_grad(expr) expected = as_tensor(Jinv[k, i] * ReferenceGrad(u)[k], (i, )) assert renumber_indices(actual) == renumber_indices(expected) # Single grad change on a vector valued function expr = grad(v) actual = change_to_reference_grad(expr) expected = as_tensor(Jinv[k, j] * ReferenceGrad(v)[i, k], (i, j)) assert renumber_indices(actual) == renumber_indices(expected) # Multiple grads should work fine for affine domains: expr = grad(grad(u)) actual = change_to_reference_grad(expr) expected = as_tensor( Jinv[s, j] * (Jinv[r, i] * ReferenceGrad(ReferenceGrad(u))[r, s]), (i, j)) assert renumber_indices(actual) == renumber_indices(expected) expr = grad(grad(grad(u))) actual = change_to_reference_grad(expr) expected = as_tensor( Jinv[s, k] * (Jinv[r, j] * (Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(u)))[q, r, s])), (i, j, k)) assert renumber_indices(actual) == renumber_indices(expected) # Multiple grads on a vector valued function expr = grad(grad(v)) actual = change_to_reference_grad(expr) expected = as_tensor( Jinv[s, j] * (Jinv[r, i] * ReferenceGrad(ReferenceGrad(v))[t, r, s]), (t, i, j)) assert renumber_indices(actual) == renumber_indices(expected) expr = grad(grad(grad(v))) actual = change_to_reference_grad(expr) expected = as_tensor( Jinv[s, k] * (Jinv[r, j] * (Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(v)))[t, q, r, s])), (t, i, j, k)) assert renumber_indices(actual) == renumber_indices(expected)