Exemple #1
0
def _encapsulate(prefix, object_names, analysis, parameters):

    # Extract data from analysis
    form_datas, elements, element_map, domains = analysis

    # FIXME: Encapsulate domains?

    num_form_datas = len(form_datas)
    common_space = False

    # Special case: single element
    if num_form_datas == 0:
        capsules = _encapsule_element(prefix, elements)

    # Special case: with error control
    elif parameters["error_control"] and num_form_datas == 11:
        capsules = [_encapsule_form(prefix, object_names, form_data, i, element_map)
                    for (i, form_data) in enumerate(form_datas[:num_form_datas-1])]
        capsules += [_encapsule_form(prefix, object_names, form_datas[-1], num_form_datas-1,
                                     element_map, "GoalFunctional")]
    # Otherwise: generate standard capsules for each form
    else:
        capsules = [_encapsule_form(prefix, object_names, form_data, i, element_map) for
                    (i, form_data) in enumerate(form_datas)]
        # Check if all argument elements are equal
        elements = []
        for form_data in form_datas:
            elements += form_data.argument_elements
        common_space = all_equal(elements)

    return (capsules, common_space)
Exemple #2
0
def _encapsulate(prefix, object_names, analysis, parameters):

    # Extract data from analysis
    form_datas, elements, element_map, domains = analysis

    # FIXME: Encapsulate domains?

    num_form_datas = len(form_datas)
    common_space = False

    # Special case: single element
    if num_form_datas == 0:
        capsules = _encapsule_element(prefix, elements)

    # Special case: with error control
    elif parameters["error_control"] and num_form_datas == 11:
        capsules = [_encapsule_form(prefix, object_names, form_data, i, element_map)
                    for (i, form_data) in enumerate(form_datas[:num_form_datas - 1])]
        capsules += [_encapsule_form(prefix, object_names, form_datas[-1], num_form_datas - 1,
                                     element_map, "GoalFunctional")]
    # Otherwise: generate standard capsules for each form
    else:
        capsules = [_encapsule_form(prefix, object_names, form_data, i, element_map) for
                    (i, form_data) in enumerate(form_datas)]
        # Check if all argument elements are equal
        elements = []
        for form_data in form_datas:
            elements += form_data.argument_elements
        common_space = all_equal(elements)

    return (capsules, common_space)
Exemple #3
0
def _validate_quadrature_schemes_of_elements(quad_schemes, elements):
    # Update scheme for QuadratureElements
    if quad_schemes and all_equal(quad_schemes):
        scheme = quad_schemes[0]
    else:
        scheme = "canonical"
        info("Quadrature rule must be equal within each sub domain, using %s rule." % scheme)
    for element in elements:
        if element.family() == "Quadrature":
            qs = element.quadrature_scheme()
            if qs != scheme:
                error("Quadrature element must have specified quadrature scheme (%s) equal to the integral (%s)." % (qs, scheme))
Exemple #4
0
def _extract_common_quadrature_rule(integral_metadatas):
    # Check that quadrature rule is the same (To support mixed rules
    # would be some work since num_points is used to identify
    # quadrature rules in large parts of the pipeline)
    quadrature_rules = [md["quadrature_rule"] for md in integral_metadatas]
    if all_equal(quadrature_rules):
        qr = quadrature_rules[0]
    else:
        qr = "canonical"
        # FIXME: Shouldn't we raise here?
        info("Quadrature rule must be equal within each sub domain, using %s rule." % qr)
    return qr
Exemple #5
0
def _extract_common_quadrature_rule(integral_metadatas):
    # Check that quadrature rule is the same (To support mixed rules
    # would be some work since num_points is used to identify
    # quadrature rules in large parts of the pipeline)
    quadrature_rules = [md["quadrature_rule"] for md in integral_metadatas]
    if all_equal(quadrature_rules):
        qr = quadrature_rules[0]
    else:
        qr = "canonical"
        # FIXME: Shouldn't we raise here?
        info(
            "Quadrature rule must be equal within each sub domain, using %s rule."
            % qr)
    return qr
