예제 #1
0
def apply_single_function_pullbacks(r, element):
    """Apply an appropriate pullback to something in physical space

    :arg r: An expression wrapped in ReferenceValue.
    :arg element: The element this expression lives in.
    :returns: a pulled back expression."""
    mapping = element.mapping()
    if r.ufl_shape != element.reference_value_shape():
        error("Expecting reference space expression with shape '%s', got '%s'" % (element.reference_value_shape(), r.ufl_shape))
    if mapping in {"physical", "identity",
                   "contravariant Piola", "covariant Piola",
                   "double contravariant Piola", "double covariant Piola",
                   "L2 Piola"}:
        # Base case in recursion through elements. If the element
        # advertises a mapping we know how to handle, do that
        # directly.
        f = apply_known_single_pullback(r, element)
        if f.ufl_shape != element.value_shape():
            error("Expecting pulled back expression with shape '%s', got '%s'" % (element.value_shape(), f.ufl_shape))
        return f
    elif mapping in {"symmetries", "undefined"}:
        # Need to pull back each unique piece of the reference space thing
        gsh = element.value_shape()
        rsh = r.ufl_shape
        if mapping == "symmetries":
            subelem = element.sub_elements()[0]
            fcm = element.flattened_sub_element_mapping()
            offsets = (product(subelem.reference_value_shape()) * i for i in fcm)
            elements = repeat(subelem)
        else:
            elements = sub_elements_with_mappings(element)
            # Python >= 3.8 has an initial keyword argument to
            # accumulate, but 3.7 does not.
            offsets = chain([0],
                            accumulate(product(e.reference_value_shape())
                                       for e in elements))
        rflat = as_vector([r[idx] for idx in numpy.ndindex(rsh)])
        g_components = []
        # For each unique piece in reference space, apply the appropriate pullback
        for offset, subelem in zip(offsets, elements):
            sub_rsh = subelem.reference_value_shape()
            rm = product(sub_rsh)
            rsub = [rflat[offset + i] for i in range(rm)]
            rsub = as_tensor(numpy.asarray(rsub).reshape(sub_rsh))
            rmapped = apply_single_function_pullbacks(rsub, subelem)
            # Flatten into the pulled back expression for the whole thing
            g_components.extend([rmapped[idx]
                                 for idx in numpy.ndindex(rmapped.ufl_shape)])
        # And reshape appropriately
        f = as_tensor(numpy.asarray(g_components).reshape(gsh))
        if f.ufl_shape != element.value_shape():
            error("Expecting pulled back expression with shape '%s', got '%s'" % (element.value_shape(), f.ufl_shape))
        return f
    else:
        error("Unhandled mapping type '%s'" % mapping)
예제 #2
0
    def extract_subelement_component(self, i):
        """Extract direct subelement index and subelement relative
        component index for a given component index."""
        if isinstance(i, int):
            i = (i, )
        self._check_component(i)

        # Select between indexing modes
        if len(self.value_shape()) == 1:
            # Indexing into a long vector of flattened subelement
            # shapes
            j, = i

            # Find subelement for this index
            for sub_element_index, e in enumerate(self._sub_elements):
                sh = e.value_shape()
                si = product(sh)
                if j < si:
                    break
                j -= si
            if j < 0:
                error("Moved past last value component!")

            # Convert index into a shape tuple
            st = shape_to_strides(sh)
            component = unflatten_index(j, st)
        else:
            # Indexing into a multidimensional tensor where subelement
            # index is first axis
            sub_element_index = i[0]
            if sub_element_index >= len(self._sub_elements):
                error("Illegal component index (dimension %d)." %
                      sub_element_index)
            component = i[1:]
        return (sub_element_index, component)
예제 #3
0
    def extract_subelement_reference_component(self, i):
        """Extract direct subelement index and subelement relative
        reference_component index for a given reference_component index."""
        if isinstance(i, int):
            i = (i, )
        self._check_reference_component(i)

        # Select between indexing modes
        assert len(self.reference_value_shape()) == 1
        # Indexing into a long vector of flattened subelement shapes
        j, = i

        # Find subelement for this index
        for sub_element_index, e in enumerate(self._sub_elements):
            sh = e.reference_value_shape()
            si = product(sh)
            if j < si:
                break
            j -= si
        if j < 0:
            error("Moved past last value reference_component!")

        # Convert index into a shape tuple
        st = shape_to_strides(sh)
        reference_component = unflatten_index(j, st)
        return (sub_element_index, reference_component)
