def test_change_unmapped_form_arguments_to_reference_frame(): U = FiniteElement("CG", triangle, 1) V = VectorElement("CG", triangle, 1) T = TensorElement("CG", triangle, 1) expr = Coefficient(U) assert change_to_reference_frame(expr) == ReferenceValue(expr) expr = Coefficient(V) assert change_to_reference_frame(expr) == ReferenceValue(expr) expr = Coefficient(T) assert change_to_reference_frame(expr) == ReferenceValue(expr)
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 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 check_single_function_pullback(g, mappings): expected = mappings[g] actual = apply_single_function_pullbacks(ReferenceValue(g), g.ufl_element()) assert expected.ufl_shape == actual.ufl_shape for idx in numpy.ndindex(actual.ufl_shape): rexp = renumber_indices(expected[idx]) ract = renumber_indices(actual[idx]) if not rexp == ract: print() print("In check_single_function_pullback:") print("input:") print(repr(g)) print("expected:") print(str(rexp)) print("actual:") print(str(ract)) print("signatures:") print((expected**2*dx).signature()) print((actual**2*dx).signature()) print() assert ract == rexp
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 test_apply_single_function_pullbacks_triangle3d(): triangle3d = Cell("triangle", geometric_dimension=3) cell = triangle3d domain = as_domain(cell) UL2 = FiniteElement("DG L2", cell, 1) U0 = FiniteElement("DG", cell, 0) U = FiniteElement("CG", cell, 1) V = VectorElement("CG", cell, 1) Vd = FiniteElement("RT", cell, 1) Vc = FiniteElement("N1curl", cell, 1) T = TensorElement("CG", cell, 1) S = TensorElement("CG", cell, 1, symmetry=True) COV2T = FiniteElement("Regge", cell, 0) # (0, 2)-symmetric tensors CONTRA2T = FiniteElement("HHJ", cell, 0) # (2, 0)-symmetric tensors Uml2 = UL2 * UL2 Um = U * U Vm = U * V Vdm = V * Vd Vcm = Vd * Vc Tm = Vc * T Sm = T * S Vd0 = Vd * U0 # case from failing ffc demo W = S * T * Vc * Vd * V * U ul2 = Coefficient(UL2) u = Coefficient(U) v = Coefficient(V) vd = Coefficient(Vd) vc = Coefficient(Vc) t = Coefficient(T) s = Coefficient(S) cov2t = Coefficient(COV2T) contra2t = Coefficient(CONTRA2T) uml2 = Coefficient(Uml2) um = Coefficient(Um) vm = Coefficient(Vm) vdm = Coefficient(Vdm) vcm = Coefficient(Vcm) tm = Coefficient(Tm) sm = Coefficient(Sm) vd0m = Coefficient(Vd0) # case from failing ffc demo w = Coefficient(W) rul2 = ReferenceValue(ul2) ru = ReferenceValue(u) rv = ReferenceValue(v) rvd = ReferenceValue(vd) rvc = ReferenceValue(vc) rt = ReferenceValue(t) rs = ReferenceValue(s) rcov2t = ReferenceValue(cov2t) rcontra2t = ReferenceValue(contra2t) ruml2 = ReferenceValue(uml2) rum = ReferenceValue(um) rvm = ReferenceValue(vm) rvdm = ReferenceValue(vdm) rvcm = ReferenceValue(vcm) rtm = ReferenceValue(tm) rsm = ReferenceValue(sm) rvd0m = ReferenceValue(vd0m) rw = ReferenceValue(w) assert len(w) == 9 + 9 + 3 + 3 + 3 + 1 assert len(rw) == 6 + 9 + 2 + 2 + 3 + 1 assert len(w) == 28 assert len(rw) == 23 assert len(vd0m) == 4 assert len(rvd0m) == 3 # Geometric quantities we need: J = Jacobian(domain) detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) # o = CellOrientation(domain) i, j, k, l = indices(4) # Contravariant H(div) Piola mapping: M_hdiv = ((1.0 / detJ) * J) # Not applying cell orientation here # Covariant H(curl) Piola mapping: Jinv.T mappings = { # Simple elements should get a simple representation ul2: rul2 / detJ, u: ru, v: rv, vd: as_vector(M_hdiv[i, j] * rvd[j], i), vc: as_vector(Jinv[j, i] * rvc[j], i), t: rt, s: as_tensor([[rs[0], rs[1], rs[2]], [rs[1], rs[3], rs[4]], [rs[2], rs[4], rs[5]]]), cov2t: as_tensor(Jinv[k, i] * rcov2t[k, l] * Jinv[l, j], (i, j)), contra2t: as_tensor( (1.0 / detJ) * (1.0 / detJ) * J[i, k] * rcontra2t[k, l] * J[j, l], (i, j)), # Mixed elements become a bit more complicated uml2: as_vector([ruml2[0] / detJ, ruml2[1] / detJ]), um: rum, vm: rvm, vdm: as_vector([ # V rvdm[0], rvdm[1], rvdm[2], # Vd M_hdiv[0, j] * as_vector([rvdm[3], rvdm[4]])[j], M_hdiv[1, j] * as_vector([rvdm[3], rvdm[4]])[j], M_hdiv[2, j] * as_vector([rvdm[3], rvdm[4]])[j], ]), vcm: as_vector([ # Vd M_hdiv[0, j] * as_vector([rvcm[0], rvcm[1]])[j], M_hdiv[1, j] * as_vector([rvcm[0], rvcm[1]])[j], M_hdiv[2, j] * as_vector([rvcm[0], rvcm[1]])[j], # Vc Jinv[i, 0] * as_vector([rvcm[2], rvcm[3]])[i], Jinv[i, 1] * as_vector([rvcm[2], rvcm[3]])[i], Jinv[i, 2] * as_vector([rvcm[2], rvcm[3]])[i], ]), tm: as_vector([ # Vc Jinv[i, 0] * as_vector([rtm[0], rtm[1]])[i], Jinv[i, 1] * as_vector([rtm[0], rtm[1]])[i], Jinv[i, 2] * as_vector([rtm[0], rtm[1]])[i], # T rtm[2], rtm[3], rtm[4], rtm[5], rtm[6], rtm[7], rtm[8], rtm[9], rtm[10], ]), sm: as_vector([ # T rsm[0], rsm[1], rsm[2], rsm[3], rsm[4], rsm[5], rsm[6], rsm[7], rsm[8], # S rsm[9], rsm[10], rsm[11], rsm[10], rsm[12], rsm[13], rsm[11], rsm[13], rsm[14], ]), # Case from failing ffc demo: vd0m: as_vector([ M_hdiv[0, j] * as_vector([rvd0m[0], rvd0m[1]])[j], M_hdiv[1, j] * as_vector([rvd0m[0], rvd0m[1]])[j], M_hdiv[2, j] * as_vector([rvd0m[0], rvd0m[1]])[j], rvd0m[2] ]), # This combines it all: w: as_vector([ # S rw[0], rw[1], rw[2], rw[1], rw[3], rw[4], rw[2], rw[4], rw[5], # T rw[6], rw[7], rw[8], rw[9], rw[10], rw[11], rw[12], rw[13], rw[14], # Vc Jinv[i, 0] * as_vector([rw[15], rw[16]])[i], Jinv[i, 1] * as_vector([rw[15], rw[16]])[i], Jinv[i, 2] * as_vector([rw[15], rw[16]])[i], # Vd M_hdiv[0, j] * as_vector([rw[17], rw[18]])[j], M_hdiv[1, j] * as_vector([rw[17], rw[18]])[j], M_hdiv[2, j] * as_vector([rw[17], rw[18]])[j], # V rw[19], rw[20], rw[21], # U rw[22], ]), } # Check functions of various elements outside a mixed context check_single_function_pullback(ul2, mappings) check_single_function_pullback(u, mappings) check_single_function_pullback(v, mappings) check_single_function_pullback(vd, mappings) check_single_function_pullback(vc, mappings) check_single_function_pullback(t, mappings) check_single_function_pullback(s, mappings) check_single_function_pullback(cov2t, mappings) check_single_function_pullback(contra2t, mappings) # Check functions of various elements inside a mixed context check_single_function_pullback(uml2, mappings) check_single_function_pullback(um, mappings) check_single_function_pullback(vm, mappings) check_single_function_pullback(vdm, mappings) check_single_function_pullback(vcm, mappings) check_single_function_pullback(tm, mappings) check_single_function_pullback(sm, mappings) # Check the ridiculous mixed element W combining it all check_single_function_pullback(w, mappings)
def test_apply_single_function_pullbacks_triangle(): cell = triangle domain = as_domain(cell) Ul2 = FiniteElement("DG L2", cell, 1) U = FiniteElement("CG", cell, 1) V = VectorElement("CG", cell, 1) Vd = FiniteElement("RT", cell, 1) Vc = FiniteElement("N1curl", cell, 1) T = TensorElement("CG", cell, 1) S = TensorElement("CG", cell, 1, symmetry=True) Uml2 = Ul2 * Ul2 Um = U * U Vm = U * V Vdm = V * Vd Vcm = Vd * Vc Tm = Vc * T Sm = T * S W = S * T * Vc * Vd * V * U ul2 = Coefficient(Ul2) u = Coefficient(U) v = Coefficient(V) vd = Coefficient(Vd) vc = Coefficient(Vc) t = Coefficient(T) s = Coefficient(S) uml2 = Coefficient(Uml2) um = Coefficient(Um) vm = Coefficient(Vm) vdm = Coefficient(Vdm) vcm = Coefficient(Vcm) tm = Coefficient(Tm) sm = Coefficient(Sm) w = Coefficient(W) rul2 = ReferenceValue(ul2) ru = ReferenceValue(u) rv = ReferenceValue(v) rvd = ReferenceValue(vd) rvc = ReferenceValue(vc) rt = ReferenceValue(t) rs = ReferenceValue(s) ruml2 = ReferenceValue(uml2) rum = ReferenceValue(um) rvm = ReferenceValue(vm) rvdm = ReferenceValue(vdm) rvcm = ReferenceValue(vcm) rtm = ReferenceValue(tm) rsm = ReferenceValue(sm) rw = ReferenceValue(w) assert len(w) == 4 + 4 + 2 + 2 + 2 + 1 assert len(rw) == 3 + 4 + 2 + 2 + 2 + 1 assert len(w) == 15 assert len(rw) == 14 # Geometric quantities we need: J = Jacobian(domain) detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) i, j, k, l = indices(4) # Contravariant H(div) Piola mapping: M_hdiv = (1.0 / detJ) * J # Covariant H(curl) Piola mapping: Jinv.T mappings = { # Simple elements should get a simple representation ul2: rul2 / detJ, u: ru, v: rv, vd: as_vector(M_hdiv[i, j] * rvd[j], i), vc: as_vector(Jinv[j, i] * rvc[j], i), t: rt, s: as_tensor([[rs[0], rs[1]], [rs[1], rs[2]]]), # Mixed elements become a bit more complicated uml2: as_vector([ruml2[0] / detJ, ruml2[1] / detJ]), um: rum, vm: rvm, vdm: as_vector([ # V rvdm[0], rvdm[1], # Vd M_hdiv[0, j] * as_vector([rvdm[2], rvdm[3]])[j], M_hdiv[1, j] * as_vector([rvdm[2], rvdm[3]])[j], ]), vcm: as_vector([ # Vd M_hdiv[0, j] * as_vector([rvcm[0], rvcm[1]])[j], M_hdiv[1, j] * as_vector([rvcm[0], rvcm[1]])[j], # Vc Jinv[i, 0] * as_vector([rvcm[2], rvcm[3]])[i], Jinv[i, 1] * as_vector([rvcm[2], rvcm[3]])[i], ]), tm: as_vector([ # Vc Jinv[i, 0] * as_vector([rtm[0], rtm[1]])[i], Jinv[i, 1] * as_vector([rtm[0], rtm[1]])[i], # T rtm[2], rtm[3], rtm[4], rtm[5], ]), sm: as_vector([ # T rsm[0], rsm[1], rsm[2], rsm[3], # S rsm[4], rsm[5], rsm[5], rsm[6], ]), # This combines it all: w: as_vector([ # S rw[0], rw[1], rw[1], rw[2], # T rw[3], rw[4], rw[5], rw[6], # Vc Jinv[i, 0] * as_vector([rw[7], rw[8]])[i], Jinv[i, 1] * as_vector([rw[7], rw[8]])[i], # Vd M_hdiv[0, j] * as_vector([rw[9], rw[10]])[j], M_hdiv[1, j] * as_vector([rw[9], rw[10]])[j], # V rw[11], rw[12], # U rw[13], ]), } # Check functions of various elements outside a mixed context check_single_function_pullback(ul2, mappings) check_single_function_pullback(u, mappings) check_single_function_pullback(v, mappings) check_single_function_pullback(vd, mappings) check_single_function_pullback(vc, mappings) check_single_function_pullback(t, mappings) check_single_function_pullback(s, mappings) # Check functions of various elements inside a mixed context check_single_function_pullback(uml2, mappings) check_single_function_pullback(um, mappings) check_single_function_pullback(vm, mappings) check_single_function_pullback(vdm, mappings) check_single_function_pullback(vcm, mappings) check_single_function_pullback(tm, mappings) check_single_function_pullback(sm, mappings) # Check the ridiculous mixed element W combining it all check_single_function_pullback(w, mappings)
def apply_single_function_pullbacks(g): element = g.ufl_element() mapping = element.mapping() r = ReferenceValue(g) gsh = g.ufl_shape rsh = r.ufl_shape if mapping == "physical": # TODO: Is this right for immersed things assert gsh == rsh return r # Shortcut the "identity" case which includes Expression and # Constant from dolfin that may be ill-formed without a domain # (until we get that fixed) if mapping == "identity": assert rsh == gsh return r gsize = product(gsh) rsize = product(rsh) # Create some geometric objects for reuse domain = g.ufl_domain() J = Jacobian(domain) detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) # Create contravariant transform for reuse (note that detJ is the # _signed_ (pseudo-)determinant) transform_hdiv = (1.0 / detJ) * J # Shortcut simple cases for a more efficient representation, # including directly Piola-mapped elements and mixed elements of # any combination of affinely mapped elements without symmetries if mapping == "symmetries": fcm = element.flattened_sub_element_mapping() assert gsize >= rsize assert len(fcm) == gsize assert sorted(set(fcm)) == sorted(range(rsize)) g_components = [r[fcm[i]] for i in range(gsize)] g_components = reshape_to_nested_list(g_components, gsh) f = as_tensor(g_components) assert f.ufl_shape == g.ufl_shape return f elif mapping == "contravariant Piola": assert transform_hdiv.ufl_shape == (gsize, rsize) i, j = indices(2) f = as_vector(transform_hdiv[i, j] * r[j], i) # f = as_tensor(transform_hdiv[i, j]*r[k,j], (k,i)) # FIXME: Handle Vector(Piola) here? assert f.ufl_shape == g.ufl_shape return f elif mapping == "covariant Piola": assert Jinv.ufl_shape == (rsize, gsize) i, j = indices(2) f = as_vector(Jinv[j, i] * r[j], i) # f = as_tensor(Jinv[j, i]*r[k,j], (k,i)) # FIXME: Handle Vector(Piola) here? assert f.ufl_shape == g.ufl_shape return f elif mapping == "double covariant Piola": i, j, m, n = indices(4) f = as_tensor(Jinv[m, i] * r[m, n] * Jinv[n, j], (i, j)) assert f.ufl_shape == g.ufl_shape return f elif mapping == "double contravariant Piola": i, j, m, n = indices(4) f = as_tensor( (1.0 / detJ) * (1.0 / detJ) * J[i, m] * r[m, n] * J[j, n], (i, j)) assert f.ufl_shape == g.ufl_shape return f elif mapping == "L2 Piola": assert rsh == gsh return r / detJ # By placing components in a list and using as_vector at the end, # we're assuming below that both global function g and its # reference value r have vector shape, which is the case for most # elements with the exceptions: # - TensorElements # - All cases with scalar subelements and without symmetries # are covered by the shortcut above # (ONLY IF REFERENCE VALUE SHAPE PRESERVES TENSOR RANK) # - All cases with scalar subelements and without symmetries are # covered by the shortcut above g_components = [None] * gsize gpos = 0 rpos = 0 r = as_vector([r[idx] for idx in numpy.ndindex(r.ufl_shape)]) for subelm in sub_elements_with_mappings(element): gm = product(subelm.value_shape()) rm = product(subelm.reference_value_shape()) mp = subelm.mapping() if mp == "identity": assert gm == rm for i in range(gm): g_components[gpos + i] = r[rpos + i] elif mp == "symmetries": """ tensor_element.value_shape() == (2,2) tensor_element.reference_value_shape() == (3,) tensor_element.symmetry() == { (1,0): (0,1) } tensor_element.component_mapping() == { (0,0): 0, (0,1): 1, (1,0): 1, (1,1): 2 } tensor_element.flattened_component_mapping() == { 0: 0, 1: 1, 2: 1, 3: 2 } """ fcm = subelm.flattened_sub_element_mapping() assert gm >= rm assert len(fcm) == gm assert sorted(set(fcm)) == sorted(range(rm)) for i in range(gm): g_components[gpos + i] = r[rpos + fcm[i]] elif mp == "contravariant Piola": assert transform_hdiv.ufl_shape == (gm, rm) # Get reference value vector corresponding to this subelement: rv = as_vector([r[rpos + k] for k in range(rm)]) # Apply transform with IndexSum over j for each row j = Index() for i in range(gm): g_components[gpos + i] = transform_hdiv[i, j] * rv[j] elif mp == "covariant Piola": assert Jinv.ufl_shape == (rm, gm) # Get reference value vector corresponding to this subelement: rv = as_vector([r[rpos + k] for k in range(rm)]) # Apply transform with IndexSum over j for each row j = Index() for i in range(gm): g_components[gpos + i] = Jinv[j, i] * rv[j] elif mp == "double covariant Piola": # components are flatten, map accordingly rv = as_vector([r[rpos + k] for k in range(rm)]) (gdim, _) = subelm.value_shape() (rdim, _) = subelm.reference_value_shape() for i in range(gdim): for j in range(gdim): gv = 0 # int times Index is not allowed. so sum by hand for m in range(rdim): for n in range(rdim): gv += Jinv[m, i] * rv[m * rdim + n] * Jinv[n, j] g_components[gpos + i * gdim + j] = gv elif mp == "double contravariant Piola": # components are flatten, map accordingly rv = as_vector([r[rpos + k] for k in range(rm)]) (gdim, _) = subelm.value_shape() (rdim, _) = subelm.reference_value_shape() for i in range(gdim): for j in range(gdim): gv = 0 # int times Index is not allowed. so sum by hand for m in range(rdim): for n in range(rdim): gv += ((1.0 / detJ) * (1.0 / detJ) * J[i, m] * rv[m * rdim + n] * J[j, n]) g_components[gpos + i * gdim + j] = gv elif mp == "L2 Piola": assert gm == rm for i in range(gm): g_components[gpos + i] = r[rpos + i] / detJ else: error("Unknown subelement mapping type %s for element %s." % (mp, str(subelm))) gpos += gm rpos += rm # Wrap up components in a vector, must return same shape as input # function g f = as_tensor(numpy.asarray(g_components).reshape(gsh)) assert f.ufl_shape == g.ufl_shape return f
def form_argument(self, o): # Represent 0-derivatives of form arguments on reference # element f = apply_single_function_pullbacks(ReferenceValue(o), o.ufl_element()) assert f.ufl_shape == o.ufl_shape return f
def test_change_hcurl_form_arguments_to_reference_frame(): V = FiniteElement("RT", triangle, 1) expr = Coefficient(V) assert change_to_reference_frame(expr) == ReferenceValue(expr) '''
def change_expr_to_reference_frame(expr): expr = ReferenceValue(expr) return expr
def spatial_coordinate(self, o): assert o.ufl_domain().ufl_coordinate_element().mapping() == "identity" return ReferenceValue(self.coordinates)
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 _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