Exemple #6
0
def _extract_common_quadrature_degree(integral_metadatas):
    # Check that quadrature degree is the same
    quadrature_degrees = [md["quadrature_degree"] for md in integral_metadatas]
    for d in quadrature_degrees:
        if not isinstance(d, int):
            error("Invalid non-integer quadrature degree %s" % (str(d),))
    qd = max(quadrature_degrees)
    if not all_equal(quadrature_degrees):
        # FIXME: Shouldn't we raise here?
        # TODO: This may be loosened up without too much effort,
        # if the form compiler handles mixed integration degree,
        # something that most of the pipeline seems to be ready for.
        info("Quadrature degree must be equal within each sub domain, using degree %d." % qd)
    return qd
Exemple #7
0
def _validate_quadrature_schemes_of_elements(quad_schemes, elements):
    # Update scheme for QuadratureElements
    if quad_schemes and all_equal(quad_schemes):
        scheme = quad_schemes[0]
    else:
        scheme = "canonical"
        info(
            "Quadrature rule must be equal within each sub domain, using %s rule."
            % scheme)
    for element in elements:
        if element.family() == "Quadrature":
            qs = element.quadrature_scheme()
            if qs != scheme:
                error(
                    "Quadrature element must have specified quadrature scheme (%s) equal to the integral (%s)."
                    % (qs, scheme))
Exemple #8
0
def _extract_common_quadrature_degree(integral_metadatas):
    # Check that quadrature degree is the same
    quadrature_degrees = [md["quadrature_degree"] for md in integral_metadatas]
    for d in quadrature_degrees:
        if not isinstance(d, int):
            error("Invalid non-integer quadrature degree %s" % (str(d), ))
    qd = max(quadrature_degrees)
    if not all_equal(quadrature_degrees):
        # FIXME: Shouldn't we raise here?
        # TODO: This may be loosened up without too much effort,
        # if the form compiler handles mixed integration degree,
        # something that most of the pipeline seems to be ready for.
        info(
            "Quadrature degree must be equal within each sub domain, using degree %d."
            % qd)
    return qd