예제 #4
0
파일: mixedelement.py 프로젝트: FEniCS/ufl
    def extract_subelement_component(self, i):
        """Extract direct subelement index and subelement relative
        component index for a given component index."""
        if isinstance(i, int):
            i = (i,)
        self._check_component(i)

        # Select between indexing modes
        if len(self.value_shape()) == 1:
            # Indexing into a long vector of flattened subelement
            # shapes
            j, = i

            # Find subelement for this index
            for sub_element_index, e in enumerate(self._sub_elements):
                sh = e.value_shape()
                si = product(sh)
                if j < si:
                    break
                j -= si
            if j < 0:
                error("Moved past last value component!")

            # Convert index into a shape tuple
            st = shape_to_strides(sh)
            component = unflatten_index(j, st)
        else:
            # Indexing into a multidimensional tensor where subelement
            # index is first axis
            sub_element_index = i[0]
            if sub_element_index >= len(self._sub_elements):
                error("Illegal component index (dimension %d)." % sub_element_index)
            component = i[1:]
        return (sub_element_index, component)
예제 #5
0
파일: mixedelement.py 프로젝트: FEniCS/ufl
    def extract_subelement_reference_component(self, i):
        """Extract direct subelement index and subelement relative
        reference_component index for a given reference_component index."""
        if isinstance(i, int):
            i = (i,)
        self._check_reference_component(i)

        # Select between indexing modes
        assert len(self.reference_value_shape()) == 1
        # Indexing into a long vector of flattened subelement shapes
        j, = i

        # Find subelement for this index
        for sub_element_index, e in enumerate(self._sub_elements):
            sh = e.reference_value_shape()
            si = product(sh)
            if j < si:
                break
            j -= si
        if j < 0:
            error("Moved past last value reference_component!")

        # Convert index into a shape tuple
        st = shape_to_strides(sh)
        reference_component = unflatten_index(j, st)
        return (sub_element_index, reference_component)
예제 #6
0
def reshape_to_nested_list(components, shape):
    if len(shape) == 0:
        assert len(components) == 1
        return [components[0]]
    elif len(shape) == 1:
        assert len(components) == shape[0]
        return components
    else:
        n = product(shape[1:])
        return [reshape_to_nested_list(components[n * i:n * (i + 1)], shape[1:]) for i in range(shape[0])]
예제 #7
0
 def symmetry(self):
     """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1`
     meaning that component :math:`c_0` is represented by component
     :math:`c_1`.
     A component is a tuple of one or more ints."""
     # Build symmetry map from symmetries of subelements
     sm = {}
     # Base index of the current subelement into mixed value
     j = 0
     for e in self._sub_elements:
         sh = e.value_shape()
         st = shape_to_strides(sh)
         # Map symmetries of subelement into index space of this
         # element
         for c0, c1 in e.symmetry().items():
             j0 = flatten_multiindex(c0, st) + j
             j1 = flatten_multiindex(c1, st) + j
             sm[(j0, )] = (j1, )
         # Update base index for next element
         j += product(sh)
     if j != product(self.value_shape()):
         error("Size mismatch in symmetry algorithm.")
     return sm or EmptyDict
예제 #8
0
파일: mixedelement.py 프로젝트: FEniCS/ufl
 def symmetry(self):
     """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1`
     meaning that component :math:`c_0` is represented by component
     :math:`c_1`.
     A component is a tuple of one or more ints."""
     # Build symmetry map from symmetries of subelements
     sm = {}
     # Base index of the current subelement into mixed value
     j = 0
     for e in self._sub_elements:
         sh = e.value_shape()
         st = shape_to_strides(sh)
         # Map symmetries of subelement into index space of this
         # element
         for c0, c1 in e.symmetry().items():
             j0 = flatten_multiindex(c0, st) + j
             j1 = flatten_multiindex(c1, st) + j
             sm[(j0,)] = (j1,)
         # Update base index for next element
         j += product(sh)
     if j != product(self.value_shape()):
         error("Size mismatch in symmetry algorithm.")
     return sm or EmptyDict
