def generic_pseudo_inverse_expr(A): """Compute the Penrose-Moore pseudo-inverse of A: (A.T*A)^-1 * A.T.""" i, j, k = indices(3) ATA = as_tensor(A[k, i] * A[k, j], (i, j)) ATAinv = inverse_expr(ATA) q, r, s = indices(3) return as_tensor(ATAinv[r, q] * A[s, q], (r, s))
def dot(self, o, a, b): ai = indices(len(a.ufl_shape)-1) bi = indices(len(b.ufl_shape)-1) k = (Index(),) # Creates a single IndexSum over a Product s = a[ai+k]*b[k+bi] return as_tensor(s, ai+bi)
def dot(self, o, a, b): ai = indices(len(a.ufl_shape) - 1) bi = indices(len(b.ufl_shape) - 1) k = (Index(),) # Creates a single IndexSum over a Product s = a[ai + k] * b[k + bi] return as_tensor(s, ai + bi)
def circumradius(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler warning( "Only know how to compute the circumradius of an affine cell.") return o cellname = domain.ufl_cell().cellname() cellvolume = self.cell_volume(CellVolume(domain)) if cellname == "interval": r = 0.5 * cellvolume elif cellname == "triangle": J = self.jacobian(Jacobian(domain)) trev = CellEdgeVectors(domain) num_edges = 3 i, j, k = indices(3) elen = [ sqrt((J[i, j] * trev[edge, j]) * (J[i, k] * trev[edge, k])) for edge in range(num_edges) ] r = (elen[0] * elen[1] * elen[2]) / (4.0 * cellvolume) elif cellname == "tetrahedron": J = self.jacobian(Jacobian(domain)) trev = CellEdgeVectors(domain) num_edges = 6 i, j, k = indices(3) elen = [ sqrt((J[i, j] * trev[edge, j]) * (J[i, k] * trev[edge, k])) for edge in range(num_edges) ] # elen[3] = length of edge 3 # la, lb, lc = lengths of the sides of an intermediate triangle la = elen[3] * elen[2] lb = elen[4] * elen[1] lc = elen[5] * elen[0] # p = perimeter p = (la + lb + lc) # s = semiperimeter s = p / 2 # area of intermediate triangle with Herons formula triangle_area = sqrt(s * (s - la) * (s - lb) * (s - lc)) r = triangle_area / (6.0 * cellvolume) else: error("Unhandled cell type %s." % cellname) return r
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 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 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 apply_known_single_pullback(r, element): """Apply pullback with given mapping. :arg r: Expression wrapped in ReferenceValue :arg element: The element defining the mapping """ # Need to pass in r rather than the physical space thing, because # the latter may be a ListTensor or similar, rather than a # Coefficient/Argument (in the case of mixed elements, see below # in apply_single_function_pullbacks), to which we cannot apply ReferenceValue mapping = element.mapping() domain = r.ufl_domain() if mapping == "physical": return r elif mapping == "identity": return r elif mapping == "contravariant Piola": J = Jacobian(domain) detJ = JacobianDeterminant(J) transform = (1.0 / detJ) * J # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) *k, i, j = indices(len(r.ufl_shape) + 1) kj = (*k, j) f = as_tensor(transform[i, j] * r[kj], (*k, i)) return f elif mapping == "covariant Piola": K = JacobianInverse(domain) # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) *k, i, j = indices(len(r.ufl_shape) + 1) kj = (*k, j) f = as_tensor(K[j, i] * r[kj], (*k, i)) return f elif mapping == "L2 Piola": detJ = JacobianDeterminant(domain) return r / detJ elif mapping == "double contravariant Piola": J = Jacobian(domain) detJ = JacobianDeterminant(J) transform = (1.0 / detJ) * J # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) *k, i, j, m, n = indices(len(r.ufl_shape) + 2) kmn = (*k, m, n) f = as_tensor((1.0 / detJ)**2 * J[i, m] * r[kmn] * J[j, n], (*k, i, j)) return f elif mapping == "double covariant Piola": K = JacobianInverse(domain) # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) *k, i, j, m, n = indices(len(r.ufl_shape) + 2) kmn = (*k, m, n) f = as_tensor(K[m, i] * r[kmn] * K[n, j], (*k, i, j)) return f else: error("Should never be reached!")
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 altenative_dot(self, o, a, b): # TODO: Test this ash = a.ufl_shape bsh = b.ufl_shape ai = indices(len(ash) - 1) bi = indices(len(bsh) - 1) # Simplification for tensors where the dot-sum dimension has # length 1 if ash[-1] == 1: k = (FixedIndex(0),) else: k = (Index(),) # Potentially creates a single IndexSum over a Product s = a[ai + k] * b[k + bi] return as_tensor(s, ai + bi)
def altenative_dot(self, o, a, b): # TODO: Test this ash = a.ufl_shape bsh = b.ufl_shape ai = indices(len(ash) - 1) bi = indices(len(bsh) - 1) # Simplification for tensors where the dot-sum dimension has # length 1 if ash[-1] == 1: k = (FixedIndex(0),) else: k = (Index(),) # Potentially creates a single IndexSum over a Product s = a[ai+k]*b[k+bi] return as_tensor(s, ai+bi)
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 indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in nesting rules # Propagate zeros if isinstance(Ap, Zero): return self.independent_operator(o) # Untangle as_tensor(C[kk], jj)[ii] -> C[ll] to simplify # resulting expression if isinstance(Ap, ComponentTensor): B, jj = Ap.ufl_operands if isinstance(B, Indexed): C, kk = B.ufl_operands kk = list(kk) if all(j in kk for j in jj): rep = dict(zip(jj, ii)) Cind = [rep.get(k, k) for k in kk] expr = Indexed(C, MultiIndex(tuple(Cind))) assert expr.ufl_free_indices == o.ufl_free_indices assert expr.ufl_shape == o.ufl_shape return expr # Otherwise a more generic approach r = len(Ap.ufl_shape) - len(ii) if r: kk = indices(r) op = Indexed(Ap, MultiIndex(ii.indices() + kk)) op = as_tensor(op, kk) else: op = Indexed(Ap, ii) return op
def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in generic rules # Reuse if untouched if Ap is o.ufl_operands[0]: return o # Untangle as_tensor(C[kk], jj)[ii] -> C[ll] to simplify # resulting expression if isinstance(Ap, ComponentTensor): B, jj = Ap.ufl_operands if isinstance(B, Indexed): C, kk = B.ufl_operands kk = list(kk) if all(j in kk for j in jj): Cind = list(kk) for i, j in zip(ii, jj): Cind[kk.index(j)] = i return Indexed(C, MultiIndex(tuple(Cind))) # Otherwise a more generic approach r = len(Ap.ufl_shape) - len(ii) if r: kk = indices(r) op = Indexed(Ap, MultiIndex(ii.indices() + kk)) op = as_tensor(op, kk) else: op = Indexed(Ap, ii) return op
def _make_identity(self, sh): "Create a higher order identity tensor to represent dv/dv." res = None if sh == (): # Scalar dv/dv is scalar return FloatValue(1.0) elif len(sh) == 1: # Vector v makes dv/dv the identity matrix return Identity(sh[0]) else: # TODO: Add a type for this higher order identity? # II[i0,i1,i2,j0,j1,j2] = 1 if all((i0==j0, i1==j1, i2==j2)) else 0 # Tensor v makes dv/dv some kind of higher rank identity tensor ind1 = () ind2 = () for d in sh: i, j = indices(2) dij = Identity(d)[i, j] if res is None: res = dij else: res *= dij ind1 += (i, ) ind2 += (j, ) fp = as_tensor(res, ind1 + ind2) return fp
def max_facet_edge_length(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler warning( "Only know how to compute the max_facet_edge_length of an affine cell." ) return o cellname = domain.ufl_cell().cellname() if cellname == "triangle": return self.facet_area(FacetArea(domain)) elif cellname == "tetrahedron": J = self.jacobian(Jacobian(domain)) trev = FacetEdgeVectors(domain) num_edges = 3 i, j, k = indices(3) elen = [ sqrt((J[i, j] * trev[edge, j]) * (J[i, k] * trev[edge, k])) for edge in range(num_edges) ] return max_value(elen[0], max_value(elen[1], elen[2])) else: error("Unhandled cell type %s." % cellname)
def _make_identity(self, sh): "Create a higher order identity tensor to represent dv/dv." res = None if sh == (): # Scalar dv/dv is scalar return FloatValue(1.0) elif len(sh) == 1: # Vector v makes dv/dv the identity matrix return Identity(sh[0]) else: # TODO: Add a type for this higher order identity? # II[i0,i1,i2,j0,j1,j2] = 1 if all((i0==j0, i1==j1, i2==j2)) else 0 # Tensor v makes dv/dv some kind of higher rank identity tensor ind1 = () ind2 = () for d in sh: i, j = indices(2) dij = Identity(d)[i, j] if res is None: res = dij else: res *= dij ind1 += (i,) ind2 += (j,) fp = as_tensor(res, ind1 + ind2) return fp
def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in nesting rules # Propagate zeros if isinstance(Ap, Zero): return self.independent_operator(o) # Untangle as_tensor(C[kk], jj)[ii] -> C[ll] to simplify # resulting expression if isinstance(Ap, ComponentTensor): B, jj = Ap.ufl_operands if isinstance(B, Indexed): C, kk = B.ufl_operands kk = list(kk) if all(j in kk for j in jj): Cind = list(kk) for i, j in zip(ii, jj): Cind[kk.index(j)] = i return Indexed(C, MultiIndex(tuple(Cind))) # Otherwise a more generic approach r = len(Ap.ufl_shape) - len(ii) if r: kk = indices(r) op = Indexed(Ap, MultiIndex(ii.indices() + kk)) op = as_tensor(op, kk) else: op = Indexed(Ap, ii) return op
def nabla_grad(self, o, a): sh = a.ufl_shape if sh == (): return Grad(a) else: j = Index() ii = tuple(indices(len(sh))) return as_tensor(a[ii].dx(j), (j,) + ii)
def inner(self, o, a, b): ash = a.ufl_shape bsh = b.ufl_shape if ash != bsh: error("Nonmatching shapes.") ii = indices(len(ash)) # Creates multiple IndexSums over a Product s = a[ii] * Conj(b[ii]) return s
def inner(self, o, a, b): ash = a.ufl_shape bsh = b.ufl_shape if ash != bsh: error("Nonmatching shapes.") ii = indices(len(ash)) # Creates multiple IndexSums over a Product s = a[ii]*Conj(b[ii]) return s
def facet_jacobian(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() J = self.jacobian(Jacobian(domain)) RFJ = CellFacetJacobian(domain) i, j, k = indices(3) return as_tensor(J[i, k] * RFJ[k, j], (i, j))
def _div(self, o): if not isinstance(o, _valid_types): return NotImplemented sh = self.ufl_shape if sh: ii = indices(len(sh)) d = Division(self[ii], o) return as_tensor(d, ii) return Division(self, o)
def facet_jacobian(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() J = self.jacobian(Jacobian(domain)) RFJ = CellFacetJacobian(domain) i, j, k = indices(3) return as_tensor(J[i, k]*RFJ[k, j], (i, j))
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 pseudo_inverse_expr(A): """Compute the Penrose-Moore pseudo-inverse of A: (A.T*A)^-1 * A.T.""" m, n = A.ufl_shape if n == 1: # Simpler special case for 1d i, j, k = indices(3) return as_tensor(A[i, j], (j, i)) / (A[k, 0] * A[k, 0]) else: # Generic formulation return generic_pseudo_inverse_expr(A)
def as_scalars(*expressions): """Given multiple scalar or tensor valued expressions A, returns either of the tuples:: (a,b) = (A, ()) (a,b) = ([A[0][indices], ..., A[-1][indices]], indices) such that a is always a list of scalar valued expressions.""" ii = indices(len(expressions[0].ufl_shape)) if ii: expressions = [expression[ii] for expression in expressions] return expressions, ii
def as_scalar(expression): """Given a scalar or tensor valued expression A, returns either of the tuples:: (a,b) = (A, ()) (a,b) = (A[indices], indices) such that a is always a scalar valued expression.""" ii = indices(len(expression.ufl_shape)) if ii: expression = expression[ii] return expression, ii
def cell_coordinate(self, o): "Compute from physical coordinates if they are known, using the appropriate mappings." if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() K = self.jacobian_inverse(JacobianInverse(domain)) x = self.spatial_coordinate(SpatialCoordinate(domain)) x0 = CellOrigin(domain) i, j = indices(2) X = as_tensor(K[i, j] * (x[j] - x0[j]), (i,)) return X
def cell_coordinate(self, o): "Compute from physical coordinates if they are known, using the appropriate mappings." if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() K = self.jacobian_inverse(JacobianInverse(domain)) x = self.spatial_coordinate(SpatialCoordinate(domain)) x0 = CellOrigin(domain) i, j = indices(2) X = as_tensor(K[i, j] * (x[j] - x0[j]), (i, )) return X
def contraction(a, a_axes, b, b_axes): "UFL operator: Take the contraction of a and b over given axes." ai, bi = a_axes, b_axes if len(ai) != len(bi): error("Contraction must be over the same number of axes.") ash = a.ufl_shape bsh = b.ufl_shape aii = indices(len(a.ufl_shape)) bii = indices(len(b.ufl_shape)) cii = indices(len(ai)) shape = [None] * len(ai) for i, j in enumerate(ai): aii[j] = cii[i] shape[i] = ash[j] for i, j in enumerate(bi): bii[j] = cii[i] if shape[i] != bsh[j]: error("Shape mismatch in contraction.") s = a[aii] * b[bii] cii = set(cii) ii = tuple(i for i in (aii + bii) if i not in cii) return as_tensor(s, ii)
def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # Apply gradients directly to argument vval, and get the # right indexed scalar component(s) kk = indices(ngrads) Dvkk = apply_grads(vval)[vcomp+kk] # Place scalar component(s) Dvkk into the right tensor # positions if wshape: Ejj, jj = unit_indexed_tensor(wshape, wcomp) else: Ejj, jj = 1, () gprimeterm = as_tensor(Ejj*Dvkk, jj+kk) return gprimeterm
def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # Apply gradients directly to argument vval, and get the # right indexed scalar component(s) kk = indices(ngrads) Dvkk = apply_grads(vval)[vcomp + kk] # Place scalar component(s) Dvkk into the right tensor # positions if wshape: Ejj, jj = unit_indexed_tensor(wshape, wcomp) else: Ejj, jj = 1, () gprimeterm = as_tensor(Ejj * Dvkk, jj + kk) return gprimeterm
def unit_indexed_tensor(shape, component): from ufl.constantvalue import Identity from ufl.operators import outer # a bit of circular dependency issue here r = len(shape) if r == 0: return 0, () jj = indices(r) es = [] for i in range(r): s = shape[i] c = component[i] j = jj[i] e = Identity(s)[c, j] es.append(e) E = es[0] for e in es[1:]: E = outer(E, e) return E, jj
def facet_normal(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() tdim = domain.topological_dimension() if tdim == 1: # Special-case 1D (possibly immersed), for which we say # that n is just in the direction of J. J = self.jacobian(Jacobian(domain)) # dx/dX ndir = J[:, 0] gdim = domain.geometric_dimension() if gdim == 1: nlen = abs(ndir[0]) else: i = Index() nlen = sqrt(ndir[i] * ndir[i]) rn = ReferenceNormal(domain) # +/- 1.0 here n = rn[0] * ndir / nlen r = n else: # Recall that the covariant Piola transform u -> J^(-T)*u # preserves tangential components. The normal vector is # characterised by having zero tangential component in # reference and physical space. Jinv = self.jacobian_inverse(JacobianInverse(domain)) i, j = indices(2) rn = ReferenceNormal(domain) # compute signed, unnormalised normal; note transpose ndir = as_vector(Jinv[j, i] * rn[j], i) # normalise i = Index() n = ndir / sqrt(ndir[i] * ndir[i]) r = n if r.ufl_shape != o.ufl_shape: error("Inconsistent dimensions (in=%d, out=%d)." % (o.ufl_shape[0], r.ufl_shape[0])) return r
def facet_normal(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() tdim = domain.topological_dimension() if tdim == 1: # Special-case 1D (possibly immersed), for which we say # that n is just in the direction of J. J = self.jacobian(Jacobian(domain)) # dx/dX ndir = J[:, 0] gdim = domain.geometric_dimension() if gdim == 1: nlen = abs(ndir[0]) else: i = Index() nlen = sqrt(ndir[i]*ndir[i]) rn = ReferenceNormal(domain) # +/- 1.0 here n = rn[0] * ndir / nlen r = n else: # Recall that the covariant Piola transform u -> J^(-T)*u # preserves tangential components. The normal vector is # characterised by having zero tangential component in # reference and physical space. Jinv = self.jacobian_inverse(JacobianInverse(domain)) i, j = indices(2) rn = ReferenceNormal(domain) # compute signed, unnormalised normal; note transpose ndir = as_vector(Jinv[j, i] * rn[j], i) # normalise i = Index() n = ndir / sqrt(ndir[i]*ndir[i]) r = n if r.ufl_shape != o.ufl_shape: error("Inconsistent dimensions (in=%d, out=%d)." % (o.ufl_shape[0], r.ufl_shape[0])) return r
def create_slice_indices(component, shape, fi): all_indices = [] slice_indices = [] repeated_indices = [] free_indices = [] for ind in component: if isinstance(ind, Index): all_indices.append(ind) if ind.count() in fi or ind in free_indices: repeated_indices.append(ind) free_indices.append(ind) elif isinstance(ind, FixedIndex): if int(ind) >= shape[len(all_indices)]: error("Index out of bounds.") all_indices.append(ind) elif isinstance(ind, int): if int(ind) >= shape[len(all_indices)]: error("Index out of bounds.") all_indices.append(FixedIndex(ind)) elif isinstance(ind, slice): if ind != slice(None): error("Only full slices (:) allowed.") i = Index() slice_indices.append(i) all_indices.append(i) elif ind == Ellipsis: er = len(shape) - len(component) + 1 ii = indices(er) slice_indices.extend(ii) all_indices.extend(ii) else: error("Not expecting {0}.".format(ind)) if len(all_indices) != len(shape): error("Component and shape length don't match.") return tuple(all_indices), tuple(slice_indices), tuple(repeated_indices)
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 sym(self, o, A): i, j = indices(2) return as_matrix((A[i, j] + A[j, i]) / 2, (i, j))
def transposed(self, o, A): i, j = indices(2) return as_tensor(A[i, j], (j, i))
def generic_pseudo_determinant_expr(A): """Compute the pseudo-determinant of A: sqrt(det(A.T*A)).""" i, j, k = indices(3) ATA = as_tensor(A[k, i] * A[k, j], (i, j)) return sqrt(determinant_expr(ATA))
def outer(self, o, a, b): ii = indices(len(a.ufl_shape)) jj = indices(len(b.ufl_shape)) # Create a Product with no shared indices s = Conj(a[ii]) * b[jj] return as_tensor(s, ii + jj)
def outer(self, o, a, b): ii = indices(len(a.ufl_shape)) jj = indices(len(b.ufl_shape)) # Create a Product with no shared indices s = Conj(a[ii])*b[jj] return as_tensor(s, ii+jj)
def analyse_key(ii, rank): """Takes something the user might input as an index tuple inside [], which could include complete slices (:) and ellipsis (...), and returns tuples of actual UFL index objects. The return value is a tuple (indices, axis_indices), each being a tuple of IndexBase instances. The return value 'indices' corresponds to all input objects of these types: - Index - FixedIndex - int => Wrapped in FixedIndex The return value 'axis_indices' corresponds to all input objects of these types: - Complete slice (:) => Replaced by a single new index - Ellipsis (...) => Replaced by multiple new indices """ # Wrap in tuple if not isinstance(ii, (tuple, MultiIndex)): ii = (ii,) else: # Flatten nested tuples, happens with f[...,ii] where ii is a # tuple of indices jj = [] for j in ii: if isinstance(j, (tuple, MultiIndex)): jj.extend(j) else: jj.append(j) ii = tuple(jj) # Convert all indices to Index or FixedIndex objects. If there is # an ellipsis, split the indices into before and after. axis_indices = set() pre = [] post = [] indexlist = pre for i in ii: if i == Ellipsis: # Switch from pre to post list when an ellipsis is # encountered if indexlist is not pre: error("Found duplicate ellipsis.") indexlist = post else: # Convert index to a proper type if isinstance(i, numbers.Integral): idx = FixedIndex(i) elif isinstance(i, IndexBase): idx = i elif isinstance(i, slice): if i == slice(None): idx = Index() axis_indices.add(idx) else: # TODO: Use ListTensor to support partial slices? error("Partial slices not implemented, only complete slices like [:]") else: error("Can't convert this object to index: %s" % (i,)) # Store index in pre or post list indexlist.append(idx) # Handle ellipsis as a number of complete slices, that is create a # number of new axis indices num_axis = rank - len(pre) - len(post) if indexlist is post: ellipsis_indices = indices(num_axis) axis_indices.update(ellipsis_indices) else: ellipsis_indices = () # Construct final tuples to return all_indices = tuple(chain(pre, ellipsis_indices, post)) axis_indices = tuple(i for i in all_indices if i in axis_indices) return all_indices, axis_indices
def _mult(a, b): # Discover repeated indices, which results in index sums afi = a.ufl_free_indices bfi = b.ufl_free_indices afid = a.ufl_index_dimensions bfid = b.ufl_index_dimensions fi, fid, ri, rid = merge_overlapping_indices(afi, afid, bfi, bfid) # Pick out valid non-scalar products here (dot products): # - matrix-matrix (A*B, M*grad(u)) => A . B # - matrix-vector (A*v) => A . v s1, s2 = a.ufl_shape, b.ufl_shape r1, r2 = len(s1), len(s2) if r1 == 0 and r2 == 0: # Create scalar product p = Product(a, b) ti = () elif r1 == 0 or r2 == 0: # Scalar - tensor product if r2 == 0: a, b = b, a # Check for zero, simplifying early if possible if isinstance(a, Zero) or isinstance(b, Zero): shape = s1 or s2 return Zero(shape, fi, fid) # Repeated indices are allowed, like in: # v[i]*M[i,:] # Apply product to scalar components ti = indices(len(b.ufl_shape)) p = Product(a, b[ti]) elif r1 == 2 and r2 in (1, 2): # Matrix-matrix or matrix-vector if ri: error("Not expecting repeated indices in non-scalar product.") # Check for zero, simplifying early if possible if isinstance(a, Zero) or isinstance(b, Zero): shape = s1[:-1] + s2[1:] return Zero(shape, fi, fid) # Return dot product in index notation ai = indices(len(a.ufl_shape) - 1) bi = indices(len(b.ufl_shape) - 1) k = indices(1) p = a[ai + k] * b[k + bi] ti = ai + bi else: error("Invalid ranks {0} and {1} in product.".format(r1, r2)) # TODO: I think applying as_tensor after index sums results in # cleaner expression graphs. # Wrap as tensor again if ti: p = as_tensor(p, ti) # If any repeated indices were found, apply implicit summation # over those for i in ri: mi = MultiIndex((Index(count=i),)) p = IndexSum(p, mi) return p