Exemple #9
0
def _attach_integral_metadata(form_data, parameters):
    "Attach integral metadata"

    # Recognized metadata keys
    metadata_keys = ("representation", "quadrature_degree", "quadrature_rule")

    # Iterate over integral collections
    quad_schemes = []
    for ida in form_data.integral_data:
        # TODO: Is it possible to detach this from IntegralData? It's a bit strange from the ufl side.
        common_metadata = ida.metadata

        # Iterate over integrals
        integral_metadatas = []
        for integral in ida.integrals:

            # Fill in integral metadata with default values
            # NB! This modifies the metadata of the input integral data!
            integral_metadata = integral.metadata() or {}
            for key in metadata_keys:
                if key not in integral_metadata:
                    integral_metadata[key] = parameters[key]

            # Automatic selection of representation
            r = integral_metadata["representation"]

            # Hack to override representation with environment variable
            forced_r = os.environ.get("FFC_FORCE_REPRESENTATION")
            if forced_r:
                r = forced_r
                warning("representation:    forced by $FFC_FORCE_REPRESENTATION to '%s'" % r)
            elif r == "auto":
                r = _auto_select_representation(integral,
                                                form_data.unique_sub_elements,
                                                form_data.function_replace_map)
                info("representation:    auto --> %s" % r)
            elif r in ("quadrature", "tensor", "uflacs"):
                info("representation:    %s" % r)
            else:
                info("Valid choices are 'tensor', 'quadrature', 'uflacs', or 'auto'.")
                error("Illegal choice of representation for integral: " + str(r))
            integral_metadata["representation"] = r

            # Automatic selection of quadrature degree
            qd = integral_metadata["quadrature_degree"]
            # Special case: handling -1 as "auto" for quadrature_degree
            if qd in ("auto", -1):
                qd = _auto_select_quadrature_degree(integral.integrand(),
                                                    r,
                                                    form_data.unique_sub_elements,
                                                    form_data.element_replace_map)
                info("quadrature_degree: auto --> " + str(qd))
            else:
                if isinstance(qd, tuple):
                    qd = tuple(int(q) for q in qd)
                else:
                    qd = int(qd)
                info("quadrature_degree: " + str(qd))
            # Validate degree
            if not qd >= 0:
                info("Valid choices are nonnegative integers or 'auto'.")
                error("Illegal quadrature degree for integral: " + str(qd))
            tdim = integral.ufl_domain().topological_dimension()
            _check_quadrature_degree(qd, tdim)

            integral_metadata["quadrature_degree"] = qd
            assert isinstance(qd, (int, tuple))

            # Automatic selection of quadrature rule
            qr = integral_metadata["quadrature_rule"]
            if qr == "auto":
                # Just use default for now.
                qr = "default"
                info("quadrature_rule:   auto --> %s" % qr)
            elif qr in ("default", "canonical", "vertex"):
                info("quadrature_rule:   %s" % qr)
            else:
                info("Valid choices are 'default', 'canonical', 'vertex', and 'auto'.")
                error("Illegal choice of quadrature rule for integral: " + str(qr))
            integral_metadata["quadrature_rule"] = qr
            quad_schemes.append(qr)

            # Append to list of metadata
            integral_metadatas.append(integral_metadata)

        # Extract common metadata for integral collection
        if len(ida.integrals) == 1:
            common_metadata.update(integral_metadatas[0])
        else:
            # Check that representation is the same
            # (Generating code with different representations within a
            # single tabulate_tensor is considered not worth the effort)
            representations = [md["representation"] for md in integral_metadatas]
            if all_equal(representations):
                r = representations[0]
            else:
                r = "quadrature"
                info("Integral representation must be equal within each sub domain, using %s representation." % r)

            # Check that quadrature degree is the same
            # FIXME: Why must the degree within a sub domain be the same?
            #        This makes no sense considering that num_points is
            #        used as a key all over in quadrature representation...
            quadrature_degrees = [md["quadrature_degree"] for md in integral_metadatas]
            if all_equal(quadrature_degrees):
                qd = quadrature_degrees[0]
            else:
                if isinstance(quadrature_degrees[0], tuple):
                    qd = tuple(map(max, zip(*quadrature_degrees)))
                else:
                    qd = max(quadrature_degrees)
                info("Quadrature degree must be equal within each sub domain, using degree " + str(qd))
            assert isinstance(qd, (int, tuple))

            # Check that quadrature rule is the same
            # FIXME: Why must the rule within a sub domain be the same?
            #        To support this would be more work since num_points is used
            #        to identify quadrature rules in the quadrature representation.
            quadrature_rules = [md["quadrature_rule"] for md in integral_metadatas]
            if all_equal(quadrature_rules):
                qr = quadrature_rules[0]
            else:
                qr = "canonical"
                info("Quadrature rule must be equal within each sub domain, using %s rule." % qr)

            # Update common metadata
            assert isinstance(qd, (int, tuple))
            common_metadata["representation"] = r
            common_metadata["quadrature_degree"] = qd
            common_metadata["quadrature_rule"] = qr

    # Update scheme for QuadratureElements
    if quad_schemes and all_equal(quad_schemes):
        scheme = quad_schemes[0]
    else:
        scheme = "canonical"
        info("Quadrature rule must be equal within each sub domain, using %s rule." % scheme)

    # FIXME: This modifies the elements depending on the form compiler parameters,
    #        this is a serious breach of the immutability of ufl objects, since the
    #        element quad scheme is part of the signature and hash of the element...
    for element in form_data.unique_sub_elements:
        if element.family() == "Quadrature":
            element._quad_scheme = scheme