예제 #9
0
def split(v):
    """UFL operator: If v is a Coefficient or Argument in a mixed space, returns
    a tuple with the function components corresponding to the subelements."""

    # Default range is all of v
    begin = 0
    end = None

    if isinstance(v, Indexed):
        # Special case: split previous output of split again
        # Consistent with simple element, just return function in a tuple
        return (v, )

    elif isinstance(v, ListTensor):
        # Special case: split previous output of split again
        ops = v.ufl_operands
        if all(isinstance(comp, Indexed) for comp in ops):
            args = [comp.ufl_operands[0] for comp in ops]
            if all(args[0] == args[i] for i in range(1, len(args))):
                # Get innermost terminal here and its element
                v = args[0]
                # Get relevant range of v components
                begin, = ops[0].ufl_operands[1]
                end, = ops[-1].ufl_operands[1]
                begin = int(begin)
                end = int(end) + 1
            else:
                error("Don't know how to split %s." % (v, ))
        else:
            error("Don't know how to split %s." % (v, ))

    # Special case: simple element, just return function in a tuple
    element = v.ufl_element()
    if not isinstance(element, MixedElement):
        assert end is None
        return (v, )

    if isinstance(element, TensorElement):
        if element.symmetry():
            error("Split not implemented for symmetric tensor elements.")

    if len(v.ufl_shape) != 1:
        error(
            "Don't know how to split tensor valued mixed functions without flattened index space."
        )

    # Compute value size and set default range end
    value_size = product(element.value_shape())
    if end is None:
        end = value_size
    else:
        # Recursively dive into mixedelement in to subelement
        # corresponding to beginning of range
        j = begin
        while True:
            sub_i, j = element.extract_subelement_component(j)
            element = element.sub_elements()[sub_i]
            # Then break when we find the subelement that covers the whole range
            if product(element.value_shape()) == (end - begin):
                break

    # Build expressions representing the subfunction of v for each subelement
    offset = begin
    sub_functions = []
    for i, e in enumerate(element.sub_elements()):
        # Get shape, size, indices, and v components
        # corresponding to subelement value
        shape = e.value_shape()
        strides = shape_to_strides(shape)
        rank = len(shape)
        sub_size = product(shape)
        subindices = [
            flatten_multiindex(c, strides) for c in compute_indices(shape)
        ]
        components = [v[k + offset] for k in subindices]

        # Shape components into same shape as subelement
        if rank == 0:
            subv, = components
        elif rank <= 1:
            subv = as_vector(components)
        elif rank == 2:
            subv = as_matrix([
                components[i * shape[1]:(i + 1) * shape[1]]
                for i in range(shape[0])
            ])
        else:
            error(
                "Don't know how to split functions with sub functions of rank %d."
                % rank)

        offset += sub_size
        sub_functions.append(subv)

    if end != offset:
        error(
            "Function splitting failed to extract components for whole intended range. Something is wrong."
        )

    return tuple(sub_functions)
예제 #10
0
def is_zeros_table(table, rtol=default_rtol, atol=default_atol):
    return (product(table.shape) == 0
            or numpy.allclose(table, numpy.zeros(table.shape), rtol=rtol, atol=atol))
예제 #11
0
 def reference_value_size(self):
     "Return the integer product of the reference value shape."
     return product(self.reference_value_shape())
예제 #12
0
    def __init__(self,
                 family,
                 cell=None,
                 degree=None,
                 shape=None,
                 symmetry=None,
                 quad_scheme=None):
        """Create tensor element (repeated mixed element with optional symmetries).

        :arg family: The family string, or an existing FiniteElement.
        :arg cell: The geometric cell (ignored if family is a FiniteElement).
        :arg degree: The polynomial degree (ignored if family is a FiniteElement).
        :arg shape: The shape of the element (defaults to a square
             tensor given by the geometric dimension of the cell).
        :arg symmetry: Optional symmetries.
        :arg quad_scheme: Optional quadrature scheme (ignored if
             family is a FiniteElement)."""

        if isinstance(family, FiniteElementBase):
            sub_element = family
            cell = sub_element.cell()
        else:
            if cell is not None:
                cell = as_cell(cell)
            # Create scalar sub element
            sub_element = FiniteElement(family,
                                        cell,
                                        degree,
                                        quad_scheme=quad_scheme)

        # Set default shape if not specified
        if shape is None:
            if cell is None:
                error("Cannot infer tensor shape without a cell.")
            dim = cell.geometric_dimension()
            shape = (dim, dim)

        if symmetry is None:
            symmetry = EmptyDict
        elif symmetry is True:
            # Construct default symmetry dict for matrix elements
            if not (len(shape) == 2 and shape[0] == shape[1]):
                error("Cannot set automatic symmetry for non-square tensor.")
            symmetry = dict(((i, j), (j, i)) for i in range(shape[0])
                            for j in range(shape[1]) if i > j)
        else:
            if not isinstance(symmetry, dict):
                error("Expecting symmetry to be None (unset), True, or dict.")

        # Validate indices in symmetry dict
        for i, j in symmetry.items():
            if len(i) != len(j):
                error("Non-matching length of symmetry index tuples.")
            for k in range(len(i)):
                if not (i[k] >= 0 and j[k] >= 0 and i[k] < shape[k]
                        and j[k] < shape[k]):
                    error("Symmetry dimensions out of bounds.")

        # Compute all index combinations for given shape
        indices = compute_indices(shape)

        # Compute mapping from indices to sub element number,
        # accounting for symmetry
        sub_elements = []
        sub_element_mapping = {}
        for index in indices:
            if index in symmetry:
                continue
            sub_element_mapping[index] = len(sub_elements)
            sub_elements += [sub_element]

        # Update mapping for symmetry
        for index in indices:
            if index in symmetry:
                sub_element_mapping[index] = sub_element_mapping[
                    symmetry[index]]
        flattened_sub_element_mapping = [
            sub_element_mapping[index] for i, index in enumerate(indices)
        ]

        # Compute value shape
        value_shape = shape

        # Compute reference value shape based on symmetries
        if symmetry:
            # Flatten and subtract symmetries
            reference_value_shape = (product(shape) - len(symmetry), )
            self._mapping = "symmetries"
        else:
            # Do not flatten if there are no symmetries
            reference_value_shape = shape
            self._mapping = "identity"

        value_shape = value_shape + sub_element.value_shape()
        reference_value_shape = reference_value_shape + sub_element.reference_value_shape(
        )
        # Initialize element data
        MixedElement.__init__(self,
                              sub_elements,
                              value_shape=value_shape,
                              reference_value_shape=reference_value_shape)
        self._family = sub_element.family()
        self._degree = sub_element.degree()
        self._sub_element = sub_element
        self._shape = shape
        self._symmetry = symmetry
        self._sub_element_mapping = sub_element_mapping
        self._flattened_sub_element_mapping = flattened_sub_element_mapping

        # Cache repr string
        self._repr = "TensorElement(%s, shape=%s, symmetry=%s)" % (
            repr(sub_element), repr(self._shape), repr(self._symmetry))
예제 #13
0
 def value_size(self):
     "Return the integer product of the value shape."
     return product(self.value_shape())
예제 #14
0
 def value_size(self):
     "Return the integer product of the value shape."
     return product(self.value_shape())
예제 #15
0
    def __init__(self, *elements, **kwargs):
        "Create mixed finite element from given list of elements"

        if type(self) is MixedElement:
            if kwargs:
                error(
                    "Not expecting keyword arguments to MixedElement constructor."
                )

        # Un-nest arguments if we get a single argument with a list of elements
        if len(elements) == 1 and isinstance(elements[0], (tuple, list)):
            elements = elements[0]
        # Interpret nested tuples as sub-mixedelements recursively
        elements = [
            MixedElement(e) if isinstance(e, (tuple, list)) else e
            for e in elements
        ]
        self._sub_elements = elements

        # Pick the first cell, for now all should be equal
        cells = tuple(
            sorted(set(element.cell() for element in elements) - set([None])))
        self._cells = cells
        if cells:
            cell = cells[0]
            # Require that all elements are defined on the same cell
            if not all(c == cell for c in cells[1:]):
                error("Sub elements must live on the same cell.")
        else:
            cell = None

        # Check that all elements use the same quadrature scheme TODO:
        # We can allow the scheme not to be defined.
        if len(elements) == 0:
            quad_scheme = None
        else:
            quad_scheme = elements[0].quadrature_scheme()
            if not all(e.quadrature_scheme() == quad_scheme for e in elements):
                error(
                    "Quadrature scheme mismatch for sub elements of mixed element."
                )

        # Compute value sizes in global and reference configurations
        value_size_sum = sum(
            product(s.value_shape()) for s in self._sub_elements)
        reference_value_size_sum = sum(
            product(s.reference_value_shape()) for s in self._sub_elements)

        # Default value shape: Treated simply as all subelement values
        # unpacked in a vector.
        value_shape = kwargs.get('value_shape', (value_size_sum, ))

        # Default reference value shape: Treated simply as all
        # subelement reference values unpacked in a vector.
        reference_value_shape = kwargs.get('reference_value_shape',
                                           (reference_value_size_sum, ))

        # Validate value_shape (deliberately not for subclasses
        # VectorElement and TensorElement)
        if type(self) is MixedElement:
            # This is not valid for tensor elements with symmetries,
            # assume subclasses deal with their own validation
            if product(value_shape) != value_size_sum:
                error("Provided value_shape doesn't match the "
                      "total value size of all subelements.")

        # Initialize element data
        degrees = {e.degree() for e in self._sub_elements} - {None}
        degree = max_degree(degrees) if degrees else None
        FiniteElementBase.__init__(self, "Mixed", cell, degree, quad_scheme,
                                   value_shape, reference_value_shape)

        # Cache repr string
        if type(self) is MixedElement:
            self._repr = "MixedElement(%s)" % (", ".join(
                repr(e) for e in self._sub_elements), )