Exemple #10
0
    def __init__(self, monomial):
        "Create transformed monomial from given monomial."

        # Reset monomial data
        self.float_value = monomial.float_value
        self.determinants = []
        self.coefficients = []
        self.transforms = []
        self.arguments = []

        # Reset index counters
        _reset_indices()

        # Initialize index map
        index_map = {}

        # Iterate over factors
        for f in monomial.factors:

            # Create FIAT element
            ufl_element = f.element()
            fiat_element = create_element(ufl_element)

            # Note nifty aspect here: when gdim != tdim, it might be
            # (in particular, is for H(div)/H(curl), that the value
            # dimension is different for the physical and reference
            # elements.

            # Get number of components
            # FIXME: Can't handle tensor-valued elements: vdim = shape[0]
            shape = ufl_element.value_shape()
            assert(len(shape) <= 1), \
                "MonomialTransformation does not handle tensor-valued elements"
            if len(shape) == 0:
                vdim = 1
            else:
                vdim = shape[0]

            # Extract dimensions
            sdim = fiat_element.space_dimension()
            domain, = ufl_element.domains()  # Assuming single domain
            gdim = domain.geometric_dimension()
            tdim = domain.topological_dimension()

            # Extract basis function index and coefficients
            if isinstance(f.function, Argument):
                vindex = MonomialIndex(index_type=MonomialIndex.PRIMARY,
                                       index_range=list(range(sdim)),
                                       index_id=f.index())

            elif isinstance(f.function, Coefficient):
                vindex = MonomialIndex(index_range=list(range(sdim)))
                coefficient = MonomialCoefficient(vindex, f.index())
                self.coefficients.append(coefficient)

            # Extract components
            components = self._extract_components(f, index_map, vdim)

            if len(components) > 1:
                raise MonomialException(
                    "Can only handle rank 0 or rank 1 tensors.")

            # Handle non-affine mappings (Piola)
            if len(components) > 0:

                # We can only handle rank 1 elements for now
                component = components[0]

                # Get mapping (all need to be equal)
                mappings = []
                for i in component.index_range:
                    (offset,
                     ufl_sub_element) = ufl_element.extract_component(i)
                    fiat_sub_element = create_element(ufl_sub_element)
                    mappings.extend(fiat_sub_element.mapping())
                if not all_equal(mappings):
                    raise MonomialException("Mappings differ: " +
                                            str(mappings))
                mapping = mappings[0]

                # Get component index relative to its sub element and its sub element
                (component_index, sub_element) = ufl_element.extract_component(
                    component.index_range[0])

                # Get offset
                if len(component_index) == 0:
                    offset = 0
                else:
                    offset = component.index_range[0] - component_index[0]

                # MER: Need to handle mappings in special ways if gdim
                # != tdim and some Piolas are present. This could
                # probably be merged with the offset code above, but I
                # was not able to wrap my head around the offsets
                # always referring to component.index_range[0].
                if (gdim != tdim):
                    assert len(component.index_range) == 1, \
                        "Component transform not implemented for this case. Please request this feature."
                    component, offset = transform_component(
                        component.index_range[0], offset, ufl_element)
                    component = MonomialIndex(index_type=MonomialIndex.FIXED,
                                              index_range=[component],
                                              index_id=None)
                    components = [
                        component,
                    ]

                # Add transforms where appropriate
                if mapping == "contravariant piola":
                    # phi(x) = (det J)^{-1} J Phi(X)
                    index0 = component
                    index1 = MonomialIndex(
                        index_range=list(range(tdim))) + offset
                    transform = MonomialTransform(index0, index1,
                                                  MonomialTransform.J,
                                                  f.restriction, offset)
                    self.transforms.append(transform)
                    determinant = MonomialDeterminant(
                        power=-1, restriction=f.restriction)
                    self.determinants.append(determinant)
                    components[0] = index1
                elif mapping == "covariant piola":
                    # phi(x) = J^{-T} Phi(X)
                    index0 = MonomialIndex(
                        index_range=list(range(tdim))) + offset
                    index1 = component
                    transform = MonomialTransform(index0, index1,
                                                  MonomialTransform.JINV,
                                                  f.restriction, offset)
                    self.transforms.append(transform)
                    components[0] = index0

            # Extract derivatives / transforms
            derivatives = []
            for d in f.derivatives:
                index0 = MonomialIndex(index_range=list(range(tdim)))
                if d in index_map:
                    index1 = index_map[d]
                elif isinstance(d, FixedIndex):
                    index1 = MonomialIndex(index_type=MonomialIndex.FIXED,
                                           index_range=[int(d)],
                                           index_id=int(d))
                else:
                    index1 = MonomialIndex(index_range=list(range(gdim)))
                index_map[d] = index1
                transform = MonomialTransform(index0, index1,
                                              MonomialTransform.JINV,
                                              f.restriction, 0)

                self.transforms.append(transform)
                derivatives.append(index0)

            # Extract restriction
            restriction = f.restriction

            # Create basis function
            v = MonomialArgument(ufl_element, vindex, components, derivatives,
                                 restriction)
            self.arguments.append(v)

        # Figure out secondary and auxiliary indices
        internal_indices = self._extract_internal_indices(None)
        external_indices = self._extract_external_indices(None)
        for i in internal_indices + external_indices:

            # Skip already visited indices
            if not i.index_type is None:
                continue

            # Set index type and id
            num_internal = len([j for j in internal_indices if j == i])
            num_external = len([j for j in external_indices if j == i])

            if num_internal == 1 and num_external == 1:
                i.index_type = MonomialIndex.SECONDARY
                i.index_id = _next_secondary_index()
            elif num_internal == 2 and num_external == 0:
                i.index_type = MonomialIndex.INTERNAL
                i.index_id = _next_internal_index()
            elif num_internal == 0 and num_external == 2:
                i.index_type = MonomialIndex.EXTERNAL
                i.index_id = _next_external_index()
            else:
                raise Exception(
                    "Summation index does not appear exactly twice: %s" %
                    str(i))
    def __init__(self, monomial):
        "Create transformed monomial from given monomial."

        # Reset monomial data
        self.float_value = monomial.float_value
        self.determinants = []
        self.coefficients = []
        self.transforms = []
        self.arguments = []

        # Reset index counters
        _reset_indices()

        # Initialize index map
        index_map = {}

        # Iterate over factors
        for f in monomial.factors:

            # Create FIAT element
            ufl_element = f.element()
            fiat_element = create_element(ufl_element)

            # Note nifty aspect here: when gdim != tdim, it might be
            # (in particular, is for H(div)/H(curl), that the value
            # dimension is different for the physical and reference
            # elements.

            # Get number of components
            # FIXME: Can't handle tensor-valued elements: vdim = shape[0]
            shape = ufl_element.value_shape()
            assert(len(shape) <= 1), \
                "MonomialTransformation does not handle tensor-valued elements"
            if len(shape) == 0:
                vdim = 1
            else:
                vdim = shape[0]

            # Extract dimensions
            sdim = fiat_element.space_dimension()
            cell = ufl_element.cell()
            gdim = cell.geometric_dimension()
            tdim = cell.topological_dimension()

            # Extract basis function index and coefficients
            if isinstance(f.function, Argument):
                vindex = MonomialIndex(index_type=MonomialIndex.PRIMARY,
                                       index_range=list(range(sdim)),
                                       index_id=f.index())

            elif isinstance(f.function, Coefficient):
                vindex = MonomialIndex(index_range=list(range(sdim)))
                coefficient = MonomialCoefficient(vindex, f.index())
                self.coefficients.append(coefficient)

            # Extract components
            components = self._extract_components(f, index_map, vdim)

            if len(components) > 1:
                raise MonomialException("Can only handle rank 0 or rank 1 tensors.")

            # Handle non-affine mappings (Piola)
            if len(components) > 0:

                # We can only handle rank 1 elements for now
                component = components[0]

                # Get mapping (all need to be equal)
                mappings = []
                for i in component.index_range:
                    (offset, ufl_sub_element) = ufl_element.extract_component(i)
                    fiat_sub_element = create_element(ufl_sub_element)
                    mappings.extend(fiat_sub_element.mapping())
                if not all_equal(mappings):
                    raise MonomialException("Mappings differ: " + str(mappings))
                mapping = mappings[0]

                # Get component index relative to its sub element and its sub element
                (component_index, sub_element) = ufl_element.extract_component(component.index_range[0])

                # Get offset
                if len(component_index) == 0:
                    offset = 0
                else:
                    offset = component.index_range[0] - component_index[0]

                # MER: Need to handle mappings in special ways if gdim
                # != tdim and some Piolas are present. This could
                # probably be merged with the offset code above, but I
                # was not able to wrap my head around the offsets
                # always referring to component.index_range[0].
                if (gdim != tdim):
                    assert len(component.index_range) == 1, \
                        "Component transform not implemented for this case. Please request this feature."
                    component, offset = transform_component(component.index_range[0], offset, ufl_element)
                    component = MonomialIndex(index_type=MonomialIndex.FIXED,
                                              index_range=[component], index_id=None)
                    components = [component, ]

                # Add transforms where appropriate
                if mapping == "contravariant piola":
                    # phi(x) = (det J)^{-1} J Phi(X)
                    index0 = component
                    index1 = MonomialIndex(index_range=list(range(tdim))) + offset
                    transform = MonomialTransform(index0, index1,
                                                  MonomialTransform.J,
                                                  f.restriction, offset)
                    self.transforms.append(transform)
                    determinant = MonomialDeterminant(power=-1,
                                                      restriction=f.restriction)
                    self.determinants.append(determinant)
                    components[0] = index1
                elif mapping == "covariant piola":
                    # phi(x) = J^{-T} Phi(X)
                    index0 = MonomialIndex(index_range=list(range(tdim))) + offset
                    index1 = component
                    transform = MonomialTransform(index0, index1,
                                                  MonomialTransform.JINV,
                                                  f.restriction, offset)
                    self.transforms.append(transform)
                    components[0] = index0

            # Extract derivatives / transforms
            derivatives = []
            for d in f.derivatives:
                index0 = MonomialIndex(index_range=list(range(tdim)))
                if d in index_map:
                    index1 = index_map[d]
                elif isinstance(d, FixedIndex):
                    index1 = MonomialIndex(index_type=MonomialIndex.FIXED,
                                           index_range=[int(d)],
                                           index_id=int(d))
                else:
                    index1 = MonomialIndex(index_range=list(range(gdim)))
                index_map[d] = index1
                transform = MonomialTransform(index0, index1, MonomialTransform.JINV, f.restriction, 0)

                self.transforms.append(transform)
                derivatives.append(index0)

            # Extract restriction
            restriction = f.restriction

            # Create basis function
            v = MonomialArgument(ufl_element, vindex, components, derivatives, restriction)
            self.arguments.append(v)

        # Figure out secondary and auxiliary indices
        internal_indices = self._extract_internal_indices(None)
        external_indices = self._extract_external_indices(None)
        for i in internal_indices + external_indices:

            # Skip already visited indices
            if not i.index_type is None:
                continue

            # Set index type and id
            num_internal = len([j for j in internal_indices if j == i])
            num_external = len([j for j in external_indices if j == i])

            if num_internal == 1 and num_external == 1:
                i.index_type = MonomialIndex.SECONDARY
                i.index_id   = _next_secondary_index()
            elif num_internal == 2 and num_external == 0:
                i.index_type = MonomialIndex.INTERNAL
                i.index_id   = _next_internal_index()
            elif num_internal == 0 and num_external == 2:
                i.index_type = MonomialIndex.EXTERNAL
                i.index_id   = _next_external_index()
            else:
                raise Exception("Summation index does not appear exactly twice: %s" % str(i))