예제 #16
0
def build_element_tables(num_points, quadrature_rules,
                         cell, integral_type, entitytype,
                         modified_terminals, rtol=default_rtol, atol=default_atol):
    """Build the element tables needed for a list of modified terminals.

    Input:
      entitytype - str
      modified_terminals - ordered sequence of unique modified terminals
      FIXME: Document

    Output:
      tables - dict(name: table)
      mt_table_names - dict(ModifiedTerminal: name)

    """
    mt_table_names = {}
    tables = {}
    table_origins = {}

    # Add to element tables
    analysis = {}
    for mt in modified_terminals:
        # FIXME: Use a namedtuple for res
        res = get_modified_terminal_element(mt)
        if res:
            analysis[mt] = res

    # Build element numbering using topological
    # ordering so subelements get priority
    from ffc.analysis import extract_sub_elements, sort_elements, _compute_element_numbers
    all_elements = [res[0] for res in analysis.values()]
    unique_elements = sort_elements(extract_sub_elements(all_elements))
    element_numbers = _compute_element_numbers(unique_elements)

    def add_table(res):
        element, avg, local_derivatives, flat_component = res

        # Build name for this particular table
        element_number = element_numbers[element]
        name = generate_psi_table_name(
            num_points, element_number, avg,
            entitytype, local_derivatives, flat_component)

        # Extract the values of the table from ffc table format
        if name not in tables:
            tables[name] = get_ffc_table_values(
                quadrature_rules[num_points][0],
                cell, integral_type,
                element, avg,
                entitytype, local_derivatives, flat_component)

            # Track table origin for custom integrals:
            table_origins[name] = res
        return name

    for mt in modified_terminals:
        res = analysis.get(mt)
        if not res:
            continue
        element, avg, local_derivatives, flat_component = res

        # Generate tables for each subelement in topological ordering,
        # using same avg and local_derivatives, for each component.
        # We want the first table to be the innermost subelement so that's
        # the one the optimized tables get the name from and so that's
        # the one the table origins point to for custom integrals.
        # This results in some superfluous tables but those will be
        # removed before code generation and it's not believed to be
        # a bottleneck.
        for subelement in sort_elements(extract_sub_elements([element])):
            for fc in range(product(subelement.reference_value_shape())):
                subres = (subelement, avg, local_derivatives, fc)
                name_ignored = add_table(subres)

        # Generate table and store table name with modified terminal
        name = add_table(res)
        mt_table_names[mt] = name

    return tables, mt_table_names, table_origins
예제 #17
0
 def reference_value_size(self):
     "Return the integer product of the reference value shape."
     return product(self.reference_value_shape())
예제 #18
0
파일: mixedelement.py 프로젝트: FEniCS/ufl
    def __init__(self, family, cell=None, degree=None, shape=None,
                 symmetry=None, quad_scheme=None):
        """Create tensor element (repeated mixed element with optional symmetries).

        :arg family: The family string, or an existing FiniteElement.
        :arg cell: The geometric cell (ignored if family is a FiniteElement).
        :arg degree: The polynomial degree (ignored if family is a FiniteElement).
        :arg shape: The shape of the element (defaults to a square
             tensor given by the geometric dimension of the cell).
        :arg symmetry: Optional symmetries.
        :arg quad_scheme: Optional quadrature scheme (ignored if
             family is a FiniteElement)."""

        if isinstance(family, FiniteElementBase):
            sub_element = family
            cell = sub_element.cell()
        else:
            if cell is not None:
                cell = as_cell(cell)
            # Create scalar sub element
            sub_element = FiniteElement(family, cell, degree, quad_scheme=quad_scheme)

        if sub_element.value_shape() != ():
            error("Expecting only scalar valued subelement for TensorElement.")

        # Set default shape if not specified
        if shape is None:
            if cell is None:
                error("Cannot infer tensor shape without a cell.")
            dim = cell.geometric_dimension()
            shape = (dim, dim)

        if symmetry is None:
            symmetry = EmptyDict
        elif symmetry is True:
            # Construct default symmetry dict for matrix elements
            if not (len(shape) == 2 and shape[0] == shape[1]):
                error("Cannot set automatic symmetry for non-square tensor.")
            symmetry = dict(((i, j), (j, i)) for i in range(shape[0])
                            for j in range(shape[1]) if i > j)
        else:
            if not isinstance(symmetry, dict):
                error("Expecting symmetry to be None (unset), True, or dict.")

        # Validate indices in symmetry dict
        for i, j in symmetry.items():
            if len(i) != len(j):
                error("Non-matching length of symmetry index tuples.")
            for k in range(len(i)):
                if not (i[k] >= 0 and j[k] >= 0 and i[k] < shape[k] and j[k] < shape[k]):
                    error("Symmetry dimensions out of bounds.")

        # Compute all index combinations for given shape
        indices = compute_indices(shape)

        # Compute mapping from indices to sub element number,
        # accounting for symmetry
        sub_elements = []
        sub_element_mapping = {}
        for index in indices:
            if index in symmetry:
                continue
            sub_element_mapping[index] = len(sub_elements)
            sub_elements += [sub_element]

        # Update mapping for symmetry
        for index in indices:
            if index in symmetry:
                sub_element_mapping[index] = sub_element_mapping[symmetry[index]]
        flattened_sub_element_mapping = [sub_element_mapping[index] for i,
                                         index in enumerate(indices)]

        # Compute value shape
        value_shape = shape

        # Compute reference value shape based on symmetries
        if symmetry:
            # Flatten and subtract symmetries
            reference_value_shape = (product(shape)-len(symmetry),)
            self._mapping = "symmetries"
        else:
            # Do not flatten if there are no symmetries
            reference_value_shape = shape
            self._mapping = "identity"

        # Initialize element data
        MixedElement.__init__(self, sub_elements, value_shape=value_shape,
                              reference_value_shape=reference_value_shape)
        self._family = sub_element.family()
        self._degree = sub_element.degree()
        self._sub_element = sub_element
        self._shape = shape
        self._symmetry = symmetry
        self._sub_element_mapping = sub_element_mapping
        self._flattened_sub_element_mapping = flattened_sub_element_mapping

        # Cache repr string
        self._repr = "TensorElement(%s, shape=%s, symmetry=%s)" % (
            repr(sub_element), repr(self._shape), repr(self._symmetry))
예제 #19
0
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
예제 #20
0
def _num_components(element):
    "Compute number of components for element."
    return product(element.value_shape())