def _attach_integral_metadata(form_data, parameters):
    "Attach integral metadata"

    # Recognized metadata keys
    metadata_keys = ("representation", "quadrature_degree", "quadrature_rule")

    # Iterate over integral collections
    quad_schemes = []
    for ida in form_data.integral_data:
        common_metadata = ida.metadata # TODO: Is it possible to detach this from IntegralData? It's a bit strange from the ufl side.

        # Iterate over integrals
        integral_metadatas = []
        for integral in ida.integrals:

            # Get metadata for integral
            integral_metadata = integral.compiler_data() or {}
            for key in metadata_keys:
                if not key in integral_metadata:
                    integral_metadata[key] = parameters[key]

            # Special case: handling -1 as "auto" for quadrature_degree
            if integral_metadata["quadrature_degree"] == -1:
                integral_metadata["quadrature_degree"] = "auto"

            # Check metadata
            r  = integral_metadata["representation"]
            qd = integral_metadata["quadrature_degree"]
            qr = integral_metadata["quadrature_rule"]
            if not r in ("quadrature", "tensor", "uflacs", "auto"):
                info("Valid choices are 'tensor', 'quadrature', 'uflacs', or 'auto'.")
                error("Illegal choice of representation for integral: " + str(r))
            if not qd  == "auto":
                qd = int(qd)
                if not qd >= 0:
                    info("Valid choices are nonnegative integers or 'auto'.")
                    error("Illegal quadrature degree for integral: " + str(qd))
                integral_metadata["quadrature_degree"] = qd
            if not qr in ("default", "canonical", "vertex", "auto"):
                info("Valid choices are 'default', 'canonical', 'vertex', and 'auto'.")
                error("Illegal choice of quadrature rule for integral: " + str(qr))

            # Automatic selection of representation
            if r == "auto":
                # TODO: This doesn't really need the measure except for code redesign
                #       reasons, pass integrand instead to reduce dependencies.
                #       Not sure if function_replace_map is really needed either,
                #       just passing it to be on the safe side.
                r = _auto_select_representation(integral,
                                                form_data.unique_sub_elements,
                                                form_data.function_replace_map)
                info("representation:    auto --> %s" % r)
                integral_metadata["representation"] = r
            else:
                info("representation:    %s" % r)

            # Automatic selection of quadrature degree
            if qd == "auto":
                qd = _auto_select_quadrature_degree(integral.integrand(),
                                                    r,
                                                    form_data.unique_sub_elements,
                                                    form_data.element_replace_map)
                info("quadrature_degree: auto --> %d" % qd)
                integral_metadata["quadrature_degree"] = qd
            else:
                info("quadrature_degree: %d" % qd)
            _check_quadrature_degree(qd, form_data.topological_dimension)

            # Automatic selection of quadrature rule
            if qr == "auto":
                # Just use default for now.
                qr = "default"
                info("quadrature_rule:   auto --> %s" % qr)
                integral_metadata["quadrature_rule"] = qr
            else:
                info("quadrature_rule:   %s" % qr)
            quad_schemes.append(qr)

            # Append to list of metadata
            integral_metadatas.append(integral_metadata)

        # Extract common metadata for integral collection
        if len(ida.integrals) == 1:
            common_metadata.update(integral_metadatas[0])
        else:

            # Check that representation is the same
            # FIXME: Why must the representation within a sub domain be the same?
            representations = [md["representation"] for md in integral_metadatas]
            if not all_equal(representations):
                r = "quadrature"
                info("Integral representation must be equal within each sub domain, using %s representation." % r)
            else:
                r = representations[0]

            # Check that quadrature degree is the same
            # FIXME: Why must the degree within a sub domain be the same?
            quadrature_degrees = [md["quadrature_degree"] for md in integral_metadatas]
            if not all_equal(quadrature_degrees):
                qd = max(quadrature_degrees)
                info("Quadrature degree must be equal within each sub domain, using degree %d." % qd)
            else:
                qd = quadrature_degrees[0]

            # Check that quadrature rule is the same
            # FIXME: Why must the rule within a sub domain be the same?
            quadrature_rules = [md["quadrature_rule"] for md in integral_metadatas]
            if not all_equal(quadrature_rules):
                qr = "canonical"
                info("Quadrature rule must be equal within each sub domain, using %s rule." % qr)
            else:
                qr = quadrature_rules[0]

            # Update common metadata
            common_metadata["representation"] = r
            common_metadata["quadrature_degree"] = qd
            common_metadata["quadrature_rule"] = qr

    # Update scheme for QuadratureElements
    if not all_equal(quad_schemes):
        scheme = "canonical"
        info("Quadrature rule must be equal within each sub domain, using %s rule." % qr)
    else:
        scheme = quad_schemes[0]
    for element in form_data.sub_elements:
        if element.family() == "Quadrature":
            element._quad_scheme = scheme