예제 #21
0
def expression_to_dolfin_expression(expr, generic_function_members,
                                    mesh_function_members):
    "Generates code for a dolfin::Expression subclass for a single expression."

    # TODO: Make this configurable through global dolfin 'debug mode' parameter?
    add_runtime_checks = True

    # Check and flattern provided expression
    expr, expr_shape = flatten_and_check_expression(expr)

    # Extract code fragments from the expr
    generic_function_member_names = [
        item[0] for item in generic_function_members
    ]
    fragments, members = expression_to_code_fragments(
        expr, ["values", "x"], generic_function_member_names,
        mesh_function_members)

    # Generate code for value_rank
    value_shape_code = [
        "    _value_shape.push_back(%d);" % value_dim
        for value_dim in expr_shape
    ]

    evalcode = []

    # Runtime checks (TODO: Better to check these when updating the value instead...)
    if add_runtime_checks:
        for name, shape in generic_function_members:
            dim = product(shape)
            evalcode.append("    if (shared_%s->value_size() != %d)" %
                            (name, dim))
            evalcode.append("      dolfin_error(\"generated code\",")
            evalcode.append("                   \"calling eval\", ")
            evalcode.append(
                "                   \"Expecting value size %d for parameter \\'%s\\'\");"
                % (dim, name))
            evalcode.append("    if (shared_%s.get() == this)" % name)
            evalcode.append("      dolfin_error(\"generated code\",")
            evalcode.append("                   \"calling eval\",")
            evalcode.append(
                "                   \"Circular eval call detected. Cannot use itself as parameter \\'%s\\' within eval\");"
                % name)
            evalcode.append("")

    # Generate code for evaluating genericfunction members
    for name, shape in generic_function_members:
        dim = product(shape)

        # Setup output array and call eval
        evalcode.append("    double %s__data_[%d];" % (name, dim))
        evalcode.append("    Array<double> %s__array_(%d, %s__data_);" %
                        (name, dim, name))
        evalcode.append("    shared_%s->eval(%s__array_, x);" % (name, name))

        # Ensure const access through userdefined name
        if shape:
            # Vector valued result
            evalcode.append("    const Array<double> & %s = %s__array_;" %
                            (name, name))
        else:
            # Scalar valued result
            evalcode.append("    const double %s = %s__array_[0];" %
                            (name, name))
        evalcode.append("")

    # Lookup in MeshFunction<typename>(mesh, tdim)
    for name, typename in mesh_function_members:
        evalcode.append("    const %s %s = (*shared_%s)[cell.index];" %
                        (typename, name, name))

    # Generate code for the actual expression evaluation
    evalcode.extend("    values[%d] = %s;" % (i, c)
                    for i, c in enumerate(expr))

    # Adapt evalcode to with/without cell argument if possible
    evalcode = "\n".join(evalcode)
    evalcode_cell = evalcode.replace("__array_, x", "__array_, x, cell")
    if mesh_function_members:
        # TODO: Reuse code in Function::eval(values, x) which looks up cell from x?
        evalcode = []
        evalcode.append("      dolfin_error(\"generated code\",")
        evalcode.append("                   \"calling eval\", ")
        evalcode.append(
            "                   \"Need cell to evaluate this Expression\");")
        evalcode = "\n".join(evalcode)

    # Connect the code fragments using the expression template code
    fragments["evalcode"] = evalcode
    fragments["evalcode_cell"] = evalcode_cell
    fragments["value_shape"] = "\n".join(value_shape_code)

    # Assign classname
    classname = "Expression_" + hashlib.sha1(
        fragments["evalcode"].encode("utf-8")).hexdigest()
    fragments["classname"] = classname

    # Produce the C++ code for the expression class
    code = _expression_template % fragments
    return classname, code, members
예제 #22
0
def split(v):
    """UFL operator: If v is a Coefficient or Argument in a mixed space, returns
    a tuple with the function components corresponding to the subelements."""

    # Default range is all of v
    begin = 0
    end = None

    if isinstance(v, Indexed):
        # Special case: split previous output of split again
        # Consistent with simple element, just return function in a tuple
        return (v,)

    elif isinstance(v, ListTensor):
        # Special case: split previous output of split again
        ops = v.ufl_operands
        if all(isinstance(comp, Indexed) for comp in ops):
            args = [comp.ufl_operands[0] for comp in ops]
            if all(args[0] == args[i] for i in range(1, len(args))):
                # Get innermost terminal here and its element
                v = args[0]
                # Get relevant range of v components
                begin, = ops[0].ufl_operands[1]
                end, = ops[-1].ufl_operands[1]
                begin = int(begin)
                end = int(end) + 1
            else:
                error("Don't know how to split %s." % (v,))
        else:
            error("Don't know how to split %s." % (v,))

    # Special case: simple element, just return function in a tuple
    element = v.ufl_element()
    if not isinstance(element, MixedElement):
        assert end is None
        return (v,)

    if isinstance(element, TensorElement):
        if element.symmetry():
            error("Split not implemented for symmetric tensor elements.")

    if len(v.ufl_shape) != 1:
        error("Don't know how to split tensor valued mixed functions without flattened index space.")

    # Compute value size and set default range end
    value_size = product(element.value_shape())
    if end is None:
        end = value_size
    else:
        # Recursively dive into mixedelement in to subelement
        # corresponding to beginning of range
        j = begin
        while True:
            sub_i, j = element.extract_subelement_component(j)
            element = element.sub_elements()[sub_i]
            # Then break when we find the subelement that covers the whole range
            if product(element.value_shape()) == (end - begin):
                break

    # Build expressions representing the subfunction of v for each subelement
    offset = begin
    sub_functions = []
    for i, e in enumerate(element.sub_elements()):
        # Get shape, size, indices, and v components
        # corresponding to subelement value
        shape = e.value_shape()
        strides = shape_to_strides(shape)
        rank = len(shape)
        sub_size = product(shape)
        subindices = [flatten_multiindex(c, strides)
                      for c in compute_indices(shape)]
        components = [v[k + offset] for k in subindices]

        # Shape components into same shape as subelement
        if rank == 0:
            subv, = components
        elif rank <= 1:
            subv = as_vector(components)
        elif rank == 2:
            subv = as_matrix([components[i*shape[1]: (i+1)*shape[1]]
                              for i in range(shape[0])])
        else:
            error("Don't know how to split functions with sub functions of rank %d." % rank)

        offset += sub_size
        sub_functions.append(subv)

    if end != offset:
        error("Function splitting failed to extract components for whole intended range. Something is wrong.")

    return tuple(sub_functions)
예제 #23
0
파일: mixedelement.py 프로젝트: FEniCS/ufl
    def __init__(self, *elements, **kwargs):
        "Create mixed finite element from given list of elements"

        if type(self) is MixedElement:
            if kwargs:
                error("Not expecting keyword arguments to MixedElement constructor.")

        # Un-nest arguments if we get a single argument with a list of elements
        if len(elements) == 1 and isinstance(elements[0], (tuple, list)):
            elements = elements[0]
        # Interpret nested tuples as sub-mixedelements recursively
        elements = [MixedElement(e) if isinstance(e, (tuple, list)) else e
                    for e in elements]
        self._sub_elements = elements

        # Pick the first cell, for now all should be equal
        cells = tuple(sorted(set(element.cell() for element in elements) - set([None])))
        self._cells = cells
        if cells:
            cell = cells[0]
            # Require that all elements are defined on the same cell
            if not all(c == cell for c in cells[1:]):
                error("Sub elements must live on the same cell.")
        else:
            cell = None

        # Check that all elements use the same quadrature scheme TODO:
        # We can allow the scheme not to be defined.
        quad_scheme = elements[0].quadrature_scheme()
        if not all(e.quadrature_scheme() == quad_scheme for e in elements):
            error("Quadrature scheme mismatch for sub elements of mixed element.")

        # Compute value sizes in global and reference configurations
        value_size_sum = sum(product(s.value_shape()) for s in self._sub_elements)
        reference_value_size_sum = sum(product(s.reference_value_shape()) for s in self._sub_elements)

        # Default value shape: Treated simply as all subelement values
        # unpacked in a vector.
        value_shape = kwargs.get('value_shape', (value_size_sum,))

        # Default reference value shape: Treated simply as all
        # subelement reference values unpacked in a vector.
        reference_value_shape = kwargs.get('reference_value_shape', (reference_value_size_sum,))

        # Validate value_shape (deliberately not for subclasses
        # VectorElement and TensorElement)
        if type(self) is MixedElement:
            # This is not valid for tensor elements with symmetries,
            # assume subclasses deal with their own validation
            if product(value_shape) != value_size_sum:
                error("Provided value_shape doesn't match the "
                      "total value size of all subelements.")

        # Initialize element data
        degrees = {e.degree() for e in self._sub_elements} - {None}
        degree = max_degree(degrees) if degrees else None
        FiniteElementBase.__init__(self, "Mixed", cell, degree, quad_scheme,
                                   value_shape, reference_value_shape)

        # Cache repr string
        if type(self) is MixedElement:
            self._repr = "MixedElement(%s)" % (
                ", ".join(repr(e) for e in self._sub_elements),)
예제 #24
0
def _num_components(element):
    "Compute number of components for element."
    return product(element.value_shape())