def _attach_integral_metadata(form_data, parameters):
    "Attach integral metadata"

    # Recognized metadata keys
    metadata_keys = ("representation", "quadrature_degree", "quadrature_rule")

    # Iterate over integral collections
    quad_schemes = []
    for ida in form_data.integral_data:
        common_metadata = ida.metadata  # TODO: Is it possible to detach this from IntegralData? It's a bit strange from the ufl side.

        # Iterate over integrals
        integral_metadatas = []
        for integral in ida.integrals:

            # Get metadata for integral
            integral_metadata = integral.compiler_data() or {}
            for key in metadata_keys:
                if not key in integral_metadata:
                    integral_metadata[key] = parameters[key]

            # Special case: handling -1 as "auto" for quadrature_degree
            if integral_metadata["quadrature_degree"] == -1:
                integral_metadata["quadrature_degree"] = "auto"

            # Check metadata
            r = integral_metadata["representation"]
            qd = integral_metadata["quadrature_degree"]
            qr = integral_metadata["quadrature_rule"]
            if not r in ("quadrature", "tensor", "uflacs", "auto"):
                info(
                    "Valid choices are 'tensor', 'quadrature', 'uflacs', or 'auto'."
                )
                error("Illegal choice of representation for integral: " +
                      str(r))
            if not qd == "auto":
                qd = int(qd)
                if not qd >= 0:
                    info("Valid choices are nonnegative integers or 'auto'.")
                    error("Illegal quadrature degree for integral: " + str(qd))
                integral_metadata["quadrature_degree"] = qd
            if not qr in ("default", "canonical", "vertex", "auto"):
                info(
                    "Valid choices are 'default', 'canonical', 'vertex', and 'auto'."
                )
                error("Illegal choice of quadrature rule for integral: " +
                      str(qr))

            # Automatic selection of representation
            if r == "auto":
                # TODO: This doesn't really need the measure except for code redesign
                #       reasons, pass integrand instead to reduce dependencies.
                #       Not sure if function_replace_map is really needed either,
                #       just passing it to be on the safe side.
                r = _auto_select_representation(integral,
                                                form_data.unique_sub_elements,
                                                form_data.function_replace_map)
                info("representation:    auto --> %s" % r)
                integral_metadata["representation"] = r
            else:
                info("representation:    %s" % r)

            # Automatic selection of quadrature degree
            if qd == "auto":
                qd = _auto_select_quadrature_degree(
                    integral.integrand(), r, form_data.unique_sub_elements,
                    form_data.element_replace_map)
                info("quadrature_degree: auto --> %d" % qd)
                integral_metadata["quadrature_degree"] = qd
            else:
                info("quadrature_degree: %d" % qd)
            _check_quadrature_degree(qd, form_data.topological_dimension)

            # Automatic selection of quadrature rule
            if qr == "auto":
                # Just use default for now.
                qr = "default"
                info("quadrature_rule:   auto --> %s" % qr)
                integral_metadata["quadrature_rule"] = qr
            else:
                info("quadrature_rule:   %s" % qr)
            quad_schemes.append(qr)

            # Append to list of metadata
            integral_metadatas.append(integral_metadata)

        # Extract common metadata for integral collection
        if len(ida.integrals) == 1:
            common_metadata.update(integral_metadatas[0])
        else:

            # Check that representation is the same
            # FIXME: Why must the representation within a sub domain be the same?
            representations = [
                md["representation"] for md in integral_metadatas
            ]
            if not all_equal(representations):
                r = "quadrature"
                info(
                    "Integral representation must be equal within each sub domain, using %s representation."
                    % r)
            else:
                r = representations[0]

            # Check that quadrature degree is the same
            # FIXME: Why must the degree within a sub domain be the same?
            quadrature_degrees = [
                md["quadrature_degree"] for md in integral_metadatas
            ]
            if not all_equal(quadrature_degrees):
                qd = max(quadrature_degrees)
                info(
                    "Quadrature degree must be equal within each sub domain, using degree %d."
                    % qd)
            else:
                qd = quadrature_degrees[0]

            # Check that quadrature rule is the same
            # FIXME: Why must the rule within a sub domain be the same?
            quadrature_rules = [
                md["quadrature_rule"] for md in integral_metadatas
            ]
            if not all_equal(quadrature_rules):
                qr = "canonical"
                info(
                    "Quadrature rule must be equal within each sub domain, using %s rule."
                    % qr)
            else:
                qr = quadrature_rules[0]

            # Update common metadata
            common_metadata["representation"] = r
            common_metadata["quadrature_degree"] = qd
            common_metadata["quadrature_rule"] = qr

    # Update scheme for QuadratureElements
    if not all_equal(quad_schemes):
        scheme = "canonical"
        info(
            "Quadrature rule must be equal within each sub domain, using %s rule."
            % qr)
    else:
        scheme = quad_schemes[0]
    for element in form_data.sub_elements:
        if element.family() == "Quadrature":
            element._quad_scheme = scheme