Example #1
0
 def _basic_is_time_dependent(expression_or_form, iterator):
     for node in iterator(expression_or_form):
         # ... parametrized expressions
         if isinstance(node, BaseExpression):
             if is_pull_back_expression(
                     node) and is_pull_back_expression_time_dependent(node):
                 return True
             else:
                 if has_pybind11():
                     parameters = node._parameters
                 else:
                     parameters = node.user_parameters
                 if "t" in parameters:
                     return True
         # ... problem solutions related to nonlinear terms
         elif wrapping.is_problem_solution_type(node):
             if wrapping.is_problem_solution(node):
                 (preprocessed_node, component, truth_solution
                  ) = wrapping.solution_identify_component(node)
                 truth_problem = get_problem_from_solution(truth_solution)
                 if hasattr(truth_problem, "set_time"):
                     return True
             elif wrapping.is_problem_solution_dot(node):
                 return True
     return False
Example #2
0
 def _basic_expression_name(expression):
     str_repr = ""
     coefficients_replacement = dict()
     # Preprocess indices first, as their numeric value might change from run to run, but they
     # are always sorted the same way
     indices = set()
     min_index = None
     for t in traverse_unique_terminals(expression):
         if isinstance(t, MultiIndex):
             for i in t.indices():
                 if isinstance(i, MuteIndex):
                     if min_index is None or i.count() < min_index:
                         min_index = i.count()
                     indices.add(i)
     for i in indices:
         coefficients_replacement[repr(i)] = "MuteIndexRBniCS(" + str(i.count() - min_index) + ")"
     # Process the expression
     visited = set()
     for n in wrapping.expression_iterator(expression):
         if n in visited:
             continue
         if hasattr(n, "_cppcode"):
             coefficients_replacement[repr(n)] = str(n._cppcode)
             str_repr += repr(n._cppcode)
             visited.add(n)
         elif wrapping.is_problem_solution_type(n):
             if wrapping.is_problem_solution(n):
                 (preprocessed_n, component, truth_solution) = wrapping.solution_identify_component(n)
                 problem = get_problem_from_solution(truth_solution)
                 coefficients_replacement[repr(preprocessed_n)] = "solution of " + str(problem.name()) + " (exact problem decorator: " + str(hasattr(problem, "__is_exact__")) + ", component: " + str(component) + ")"
             elif wrapping.is_problem_solution_dot(n):
                 (preprocessed_n, component, truth_solution_dot) = wrapping.solution_dot_identify_component(n)
                 problem = get_problem_from_solution_dot(truth_solution_dot)
                 coefficients_replacement[repr(preprocessed_n)] = "solution_dot of " + str(problem.name()) + " (exact problem decorator: " + str(hasattr(problem, "__is_exact__")) + ", component: " + str(component) + ")"
             else:
                 (preprocessed_n, component, problem) = wrapping.get_auxiliary_problem_for_non_parametrized_function(n)
                 coefficients_replacement[repr(preprocessed_n)] = "non parametrized function associated to auxiliary problem " + str(problem.name())
             if len(component) == 1 and component[0] is not None:
                 coefficients_replacement[repr(preprocessed_n)] += ", component " + str(component[0])
             elif len(component) > 1:
                 coefficients_replacement[repr(preprocessed_n)] += ", component " + str(component)
             str_repr += coefficients_replacement[repr(preprocessed_n)]
             # Make sure to skip any parent solution related to this one
             visited.add(n)
             visited.add(preprocessed_n)
             for parent_n in wrapping.solution_iterator(preprocessed_n):
                 visited.add(parent_n)
         elif isinstance(n, Constant):
             vals = n.values()
             coefficients_replacement[repr(n)] = str(vals)
             str_repr += repr(str(vals))
             visited.add(n)
         else:
             str_repr += repr(n)
             visited.add(n)
     for key, value in coefficients_replacement.items():
         str_repr = str_repr.replace(key, value)
     hash_code = hashlib.sha1(str_repr.encode("utf-8")).hexdigest()
     return hash_code
Example #3
0
 def _basic_expression_description(expression):
     visited = set()
     coefficients_repr = dict()
     for n in wrapping.expression_iterator(expression):
         if n in visited:
             continue
         if hasattr(n, "_cppcode"):
             coefficients_repr[n] = str(n._cppcode)
             visited.add(n)
         elif wrapping.is_problem_solution_type(n):
             if wrapping.is_problem_solution(n):
                 (preprocessed_n, component,
                  truth_solution) = wrapping.solution_identify_component(n)
                 problem = get_problem_from_solution(truth_solution)
                 coefficients_repr[preprocessed_n] = "solution of " + str(
                     problem.name()) + " (exact problem decorator: " + str(
                         hasattr(problem, "__is_exact__")
                     ) + ", component: " + str(component) + ")"
             elif wrapping.is_problem_solution_dot(n):
                 (preprocessed_n, component, truth_solution_dot
                  ) = wrapping.solution_dot_identify_component(n)
                 problem = get_problem_from_solution_dot(truth_solution_dot)
                 coefficients_repr[
                     preprocessed_n] = "solution_dot of " + str(
                         problem.name(
                         )) + " (exact problem decorator: " + str(
                             hasattr(problem, "__is_exact__")
                         ) + ", component: " + str(component) + ")"
             else:
                 (
                     preprocessed_n, component, problem
                 ) = wrapping.get_auxiliary_problem_for_non_parametrized_function(
                     n)
                 coefficients_repr[
                     preprocessed_n] = "non parametrized function associated to auxiliary problem " + str(
                         problem.name())
             if len(component) == 1 and component[0] is not None:
                 coefficients_repr[preprocessed_n] += ", component " + str(
                     component[0])
             elif len(component) > 1:
                 coefficients_repr[preprocessed_n] += ", component " + str(
                     component)
             # Make sure to skip any parent solution related to this one
             visited.add(n)
             visited.add(preprocessed_n)
             for parent_n in wrapping.solution_iterator(preprocessed_n):
                 visited.add(parent_n)
         elif isinstance(n, Constant):
             vals = n.values()
             if len(vals) == 1:
                 coefficients_repr[n] = str(vals[0])
             else:
                 coefficients_repr[n] = str(vals.reshape(n.ufl_shape))
             visited.add(n)
         else:
             visited.add(n)
     return coefficients_repr
 def create_interpolation_locations_container(self, **kwargs):
     # Populate auxiliary_problems_and_components
     visited = set()
     auxiliary_problems_and_components = set(
     )  # of (problem, component)
     for node in wrapping.form_iterator(self._form, "nodes"):
         if node in visited:
             continue
         # ... problem solutions related to nonlinear terms
         elif wrapping.is_problem_solution_type(node):
             if wrapping.is_problem_solution(node):
                 (preprocessed_node, component, truth_solution
                  ) = wrapping.solution_identify_component(node)
                 truth_problem = get_problem_from_solution(
                     truth_solution)
                 auxiliary_problems_and_components.add(
                     (truth_problem, component))
             elif wrapping.is_problem_solution_dot(node):
                 (preprocessed_node, component, truth_solution_dot
                  ) = wrapping.solution_dot_identify_component(node)
                 truth_problem = get_problem_from_solution_dot(
                     truth_solution_dot)
                 auxiliary_problems_and_components.add(
                     (truth_problem, component))
             else:
                 (
                     preprocessed_node, component, auxiliary_problem
                 ) = wrapping.get_auxiliary_problem_for_non_parametrized_function(
                     node)
                 auxiliary_problems_and_components.add(
                     (auxiliary_problem, component))
             # Make sure to skip any parent solution related to this one
             visited.add(node)
             visited.add(preprocessed_node)
             for parent_node in wrapping.solution_iterator(
                     preprocessed_node):
                 visited.add(parent_node)
     if len(auxiliary_problems_and_components) == 0:
         auxiliary_problems_and_components = None
     # Create reduced mesh
     assert "auxiliary_problems_and_components" not in kwargs
     kwargs[
         "auxiliary_problems_and_components"] = auxiliary_problems_and_components
     return backend.ReducedMesh(self._spaces, **kwargs)
 def _basic_expression_description(expression):
     visited = set()
     coefficients_repr = dict()
     for n in wrapping.expression_iterator(expression):
         if n in visited:
             continue
         if isinstance(n, BaseExpression):
             assert isinstance(
                 n,
                 (CompiledExpression,
                  Expression)), "Other expression types are not handled yet"
             if isinstance(n, Expression):
                 coefficients_repr[n] = str(n._cppcode)
             elif isinstance(n, CompiledExpression):
                 assert hasattr(
                     n, "f_no_upcast"
                 ), "Only the case of pulled back expressions is currently handled"
                 assert hasattr(
                     n,
                     "shape_parametrization_expression_on_subdomain_no_upcast"
                 ), ("Only the case of pulled back expressions is currently handled"
                     )
                 coefficients_repr[n] = ("PullBackExpression(" + str(
                     n.
                     shape_parametrization_expression_on_subdomain_no_upcast
                     ._cppcode) + ", " + str(n.f_no_upcast._cppcode) + ")")
             visited.add(n)
         elif wrapping.is_problem_solution_type(n):
             if wrapping.is_problem_solution(n):
                 (preprocessed_n, component,
                  truth_solution) = wrapping.solution_identify_component(n)
                 problem = get_problem_from_solution(truth_solution)
                 coefficients_repr[preprocessed_n] = (
                     "solution of " + str(problem.name()) +
                     " (exact problem decorator: " +
                     str(hasattr(problem, "__is_exact__")) +
                     ", component: " + str(component) + ")")
             elif wrapping.is_problem_solution_dot(n):
                 (preprocessed_n, component, truth_solution_dot
                  ) = wrapping.solution_dot_identify_component(n)
                 problem = get_problem_from_solution_dot(truth_solution_dot)
                 coefficients_repr[preprocessed_n] = (
                     "solution_dot of " + str(problem.name()) +
                     " (exact problem decorator: " +
                     str(hasattr(problem, "__is_exact__")) +
                     ", component: " + str(component) + ")")
             else:
                 (
                     preprocessed_n, component, problem
                 ) = wrapping.get_auxiliary_problem_for_non_parametrized_function(
                     n)
                 coefficients_repr[preprocessed_n] = (
                     "non parametrized function associated to auxiliary problem "
                     + str(problem.name()))
             if len(component) == 1 and component[0] is not None:
                 coefficients_repr[preprocessed_n] += ", component " + str(
                     component[0])
             elif len(component) > 1:
                 coefficients_repr[preprocessed_n] += ", component " + str(
                     component)
             # Make sure to skip any parent solution related to this one
             visited.add(n)
             visited.add(preprocessed_n)
             for parent_n in wrapping.solution_iterator(preprocessed_n):
                 visited.add(parent_n)
         elif isinstance(n, Constant):
             vals = n.values()
             if len(vals) == 1:
                 coefficients_repr[n] = str(vals[0])
             else:
                 coefficients_repr[n] = str(vals.reshape(n.ufl_shape))
             visited.add(n)
         else:
             visited.add(n)
     return coefficients_repr
Example #6
0
        def separate(self):
            class _SeparatedParametrizedForm_Replacer(Transformer):
                def __init__(self, mapping):
                    Transformer.__init__(self)
                    self.mapping = mapping

                def operator(self, e, *ops):
                    if e in self.mapping:
                        return self.mapping[e]
                    else:
                        return e._ufl_expr_reconstruct_(*ops)

                def terminal(self, e):
                    return self.mapping.get(e, e)

            logger.log(DEBUG,
                       "***        SEPARATE FORM COEFFICIENTS        ***")

            logger.log(DEBUG, "1. Extract coefficients")
            integral_to_coefficients = dict()
            for integral in self._form.integrals():
                logger.log(
                    DEBUG,
                    "\t Currently on integrand " + str(integral.integrand()))
                self._coefficients.append(list())  # of ParametrizedExpression
                for e in iter_expressions(integral):
                    logger.log(DEBUG, "\t\t Expression " + str(e))
                    pre_traversal_e = [n for n in pre_traversal(e)]
                    tree_nodes_skip = [False for _ in pre_traversal_e]
                    for (n_i, n) in enumerate(pre_traversal_e):
                        if not tree_nodes_skip[n_i]:
                            # Skip expressions which are trivially non parametrized
                            if isinstance(n, Argument):
                                logger.log(
                                    DEBUG, "\t\t Node " + str(n) +
                                    " is skipped because it is an Argument")
                                continue
                            elif isinstance(n, Constant):
                                logger.log(
                                    DEBUG, "\t\t Node " + str(n) +
                                    " is skipped because it is a Constant")
                                continue
                            elif isinstance(n, MultiIndex):
                                logger.log(
                                    DEBUG, "\t\t Node " + str(n) +
                                    " is skipped because it is a MultiIndex")
                                continue
                            # Skip all expressions with at least one leaf which is an Argument
                            for t in traverse_terminals(n):
                                if isinstance(t, Argument):
                                    logger.log(
                                        DEBUG, "\t\t Node " + str(n) +
                                        " is skipped because it contains an Argument"
                                    )
                                    break
                            else:  # not broken
                                logger.log(
                                    DEBUG, "\t\t Node " + str(n) +
                                    " and its descendants are being analyzed for non-parametrized check"
                                )
                                # Make sure to skip all descendants of this node in the outer loop
                                # Note that a map with key set to the expression is not enough to
                                # mark the node as visited, since the same expression may appear
                                # on different sides of the tree
                                pre_traversal_n = [d for d in pre_traversal(n)]
                                for (d_i, d) in enumerate(pre_traversal_n):
                                    assert d == pre_traversal_e[
                                        n_i +
                                        d_i]  # make sure that we are marking the right node
                                    tree_nodes_skip[n_i + d_i] = True
                                # We might be able to strip any (non-parametrized) expression out
                                all_candidates = list()
                                internal_tree_nodes_skip = [
                                    False for _ in pre_traversal_n
                                ]
                                for (d_i, d) in enumerate(pre_traversal_n):
                                    if not internal_tree_nodes_skip[d_i]:
                                        # Skip all expressions where at least one leaf is not parametrized
                                        for t in traverse_terminals(d):
                                            if isinstance(t, BaseExpression):
                                                if wrapping.is_pull_back_expression(
                                                        t
                                                ) and not wrapping.is_pull_back_expression_parametrized(
                                                        t):
                                                    logger.log(
                                                        DEBUG,
                                                        "\t\t\t Descendant node "
                                                        + str(d) +
                                                        " causes the non-parametrized check to break because it contains a non-parametrized pulled back expression"
                                                    )
                                                    break
                                                else:
                                                    parameters = t._parameters
                                                    if "mu_0" not in parameters:
                                                        logger.log(
                                                            DEBUG,
                                                            "\t\t\t Descendant node "
                                                            + str(d) +
                                                            " causes the non-parametrized check to break because it contains a non-parametrized expression"
                                                        )
                                                        break
                                            elif isinstance(t, Constant):
                                                logger.log(
                                                    DEBUG,
                                                    "\t\t\t Descendant node " +
                                                    str(d) +
                                                    " causes the non-parametrized check to break because it contains a constant"
                                                )
                                                break
                                            elif isinstance(
                                                    t, GeometricQuantity
                                            ) and not isinstance(
                                                    t, FacetNormal
                                            ) and self._strict:
                                                logger.log(
                                                    DEBUG,
                                                    "\t\t\t Descendant node " +
                                                    str(d) +
                                                    " causes the non-parametrized check to break because it contains a geometric quantity and strict mode is on"
                                                )
                                                break
                                            elif wrapping.is_problem_solution_type(
                                                    t):
                                                if not wrapping.is_problem_solution(
                                                        t
                                                ) and not wrapping.is_problem_solution_dot(
                                                        t):
                                                    logger.log(
                                                        DEBUG,
                                                        "\t\t\t Descendant node "
                                                        + str(d) +
                                                        " causes the non-parametrized check to break because it contains a non-parametrized function"
                                                    )
                                                    break
                                                elif self._strict:  # solutions are not allowed, break
                                                    if wrapping.is_problem_solution(
                                                            t):
                                                        (
                                                            _, component,
                                                            solution
                                                        ) = wrapping.solution_identify_component(
                                                            t)
                                                        problem = get_problem_from_solution(
                                                            solution)
                                                        logger.log(
                                                            DEBUG,
                                                            "\t\t\t Descendant node "
                                                            + str(d) +
                                                            " causes the non-parametrized check to break because it contains the solution of "
                                                            + problem.name() +
                                                            " (exact problem decorator: "
                                                            + str(
                                                                hasattr(
                                                                    problem,
                                                                    "__is_exact__"
                                                                )) +
                                                            ", component: " +
                                                            str(component) +
                                                            ") and strict mode is on"
                                                        )
                                                        break
                                                    elif wrapping.is_problem_solution_dot(
                                                            t):
                                                        (
                                                            _, component,
                                                            solution_dot
                                                        ) = wrapping.solution_dot_identify_component(
                                                            t)
                                                        problem = get_problem_from_solution_dot(
                                                            solution_dot)
                                                        logger.log(
                                                            DEBUG,
                                                            "\t\t\t Descendant node "
                                                            + str(d) +
                                                            " causes the non-parametrized check to break because it contains the solution_dot of "
                                                            + problem.name() +
                                                            " (exact problem decorator: "
                                                            + str(
                                                                hasattr(
                                                                    problem,
                                                                    "__is_exact__"
                                                                )) +
                                                            ", component: " +
                                                            str(component) +
                                                            ") and strict mode is on"
                                                        )
                                                    else:
                                                        raise RuntimeError(
                                                            "Unidentified solution found"
                                                        )
                                        else:
                                            at_least_one_expression_or_solution = False
                                            for t in traverse_terminals(d):
                                                if isinstance(
                                                        t, BaseExpression
                                                ):  # which is parametrized, because previous for loop was not broken
                                                    at_least_one_expression_or_solution = True
                                                    logger.log(
                                                        DEBUG,
                                                        "\t\t\t Descendant node "
                                                        + str(d) +
                                                        " is a candidate after non-parametrized check because it contains the parametrized expression "
                                                        + str(t))
                                                    break
                                                elif wrapping.is_problem_solution_type(
                                                        t):
                                                    if wrapping.is_problem_solution(
                                                            t):
                                                        at_least_one_expression_or_solution = True
                                                        (
                                                            _, component,
                                                            solution
                                                        ) = wrapping.solution_identify_component(
                                                            t)
                                                        problem = get_problem_from_solution(
                                                            solution)
                                                        logger.log(
                                                            DEBUG,
                                                            "\t\t\t Descendant node "
                                                            + str(d) +
                                                            " is a candidate after non-parametrized check because it contains the solution of "
                                                            + problem.name() +
                                                            " (exact problem decorator: "
                                                            + str(
                                                                hasattr(
                                                                    problem,
                                                                    "__is_exact__"
                                                                )) +
                                                            ", component: " +
                                                            str(component) +
                                                            ")")
                                                        break
                                                    elif wrapping.is_problem_solution_dot(
                                                            t):
                                                        at_least_one_expression_or_solution = True
                                                        (
                                                            _, component,
                                                            solution_dot
                                                        ) = wrapping.solution_dot_identify_component(
                                                            t)
                                                        problem = get_problem_from_solution_dot(
                                                            solution_dot)
                                                        logger.log(
                                                            DEBUG,
                                                            "\t\t\t Descendant node "
                                                            + str(d) +
                                                            " is a candidate after non-parametrized check because it contains the solution_dot of "
                                                            + problem.name() +
                                                            " (exact problem decorator: "
                                                            + str(
                                                                hasattr(
                                                                    problem,
                                                                    "__is_exact__"
                                                                )) +
                                                            ", component: " +
                                                            str(component) +
                                                            ")")
                                                        break
                                            if at_least_one_expression_or_solution:
                                                all_candidates.append(d)
                                                pre_traversal_d = [
                                                    q for q in pre_traversal(d)
                                                ]
                                                for (q_i, q) in enumerate(
                                                        pre_traversal_d):
                                                    assert q == pre_traversal_n[
                                                        d_i +
                                                        q_i]  # make sure that we are marking the right node
                                                    internal_tree_nodes_skip[
                                                        d_i + q_i] = True
                                            else:
                                                logger.log(
                                                    DEBUG,
                                                    "\t\t\t Descendant node " +
                                                    str(d) +
                                                    " has not passed the non-parametrized because it is not a parametrized expression or a solution"
                                                )
                                # Evaluate candidates
                                if len(
                                        all_candidates
                                ) == 0:  # the whole expression was actually non-parametrized
                                    logger.log(
                                        DEBUG, "\t\t Node " + str(n) +
                                        " is skipped because it is a non-parametrized coefficient"
                                    )
                                    continue
                                elif len(
                                        all_candidates
                                ) == 1:  # the whole expression was actually parametrized
                                    logger.log(
                                        DEBUG, "\t\t Node " + str(n) +
                                        " will be accepted because it is a non-parametrized coefficient"
                                    )
                                    pass
                                else:  # part of the expression was not parametrized, and separating the non parametrized part may result in more than one coefficient
                                    if self._strict:  # non parametrized coefficients are not allowed, so split the expression
                                        logger.log(
                                            DEBUG, "\t\t\t Node " + str(n) +
                                            " will be accepted because it is a non-parametrized coefficient with more than one candidate. It will be split because strict mode is on. Its split coefficients are "
                                            + ", ".join([
                                                str(c) for c in all_candidates
                                            ]))
                                    else:  # non parametrized coefficients are allowed, so go on with the whole expression
                                        logger.log(
                                            DEBUG, "\t\t\t Node " + str(n) +
                                            " will be accepted because it is a non-parametrized coefficient with more than one candidate. It will not be split because strict mode is off. Splitting it would have resulted in more than one coefficient, namely "
                                            + ", ".join([
                                                str(c) for c in all_candidates
                                            ]))
                                        all_candidates = [n]
                                # Add the coefficient(s)
                                for candidate in all_candidates:

                                    def preprocess_candidate(candidate):
                                        if isinstance(candidate, Indexed):
                                            assert len(
                                                candidate.ufl_operands) == 2
                                            assert isinstance(
                                                candidate.ufl_operands[1],
                                                MultiIndex)
                                            if all([
                                                    isinstance(
                                                        index, FixedIndex)
                                                    for index in candidate.
                                                    ufl_operands[1].indices()
                                            ]):
                                                logger.log(
                                                    DEBUG,
                                                    "\t\t\t Preprocessed descendant node "
                                                    + str(candidate) +
                                                    " as an Indexed expression with fixed indices, resulting in a candidate "
                                                    + str(candidate) +
                                                    " of type " +
                                                    str(type(candidate)))
                                                return candidate  # no further preprocessing needed
                                            else:
                                                logger.log(
                                                    DEBUG,
                                                    "\t\t\t Preprocessed descendant node "
                                                    + str(candidate) +
                                                    " as an Indexed expression with at least one mute index, resulting in a candidate "
                                                    + str(candidate.
                                                          ufl_operands[0]) +
                                                    " of type " + str(
                                                        type(candidate.
                                                             ufl_operands[0])))
                                                return preprocess_candidate(
                                                    candidate.ufl_operands[0])
                                        elif isinstance(candidate, IndexSum):
                                            assert len(
                                                candidate.ufl_operands) == 2
                                            assert isinstance(
                                                candidate.ufl_operands[1],
                                                MultiIndex)
                                            assert all([
                                                isinstance(index, MuteIndex)
                                                for index in candidate.
                                                ufl_operands[1].indices()
                                            ])
                                            logger.log(
                                                DEBUG,
                                                "\t\t\t Preprocessed descendant node "
                                                + str(candidate) +
                                                " as an IndexSum expression, resulting in a candidate "
                                                +
                                                str(candidate.ufl_operands[0])
                                                + " of type " + str(
                                                    type(candidate.
                                                         ufl_operands[0])))
                                            return preprocess_candidate(
                                                candidate.ufl_operands[0])
                                        elif isinstance(candidate, ListTensor):
                                            candidates = set([
                                                preprocess_candidate(component)
                                                for component in
                                                candidate.ufl_operands
                                            ])
                                            if len(candidates) == 1:
                                                preprocessed_candidate = candidates.pop(
                                                )
                                                logger.log(
                                                    DEBUG,
                                                    "\t\t\t Preprocessed descendant node "
                                                    + str(candidate) +
                                                    " as an ListTensor expression with a unique preprocessed component, resulting in a candidate "
                                                    +
                                                    str(preprocessed_candidate)
                                                    + " of type " + str(
                                                        type(
                                                            preprocessed_candidate
                                                        )))
                                                return preprocess_candidate(
                                                    preprocessed_candidate)
                                            else:
                                                at_least_one_mute_index = False
                                                candidates_from_components = list(
                                                )
                                                for component in candidates:
                                                    assert isinstance(
                                                        component,
                                                        (ComponentTensor,
                                                         Indexed))
                                                    assert len(
                                                        component.ufl_operands
                                                    ) == 2
                                                    assert isinstance(
                                                        component.
                                                        ufl_operands[1],
                                                        MultiIndex)
                                                    if not all([
                                                            isinstance(
                                                                index,
                                                                FixedIndex) for
                                                            index in component.
                                                            ufl_operands[1].
                                                            indices()
                                                    ]):
                                                        at_least_one_mute_index = True
                                                    candidates_from_components.append(
                                                        preprocess_candidate(
                                                            component.
                                                            ufl_operands[0]))
                                                if at_least_one_mute_index:
                                                    candidates_from_components = set(
                                                        candidates_from_components
                                                    )
                                                    assert len(
                                                        candidates_from_components
                                                    ) == 1
                                                    preprocessed_candidate = candidates_from_components.pop(
                                                    )
                                                    logger.log(
                                                        DEBUG,
                                                        "\t\t\t Preprocessed descendant node "
                                                        + str(candidate) +
                                                        " as an ListTensor expression with multiple preprocessed components with at least one mute index, resulting in a candidate "
                                                        +
                                                        str(preprocessed_candidate
                                                            ) + " of type " +
                                                        str(
                                                            type(
                                                                preprocessed_candidate
                                                            )))
                                                    return preprocess_candidate(
                                                        preprocessed_candidate)
                                                else:
                                                    logger.log(
                                                        DEBUG,
                                                        "\t\t\t Preprocessed descendant node "
                                                        + str(candidate) +
                                                        " as an ListTensor expression with multiple preprocessed components with fixed indices, resulting in a candidate "
                                                        + str(candidate) +
                                                        " of type " +
                                                        str(type(candidate)))
                                                    return candidate  # no further preprocessing needed
                                        else:
                                            logger.log(
                                                DEBUG,
                                                "\t\t\t No preprocessing required for descendant node "
                                                + str(candidate) +
                                                " as a coefficient of type " +
                                                str(type(candidate)))
                                            return candidate

                                    preprocessed_candidate = preprocess_candidate(
                                        candidate)
                                    if preprocessed_candidate not in self._coefficients[
                                            -1]:
                                        self._coefficients[-1].append(
                                            preprocessed_candidate)
                                    logger.log(
                                        DEBUG,
                                        "\t\t\t Accepting descendant node " +
                                        str(preprocessed_candidate) +
                                        " as a coefficient of type " +
                                        str(type(preprocessed_candidate)))
                        else:
                            logger.log(
                                DEBUG, "\t\t Node " + str(n) +
                                " to be skipped because it is a descendant of a coefficient which has already been detected"
                            )
                if len(self._coefficients[-1]
                       ) == 0:  # then there were no coefficients to extract
                    logger.log(DEBUG,
                               "\t There were no coefficients to extract")
                    self._coefficients.pop(
                    )  # remove the (empty) element that was added to possibly store coefficients
                else:
                    logger.log(DEBUG, "\t Extracted coefficients are:")
                    for c in self._coefficients[-1]:
                        logger.log(DEBUG, "\t\t" + str(c))
                    integral_to_coefficients[integral] = self._coefficients[-1]

            logger.log(DEBUG,
                       "2. Prepare placeholders and forms with placeholders")
            for integral in self._form.integrals():
                # Prepare measure for the new form (from firedrake/mg/ufl_utils.py)
                measure = Measure(integral.integral_type(),
                                  domain=integral.ufl_domain(),
                                  subdomain_id=integral.subdomain_id(),
                                  subdomain_data=integral.subdomain_data(),
                                  metadata=integral.metadata())
                if integral not in integral_to_coefficients:
                    logger.log(
                        DEBUG, "\t Adding form for integrand " +
                        str(integral.integrand()) + " to unchanged forms")
                    self._form_unchanged.append(integral.integrand() * measure)
                else:
                    logger.log(
                        DEBUG,
                        "\t Preparing form with placeholders for integrand " +
                        str(integral.integrand()))
                    self._placeholders.append(list())  # of Constants
                    placeholders_dict = dict()
                    for c in integral_to_coefficients[integral]:
                        self._placeholders[-1].append(
                            Constant(self._NaN * ones(c.ufl_shape)))
                        placeholders_dict[c] = self._placeholders[-1][-1]
                        logger.log(
                            DEBUG, "\t\t " + str(placeholders_dict[c]) +
                            " is the placeholder for " + str(c))
                    replacer = _SeparatedParametrizedForm_Replacer(
                        placeholders_dict)
                    new_integrand = apply_transformer(integral.integrand(),
                                                      replacer)
                    self._form_with_placeholders.append(new_integrand *
                                                        measure)

            logger.log(
                DEBUG,
                "3. Assert that there are no parametrized expressions left")
            for form in self._form_with_placeholders:
                for integral in form.integrals():
                    for e in pre_traversal(integral.integrand()):
                        if isinstance(e, BaseExpression):
                            assert not (
                                wrapping.is_pull_back_expression(e)
                                and wrapping.
                                is_pull_back_expression_parametrized(e)
                            ), "Form " + str(
                                integral
                            ) + " still contains a parametrized pull back expression"
                            parameters = e._parameters
                            assert "mu_0" not in parameters, "Form " + str(
                                integral
                            ) + " still contains a parametrized expression"

            logger.log(DEBUG, "4. Prepare coefficients hash codes")
            for addend in self._coefficients:
                self._placeholder_names.append(list())  # of string
                for factor in addend:
                    self._placeholder_names[-1].append(
                        wrapping.expression_name(factor))

            logger.log(DEBUG, "5. Assert list length consistency")
            assert len(self._coefficients) == len(self._placeholders)
            assert len(self._coefficients) == len(self._placeholder_names)
            for (c, p, pn) in zip(self._coefficients, self._placeholders,
                                  self._placeholder_names):
                assert len(c) == len(p)
                assert len(c) == len(pn)
            assert len(self._coefficients) == len(self._form_with_placeholders)

            logger.log(DEBUG,
                       "*** DONE - SEPARATE FORM COEFFICIENTS - DONE ***")
            logger.log(DEBUG, "")
    def _basic_form_on_reduced_function_space(form_wrapper, at):
        form = form_wrapper._form
        form_name = form_wrapper.name()
        form_problem = get_problem_from_parametrized_operator(form_wrapper)
        reduced_V = at.get_reduced_function_spaces()
        reduced_subdomain_data = at.get_reduced_subdomain_data()
        mu = form_problem.mu
        if hasattr(form_problem, "set_time"):
            t = form_problem.t
        else:
            t = None

        if (form_name, reduced_V) not in form_cache:
            visited = set()
            replacements = dict()
            truth_problems = list()
            truth_problem_to_components = {  # outer dict index over time derivative
                0: dict(),
                1: dict()
            }
            truth_problem_to_exact_truth_problem = dict()
            truth_problem_to_reduced_mesh_solution = dict()
            truth_problem_to_reduced_mesh_solution_dot = dict()
            truth_problem_to_reduced_mesh_interpolator = {  # outer dict index over time derivative
                0: dict(),
                1: dict()
            }
            reduced_problem_to_components = {  # outer dict index over time derivative
                0: dict(),
                1: dict()
            }
            reduced_problem_to_reduced_mesh_solution = dict()
            reduced_problem_to_reduced_mesh_solution_dot = dict()
            reduced_problem_to_reduced_basis_functions = {  # outer dict index over time derivative
                0: dict(),
                1: dict()
            }

            # Look for terminals on truth mesh
            logger.log(DEBUG, "Traversing terminals of form " + form_name)
            for node in wrapping.form_iterator(form, "nodes"):
                if node in visited:
                    continue
                # ... test and trial functions
                elif isinstance(node, Argument):
                    logger.log(
                        DEBUG, "\tFound argument, number: " +
                        str(node.number()) + ", part: " + str(node.part()))
                    replacements[node] = wrapping.form_argument_replace(
                        node, reduced_V)
                    visited.add(node)
                # ... problem solutions related to nonlinear terms
                elif wrapping.is_problem_solution_type(node):
                    node_is_problem_solution = wrapping.is_problem_solution(
                        node)
                    node_is_problem_solution_dot = wrapping.is_problem_solution_dot(
                        node)
                    if node_is_problem_solution or node_is_problem_solution_dot:
                        if node_is_problem_solution:
                            (preprocessed_node, component, truth_solution
                             ) = wrapping.solution_identify_component(node)
                            truth_problem = get_problem_from_solution(
                                truth_solution)
                            logger.log(
                                DEBUG,
                                "\tFound problem solution of truth problem " +
                                truth_problem.name() +
                                " (exact problem decorator: " +
                                str(hasattr(truth_problem, "__is_exact__")) +
                                ", component: " + str(component) + ")")
                            # Time derivative key for components and interpolator dicts
                            time_derivative = 0
                        elif node_is_problem_solution_dot:
                            (preprocessed_node, component, truth_solution_dot
                             ) = wrapping.solution_dot_identify_component(node)
                            truth_problem = get_problem_from_solution_dot(
                                truth_solution_dot)
                            logger.log(
                                DEBUG,
                                "\tFound problem solution dot of truth problem "
                                + truth_problem.name() +
                                " (exact problem decorator: " +
                                str(hasattr(truth_problem, "__is_exact__")) +
                                ", component: " + str(component) + ")")
                            # Time derivative key for components and interpolator dicts
                            time_derivative = 1
                        # Store truth problem
                        if truth_problem not in truth_problems:
                            truth_problems.append(truth_problem)
                        # Store the component
                        if truth_problem not in truth_problem_to_components[
                                time_derivative]:
                            truth_problem_to_components[time_derivative][
                                truth_problem] = list()
                        if component not in truth_problem_to_components[
                                time_derivative][truth_problem]:
                            truth_problem_to_components[time_derivative][
                                truth_problem].append(component)
                            # Get the function space corresponding to preprocessed_node on the reduced mesh
                            auxiliary_reduced_V = at.get_auxiliary_reduced_function_space(
                                truth_problem, component)
                            # Define and store the replacement
                            assert preprocessed_node not in replacements  # as it is related to a new truth solution component
                            replacements[preprocessed_node] = backend.Function(
                                auxiliary_reduced_V)
                            if time_derivative == 0:
                                if truth_problem not in truth_problem_to_reduced_mesh_solution:
                                    truth_problem_to_reduced_mesh_solution[
                                        truth_problem] = list()
                                truth_problem_to_reduced_mesh_solution[
                                    truth_problem].append(
                                        replacements[preprocessed_node])
                            elif time_derivative == 1:
                                if truth_problem not in truth_problem_to_reduced_mesh_solution_dot:
                                    truth_problem_to_reduced_mesh_solution_dot[
                                        truth_problem] = list()
                                truth_problem_to_reduced_mesh_solution_dot[
                                    truth_problem].append(
                                        replacements[preprocessed_node])
                            # Get interpolator on reduced mesh
                            if truth_problem not in truth_problem_to_reduced_mesh_interpolator[
                                    time_derivative]:
                                truth_problem_to_reduced_mesh_interpolator[
                                    time_derivative][truth_problem] = list()
                            truth_problem_to_reduced_mesh_interpolator[
                                time_derivative][truth_problem].append(
                                    at.get_auxiliary_function_interpolator(
                                        truth_problem, component))
                    else:
                        (
                            preprocessed_node, component, auxiliary_problem
                        ) = wrapping.get_auxiliary_problem_for_non_parametrized_function(
                            node)
                        logger.log(
                            DEBUG, "\tFound non parametrized function " +
                            str(preprocessed_node) +
                            " associated to auxiliary problem " +
                            str(auxiliary_problem.name()) + ", component: " +
                            str(component))
                        if preprocessed_node not in replacements:
                            # Get interpolator on reduced mesh
                            auxiliary_truth_problem_to_reduced_mesh_interpolator = at.get_auxiliary_function_interpolator(
                                auxiliary_problem, component)
                            # Define and store the replacement
                            replacements[
                                preprocessed_node] = auxiliary_truth_problem_to_reduced_mesh_interpolator(
                                    preprocessed_node)
                    # Make sure to skip any parent solution related to this one
                    visited.add(node)
                    visited.add(preprocessed_node)
                    for parent_node in wrapping.solution_iterator(
                            preprocessed_node):
                        visited.add(parent_node)
                # ... geometric quantities
                elif isinstance(node, GeometricQuantity):
                    logger.log(DEBUG,
                               "\tFound geometric quantity " + str(node))
                    if len(reduced_V) == 2:
                        assert reduced_V[0].mesh().ufl_domain(
                        ) == reduced_V[1].mesh().ufl_domain()
                    replacements[node] = type(node)(reduced_V[0].mesh())
                    visited.add(node)
                else:
                    visited.add(node)
            # ... and replace them
            replaced_form = wrapping.form_replace(form, replacements, "nodes")

            # Look for measures ...
            if len(reduced_V) == 2:
                assert reduced_V[0].mesh().ufl_domain() == reduced_V[1].mesh(
                ).ufl_domain()
            measure_reduced_domain = reduced_V[0].mesh().ufl_domain()
            replacements_measures = dict()
            for integral in wrapping.form_iterator(replaced_form, "integrals"):
                # Prepare measure for the new form (from firedrake/mg/ufl_utils.py)
                integral_subdomain_data = integral.subdomain_data()
                if integral_subdomain_data is not None:
                    integral_reduced_subdomain_data = reduced_subdomain_data[
                        integral_subdomain_data]
                else:
                    integral_reduced_subdomain_data = None
                measure = Measure(
                    integral.integral_type(),
                    domain=measure_reduced_domain,
                    subdomain_id=integral.subdomain_id(),
                    subdomain_data=integral_reduced_subdomain_data,
                    metadata=integral.metadata())
                replacements_measures[integral.integrand(),
                                      integral.integral_type(),
                                      integral.subdomain_id()] = measure
            # ... and replace them
            replaced_form_with_replaced_measures = wrapping.form_replace(
                replaced_form, replacements_measures, "measures")

            # Cache the resulting dicts
            form_cache[(form_name,
                        reduced_V)] = replaced_form_with_replaced_measures
            truth_problems_cache[(form_name, reduced_V)] = truth_problems
            truth_problem_to_components_cache[(
                form_name, reduced_V)] = truth_problem_to_components
            truth_problem_to_exact_truth_problem_cache[(
                form_name, reduced_V)] = truth_problem_to_exact_truth_problem
            truth_problem_to_reduced_mesh_solution_cache[(
                form_name, reduced_V)] = truth_problem_to_reduced_mesh_solution
            truth_problem_to_reduced_mesh_solution_dot_cache[(
                form_name,
                reduced_V)] = truth_problem_to_reduced_mesh_solution_dot
            truth_problem_to_reduced_mesh_interpolator_cache[(
                form_name,
                reduced_V)] = truth_problem_to_reduced_mesh_interpolator
            reduced_problem_to_components_cache[(
                form_name, reduced_V)] = reduced_problem_to_components
            reduced_problem_to_reduced_mesh_solution_cache[(
                form_name,
                reduced_V)] = reduced_problem_to_reduced_mesh_solution
            reduced_problem_to_reduced_mesh_solution_dot_cache[(
                form_name,
                reduced_V)] = reduced_problem_to_reduced_mesh_solution_dot
            reduced_problem_to_reduced_basis_functions_cache[(
                form_name,
                reduced_V)] = reduced_problem_to_reduced_basis_functions

        # Extract from cache
        replaced_form_with_replaced_measures = form_cache[(form_name,
                                                           reduced_V)]
        truth_problems = truth_problems_cache[(form_name, reduced_V)]
        truth_problem_to_components = truth_problem_to_components_cache[(
            form_name, reduced_V)]
        truth_problem_to_exact_truth_problem = truth_problem_to_exact_truth_problem_cache[
            (form_name, reduced_V)]
        truth_problem_to_reduced_mesh_solution = truth_problem_to_reduced_mesh_solution_cache[
            (form_name, reduced_V)]
        truth_problem_to_reduced_mesh_solution_dot = truth_problem_to_reduced_mesh_solution_dot_cache[
            (form_name, reduced_V)]
        truth_problem_to_reduced_mesh_interpolator = truth_problem_to_reduced_mesh_interpolator_cache[
            (form_name, reduced_V)]
        reduced_problem_to_components = reduced_problem_to_components_cache[(
            form_name, reduced_V)]
        reduced_problem_to_reduced_mesh_solution = reduced_problem_to_reduced_mesh_solution_cache[
            (form_name, reduced_V)]
        reduced_problem_to_reduced_mesh_solution_dot = reduced_problem_to_reduced_mesh_solution_dot_cache[
            (form_name, reduced_V)]
        reduced_problem_to_reduced_basis_functions = reduced_problem_to_reduced_basis_functions_cache[
            (form_name, reduced_V)]

        # Get list of truth and reduced problems that need to be solved, possibly updating cache
        required_truth_problems = list()
        required_reduced_problems = list()
        for truth_problem in truth_problems:
            truth_problem_is_solving = hasattr(truth_problem, "_is_solving")
            if is_training_started(truth_problem):
                reduced_problem = get_reduced_problem_from_problem(
                    truth_problem)
                reduced_problem_is_solving = hasattr(reduced_problem,
                                                     "_is_solving")
            else:
                reduced_problem = None
                reduced_problem_is_solving = False
            if not truth_problem_is_solving:
                if is_training_finished(truth_problem):
                    logger.log(
                        DEBUG, "Truth problem " + truth_problem.name() +
                        " (exact problem decorator: " +
                        str(hasattr(truth_problem, "__is_exact__")) +
                        ") is not currently solving, and its offline stage has finished: truth problem will be replaced by reduced problem"
                    )
                    # Store the replacement for solution
                    if (reduced_problem
                            not in reduced_problem_to_reduced_mesh_solution
                            and truth_problem
                            in truth_problem_to_reduced_mesh_solution):
                        reduced_problem_to_reduced_mesh_solution[
                            reduced_problem] = truth_problem_to_reduced_mesh_solution[
                                truth_problem]
                        # Store the component
                        assert reduced_problem not in reduced_problem_to_components[
                            0]
                        assert truth_problem in truth_problem_to_components[0]
                        reduced_problem_to_components[0][
                            reduced_problem] = truth_problem_to_components[0][
                                truth_problem]
                        # Get reduced problem basis functions on reduced mesh
                        assert reduced_problem not in reduced_problem_to_reduced_basis_functions[
                            0]
                        reduced_problem_to_reduced_basis_functions[0][
                            reduced_problem] = [
                                at.get_auxiliary_basis_functions_matrix(
                                    truth_problem, component) for component in
                                reduced_problem_to_components[0]
                                [reduced_problem]
                            ]
                    # Store the replacement for solution_dot
                    if (reduced_problem
                            not in reduced_problem_to_reduced_mesh_solution_dot
                            and truth_problem
                            in truth_problem_to_reduced_mesh_solution_dot):
                        reduced_problem_to_reduced_mesh_solution_dot[
                            reduced_problem] = truth_problem_to_reduced_mesh_solution_dot[
                                truth_problem]
                        # Store the component
                        assert reduced_problem not in reduced_problem_to_components[
                            1]
                        assert truth_problem in truth_problem_to_components[1]
                        reduced_problem_to_components[1][
                            reduced_problem] = truth_problem_to_components[1][
                                truth_problem]
                        # Get reduced problem basis functions on reduced mesh
                        assert reduced_problem not in reduced_problem_to_reduced_basis_functions[
                            1]
                        reduced_problem_to_reduced_basis_functions[1][
                            reduced_problem] = [
                                at.get_auxiliary_basis_functions_matrix(
                                    truth_problem, component) for component in
                                reduced_problem_to_components[1]
                                [reduced_problem]
                            ]
                    # Append to list of required reduced problems
                    required_reduced_problems.append(
                        (reduced_problem, reduced_problem_is_solving))
                else:
                    if (hasattr(truth_problem,
                                "_apply_exact_evaluation_at_stages") and
                            not hasattr(truth_problem, "_apply_EIM_at_stages")
                            and not hasattr(truth_problem,
                                            "_apply_DEIM_at_stages")):
                        logger.log(
                            DEBUG, "Truth problem " + truth_problem.name() +
                            " (exact problem decorator: " +
                            str(hasattr(truth_problem, "__is_exact__")) +
                            ") is not currently solving, its offline stage has not finished, and only @ExactParametrizedFunctions has been used: truth solve of this truth problem instance will be called"
                        )
                        # Init truth problem (if required), as it may not have been initialized
                        truth_problem.init()
                        # Append to list of required truth problems which are not currently solving
                        required_truth_problems.append(
                            (truth_problem, False, reduced_problem_is_solving))
                    else:
                        logger.log(
                            DEBUG, "Truth problem " + truth_problem.name() +
                            " (exact problem decorator: " +
                            str(hasattr(truth_problem, "__is_exact__")) +
                            ") is not currently solving, its offline stage has not finished, and either @ExactParametrizedFunctions has not been used or it has been used in combination with @DEIM or @EIM: truth solve on an auxiliary instance (with exact problem decorator) will be called, to prevent early initialization of DEIM/EIM data structures"
                        )
                        # Store the corresponding exact truth problem
                        if truth_problem not in truth_problem_to_exact_truth_problem:
                            exact_truth_problem = exact_problem(truth_problem)
                            truth_problem_to_exact_truth_problem[
                                truth_problem] = exact_truth_problem
                            # Init exact truth problem (if required), as it may not have been initialized
                            exact_truth_problem.init()
                        else:
                            exact_truth_problem = truth_problem_to_exact_truth_problem[
                                truth_problem]
                        # Store the replacement for solution
                        if (exact_truth_problem
                                not in truth_problem_to_reduced_mesh_solution
                                and truth_problem
                                in truth_problem_to_reduced_mesh_solution):
                            truth_problem_to_reduced_mesh_solution[
                                exact_truth_problem] = truth_problem_to_reduced_mesh_solution[
                                    truth_problem]
                            # Store the component
                            assert exact_truth_problem not in truth_problem_to_components[
                                0]
                            assert truth_problem in truth_problem_to_components[
                                0]
                            truth_problem_to_components[0][
                                exact_truth_problem] = truth_problem_to_components[
                                    0][truth_problem]
                            # Get interpolator on reduced mesh
                            assert exact_truth_problem not in truth_problem_to_reduced_mesh_interpolator[
                                0]
                            assert truth_problem in truth_problem_to_reduced_mesh_interpolator[
                                0]
                            truth_problem_to_reduced_mesh_interpolator[0][
                                exact_truth_problem] = truth_problem_to_reduced_mesh_interpolator[
                                    0][truth_problem]
                        # Store the replacement for solution_dot
                        if (exact_truth_problem not in
                                truth_problem_to_reduced_mesh_solution_dot
                                and truth_problem
                                in truth_problem_to_reduced_mesh_solution_dot):
                            truth_problem_to_reduced_mesh_solution_dot[
                                exact_truth_problem] = truth_problem_to_reduced_mesh_solution_dot[
                                    truth_problem]
                            # Store the component
                            assert exact_truth_problem not in truth_problem_to_components[
                                1]
                            assert truth_problem in truth_problem_to_components[
                                1]
                            truth_problem_to_components[1][
                                exact_truth_problem] = truth_problem_to_components[
                                    1][truth_problem]
                            # Get interpolator on reduced mesh
                            assert exact_truth_problem not in truth_problem_to_reduced_mesh_interpolator[
                                1]
                            assert truth_problem in truth_problem_to_reduced_mesh_interpolator[
                                1]
                            truth_problem_to_reduced_mesh_interpolator[1][
                                exact_truth_problem] = truth_problem_to_reduced_mesh_interpolator[
                                    1][truth_problem]
                        # Append to list of required truth problems which are not currently solving
                        required_truth_problems.append(
                            (exact_truth_problem, False,
                             reduced_problem_is_solving))
            else:
                logger.log(
                    DEBUG, "Truth problem " + truth_problem.name() +
                    " (exact problem decorator: " +
                    str(hasattr(truth_problem, "__is_exact__")) +
                    ") is currently solving: current truth solution will be loaded"
                )
                assert not reduced_problem_is_solving
                # Append to list of required truth problems which are currently solving
                required_truth_problems.append((truth_problem, True, False))

        # Solve truth problems (which have not been reduced yet) associated to nonlinear terms
        for (truth_problem, truth_problem_is_solving,
             reduced_problem_is_solving) in required_truth_problems:
            if not reduced_problem_is_solving:
                # Solve (if necessary)
                truth_problem.set_mu(mu)
                if not truth_problem_is_solving:
                    logger.log(
                        DEBUG, "Requiring truth problem solve for problem " +
                        truth_problem.name() + " (exact problem decorator: " +
                        str(hasattr(truth_problem, "__is_exact__")) + ")")
                    truth_problem.solve()
                else:
                    logger.log(
                        DEBUG,
                        "Loading current truth problem solution for problem " +
                        truth_problem.name() + " (exact problem decorator: " +
                        str(hasattr(truth_problem, "__is_exact__")) + ")")
            else:
                reduced_problem = get_reduced_problem_from_problem(
                    truth_problem)
                logger.log(
                    DEBUG,
                    "Replacing current truth problem solution with reduced solution for problem "
                    + reduced_problem.truth_problem.name())
            # Assign to reduced_mesh_solution
            if truth_problem in truth_problem_to_reduced_mesh_solution:
                for (reduced_mesh_solution, reduced_mesh_interpolator) in zip(
                        truth_problem_to_reduced_mesh_solution[truth_problem],
                        truth_problem_to_reduced_mesh_interpolator[0]
                    [truth_problem]):
                    solution_to = reduced_mesh_solution
                    if t is None:
                        if not reduced_problem_is_solving:
                            solution_from = reduced_mesh_interpolator(
                                truth_problem._solution)
                        else:
                            solution_from = reduced_mesh_interpolator(
                                reduced_problem.
                                basis_functions[:reduced_problem._solution.N] *
                                reduced_problem._solution)
                    else:
                        if not reduced_problem_is_solving:
                            if not truth_problem_is_solving:
                                solution_from = reduced_mesh_interpolator(
                                    truth_problem._solution_over_time.at(t))
                            else:
                                solution_from = reduced_mesh_interpolator(
                                    truth_problem._solution)
                        else:
                            solution_from = reduced_mesh_interpolator(
                                reduced_problem.
                                basis_functions[:reduced_problem._solution.N] *
                                reduced_problem._solution)
                    backend.assign(solution_to, solution_from)
            # Assign to reduced_mesh_solution_dot
            if truth_problem in truth_problem_to_reduced_mesh_solution_dot:
                for (reduced_mesh_solution_dot,
                     reduced_mesh_interpolator) in zip(
                         truth_problem_to_reduced_mesh_solution_dot[
                             truth_problem],
                         truth_problem_to_reduced_mesh_interpolator[1]
                         [truth_problem]):
                    solution_dot_to = reduced_mesh_solution_dot
                    assert t is not None
                    if not reduced_problem_is_solving:
                        if not truth_problem_is_solving:
                            solution_dot_from = reduced_mesh_interpolator(
                                truth_problem._solution_dot_over_time.at(t))
                        else:
                            solution_dot_from = reduced_mesh_interpolator(
                                truth_problem._solution_dot)
                    else:
                        solution_dot_from = reduced_mesh_interpolator(
                            reduced_problem.basis_functions[:reduced_problem.
                                                            _solution_dot.N] *
                            reduced_problem._solution_dot)
                    backend.assign(solution_dot_to, solution_dot_from)

        # Solve reduced problems associated to nonlinear terms
        for (reduced_problem, is_solving) in required_reduced_problems:
            # Solve (if necessary)
            reduced_problem.set_mu(mu)
            if not is_solving:
                logger.log(
                    DEBUG, "Requiring reduced problem solve for problem " +
                    reduced_problem.truth_problem.name())
                reduced_problem.solve()
            else:
                logger.log(
                    DEBUG,
                    "Loading current reduced problem solution for problem " +
                    reduced_problem.truth_problem.name())
            # Assign to reduced_mesh_solution
            if reduced_problem in reduced_problem_to_reduced_mesh_solution:
                for (reduced_mesh_solution, reduced_basis_functions) in zip(
                        reduced_problem_to_reduced_mesh_solution[
                            reduced_problem],
                        reduced_problem_to_reduced_basis_functions[0]
                    [reduced_problem]):
                    solution_to = reduced_mesh_solution
                    solution_from_N = OnlineSizeDict()
                    for c, v in reduced_problem._solution.N.items():
                        if c in reduced_basis_functions._components_name:
                            solution_from_N[c] = v
                    solution_from = online_backend.OnlineFunction(
                        solution_from_N)
                    if t is None or is_solving:
                        online_backend.online_assign(solution_from,
                                                     reduced_problem._solution)
                    else:
                        online_backend.online_assign(
                            solution_from,
                            reduced_problem._solution_over_time.at(t))
                    solution_from = reduced_basis_functions[:solution_from_N] * solution_from
                    backend.assign(solution_to, solution_from)
            # Assign to reduced_mesh_solution_dot
            if reduced_problem in reduced_problem_to_reduced_mesh_solution_dot:
                for (reduced_mesh_solution_dot,
                     reduced_basis_functions) in zip(
                         reduced_problem_to_reduced_mesh_solution_dot[
                             reduced_problem],
                         reduced_problem_to_reduced_basis_functions[1]
                         [reduced_problem]):
                    solution_dot_to = reduced_mesh_solution_dot
                    solution_dot_from_N = OnlineSizeDict()
                    for c, v in reduced_problem._solution_dot.N.items():
                        if c in reduced_basis_functions._components_name:
                            solution_dot_from_N[c] = v
                    solution_dot_from = online_backend.OnlineFunction(
                        solution_dot_from_N)
                    assert t is not None
                    if is_solving:
                        online_backend.online_assign(
                            solution_dot_from, reduced_problem._solution_dot)
                    else:
                        online_backend.online_assign(
                            solution_dot_from,
                            reduced_problem._solution_dot_over_time.at(t))
                    solution_dot_from = reduced_basis_functions[:
                                                                solution_dot_from_N] * solution_dot_from
                    backend.assign(solution_dot_to, solution_dot_from)

        # Assemble and return
        assembled_replaced_form = wrapping.assemble(
            replaced_form_with_replaced_measures)
        if not isinstance(assembled_replaced_form, Number):
            form_rank = assembled_replaced_form.rank()
        else:
            form_rank = 0
        return (assembled_replaced_form, form_rank)
    def _basic_form_on_truth_function_space(form_wrapper, tensor=None):
        form = form_wrapper._form
        form_name = form_wrapper.name()
        form_problem = get_problem_from_parametrized_operator(form_wrapper)
        mu = form_problem.mu
        if hasattr(form_problem, "set_time"):
            t = form_problem.t
        else:
            t = None

        if form_name not in reduced_problem_to_truth_solution_cache:
            visited = set()
            truth_problems = list()
            truth_problem_to_components = {  # outer dict index over time derivative
                0: dict(),
                1: dict()
            }
            truth_problem_to_exact_truth_problem = dict()
            truth_problem_to_truth_solution = dict()
            truth_problem_to_truth_solution_copy = dict()
            truth_problem_to_truth_solution_dot = dict()
            truth_problem_to_truth_solution_dot_copy = dict()
            reduced_problem_to_components = {  # outer dict index over time derivative
                0: dict(),
                1: dict()
            }
            reduced_problem_to_truth_solution = dict()
            reduced_problem_to_truth_solution_copy = dict()
            reduced_problem_to_truth_solution_dot = dict()
            reduced_problem_to_truth_solution_dot_copy = dict()

            # Look for terminals on truth mesh
            for node in wrapping.form_iterator(form):
                if node in visited:
                    continue
                # ... problem solutions related to nonlinear terms
                elif wrapping.is_problem_solution_type(node):
                    node_is_problem_solution = wrapping.is_problem_solution(
                        node)
                    node_is_problem_solution_dot = wrapping.is_problem_solution_dot(
                        node)
                    if node_is_problem_solution or node_is_problem_solution_dot:
                        if node_is_problem_solution:
                            (preprocessed_node, component, truth_solution
                             ) = wrapping.solution_identify_component(node)
                            truth_problem = get_problem_from_solution(
                                truth_solution)
                            if truth_problem not in truth_problems:
                                truth_problems.append(truth_problem)
                            # Store the solution
                            if truth_problem not in truth_problem_to_truth_solution:
                                truth_problem_to_truth_solution[
                                    truth_problem] = truth_solution
                                truth_problem_to_truth_solution_copy[
                                    truth_problem] = backend.copy(
                                        truth_solution)
                            else:
                                assert truth_problem_to_truth_solution[
                                    truth_problem] is truth_solution
                                assert truth_problem in truth_problem_to_truth_solution_copy
                            # Time derivative key for components dict
                            time_derivative = 0
                        elif node_is_problem_solution_dot:
                            (preprocessed_node, component, truth_solution_dot
                             ) = wrapping.solution_dot_identify_component(node)
                            truth_problem = get_problem_from_solution_dot(
                                truth_solution_dot)
                            if truth_problem not in truth_problems:
                                truth_problems.append(truth_problem)
                            # Store the solution_dot
                            if truth_problem not in truth_problem_to_truth_solution_dot:
                                truth_problem_to_truth_solution_dot[
                                    truth_problem] = truth_solution_dot
                                truth_problem_to_truth_solution_dot_copy[
                                    truth_problem] = backend.copy(
                                        truth_solution_dot)
                            else:
                                assert truth_problem_to_truth_solution_dot[
                                    truth_problem] is truth_solution_dot
                                assert truth_problem in truth_problem_to_truth_solution_dot_copy
                            # Time derivative key for components dict
                            time_derivative = 1
                        # Store truth problem
                        if truth_problem not in truth_problems:
                            truth_problems.append(truth_problem)
                        # Store the component
                        if truth_problem not in truth_problem_to_components[
                                time_derivative]:
                            truth_problem_to_components[time_derivative][
                                truth_problem] = list()
                        if component not in truth_problem_to_components[
                                time_derivative][truth_problem]:
                            truth_problem_to_components[time_derivative][
                                truth_problem].append(component)
                    else:
                        (
                            preprocessed_node, _, _
                        ) = wrapping.get_auxiliary_problem_for_non_parametrized_function(
                            node)
                    # Make sure to skip any parent solution related to this one
                    visited.add(node)
                    visited.add(preprocessed_node)
                    for parent_node in wrapping.solution_iterator(
                            preprocessed_node):
                        visited.add(parent_node)
                else:
                    visited.add(node)

            # Cache the resulting dicts
            truth_problems_cache[form_name] = truth_problems
            truth_problem_to_components_cache[
                form_name] = truth_problem_to_components
            truth_problem_to_exact_truth_problem_cache[
                form_name] = truth_problem_to_exact_truth_problem
            truth_problem_to_truth_solution_cache[
                form_name] = truth_problem_to_truth_solution
            truth_problem_to_truth_solution_copy_cache[
                form_name] = truth_problem_to_truth_solution_copy
            truth_problem_to_truth_solution_dot_cache[
                form_name] = truth_problem_to_truth_solution_dot
            truth_problem_to_truth_solution_dot_copy_cache[
                form_name] = truth_problem_to_truth_solution_dot_copy
            reduced_problem_to_components_cache[
                form_name] = reduced_problem_to_components
            reduced_problem_to_truth_solution_cache[
                form_name] = reduced_problem_to_truth_solution
            reduced_problem_to_truth_solution_copy_cache[
                form_name] = reduced_problem_to_truth_solution_copy
            reduced_problem_to_truth_solution_dot_cache[
                form_name] = reduced_problem_to_truth_solution_dot
            reduced_problem_to_truth_solution_dot_copy_cache[
                form_name] = reduced_problem_to_truth_solution_dot_copy

        # Extract from cache
        truth_problems = truth_problems_cache[form_name]
        truth_problem_to_components = truth_problem_to_components_cache[
            form_name]
        truth_problem_to_exact_truth_problem = truth_problem_to_exact_truth_problem_cache[
            form_name]
        truth_problem_to_truth_solution = truth_problem_to_truth_solution_cache[
            form_name]
        truth_problem_to_truth_solution_copy = truth_problem_to_truth_solution_copy_cache[
            form_name]
        truth_problem_to_truth_solution_dot = truth_problem_to_truth_solution_dot_cache[
            form_name]
        truth_problem_to_truth_solution_dot_copy = truth_problem_to_truth_solution_dot_copy_cache[
            form_name]
        reduced_problem_to_components = reduced_problem_to_components_cache[
            form_name]
        reduced_problem_to_truth_solution = reduced_problem_to_truth_solution_cache[
            form_name]
        reduced_problem_to_truth_solution_copy = reduced_problem_to_truth_solution_copy_cache[
            form_name]
        reduced_problem_to_truth_solution_dot = reduced_problem_to_truth_solution_dot_cache[
            form_name]
        reduced_problem_to_truth_solution_dot_copy = reduced_problem_to_truth_solution_dot_copy_cache[
            form_name]

        # Get list of truth and reduced problems that need to be solved, possibly updating cache
        required_truth_problems = list()
        required_reduced_problems = list()
        for truth_problem in truth_problems:
            truth_problem_is_solving = hasattr(truth_problem, "_is_solving")
            if is_training_started(truth_problem):
                reduced_problem = get_reduced_problem_from_problem(
                    truth_problem)
                reduced_problem_is_solving = hasattr(reduced_problem,
                                                     "_is_solving")
            else:
                reduced_problem = None
                reduced_problem_is_solving = False
            if not truth_problem_is_solving:
                if is_training_finished(truth_problem):
                    # Store the solution
                    if (reduced_problem
                            not in reduced_problem_to_truth_solution and
                            truth_problem in truth_problem_to_truth_solution):
                        reduced_problem_to_truth_solution[
                            reduced_problem] = truth_problem_to_truth_solution[
                                truth_problem]
                        assert reduced_problem not in reduced_problem_to_truth_solution_copy
                        assert truth_problem in truth_problem_to_truth_solution_copy
                        reduced_problem_to_truth_solution_copy[
                            reduced_problem] = truth_problem_to_truth_solution_copy[
                                truth_problem]
                        # Store the component
                        assert reduced_problem not in reduced_problem_to_components
                        assert truth_problem in truth_problem_to_components[0]
                        reduced_problem_to_components[0][
                            reduced_problem] = truth_problem_to_components[0][
                                truth_problem]
                    # Store the solution_dot
                    if (reduced_problem
                            not in reduced_problem_to_truth_solution_dot
                            and truth_problem
                            in truth_problem_to_truth_solution_dot):
                        reduced_problem_to_truth_solution_dot[
                            reduced_problem] = truth_problem_to_truth_solution_dot[
                                truth_problem]
                        assert reduced_problem not in reduced_problem_to_truth_solution_dot_copy
                        assert truth_problem in truth_problem_to_truth_solution_dot_copy
                        reduced_problem_to_truth_solution_dot_copy[
                            reduced_problem] = truth_problem_to_truth_solution_dot_copy[
                                truth_problem]
                        # Store the component
                        assert reduced_problem not in reduced_problem_to_components
                        assert truth_problem in truth_problem_to_components[1]
                        reduced_problem_to_components[1][
                            reduced_problem] = truth_problem_to_components[1][
                                truth_problem]
                    # Append to list of required reduced problems
                    required_reduced_problems.append(
                        (reduced_problem, reduced_problem_is_solving))
                else:
                    if (hasattr(truth_problem,
                                "_apply_exact_evaluation_at_stages") and
                            not hasattr(truth_problem, "_apply_EIM_at_stages")
                            and not hasattr(truth_problem,
                                            "_apply_DEIM_at_stages")):
                        # Init truth problem (if required), as it may not have been initialized
                        truth_problem.init()
                        # Append to list of required truth problems which are not currently solving
                        required_truth_problems.append(
                            (truth_problem, False, reduced_problem_is_solving))
                    else:
                        # Store the corresponding exact truth problem
                        if truth_problem not in truth_problem_to_exact_truth_problem:
                            exact_truth_problem = exact_problem(truth_problem)
                            truth_problem_to_exact_truth_problem[
                                truth_problem] = exact_truth_problem
                            # Init exact truth problem (if required), as it may not have been initialized
                            exact_truth_problem.init()
                        else:
                            exact_truth_problem = truth_problem_to_exact_truth_problem[
                                truth_problem]
                        # Store the solution
                        if (exact_truth_problem
                                not in truth_problem_to_truth_solution
                                and truth_problem
                                in truth_problem_to_truth_solution):
                            truth_problem_to_truth_solution[
                                exact_truth_problem] = truth_problem_to_truth_solution[
                                    truth_problem]
                            assert exact_truth_problem not in truth_problem_to_truth_solution_copy
                            assert truth_problem in truth_problem_to_truth_solution_copy
                            truth_problem_to_truth_solution_copy[
                                exact_truth_problem] = truth_problem_to_truth_solution_copy[
                                    truth_problem]
                            # Store the component
                            assert exact_truth_problem not in truth_problem_to_components[
                                0]
                            assert truth_problem in truth_problem_to_components[
                                0]
                            truth_problem_to_components[0][
                                exact_truth_problem] = truth_problem_to_components[
                                    0][truth_problem]
                        # Store the solution_dot
                        if (exact_truth_problem
                                not in truth_problem_to_truth_solution_dot
                                and truth_problem
                                in truth_problem_to_truth_solution_dot):
                            truth_problem_to_truth_solution_dot[
                                exact_truth_problem] = truth_problem_to_truth_solution_dot[
                                    truth_problem]
                            assert exact_truth_problem not in truth_problem_to_truth_solution_dot_copy
                            assert truth_problem in truth_problem_to_truth_solution_dot_copy
                            truth_problem_to_truth_solution_dot_copy[
                                exact_truth_problem] = truth_problem_to_truth_solution_dot_copy[
                                    truth_problem]
                            # Store the component
                            assert exact_truth_problem not in truth_problem_to_components[
                                1]
                            assert truth_problem in truth_problem_to_components[
                                1]
                            truth_problem_to_components[1][
                                exact_truth_problem] = truth_problem_to_components[
                                    1][truth_problem]
                        # Append to list of required truth problems which are not currently solving
                        required_truth_problems.append(
                            (exact_truth_problem, False,
                             reduced_problem_is_solving))
            else:
                assert not reduced_problem_is_solving
                # Append to list of required truth problems which are currently solving
                required_truth_problems.append((truth_problem, True, False))

        # Solve truth problems (which have not been reduced yet) associated to nonlinear terms
        for (truth_problem, truth_problem_is_solving,
             reduced_problem_is_solving) in required_truth_problems:
            if not reduced_problem_is_solving:
                # Solve (if necessary)
                truth_problem.set_mu(mu)
                if not truth_problem_is_solving:
                    log(
                        PROGRESS,
                        "In form_on_truth_function_space, requiring truth problem solve for problem "
                        + truth_problem.name())
                    truth_problem.solve()
                else:
                    log(
                        PROGRESS,
                        "In form_on_truth_function_space, loading current truth problem solution for problem "
                        + truth_problem.name())
            else:
                reduced_problem = get_reduced_problem_from_problem(
                    truth_problem)
                log(
                    PROGRESS,
                    "In form_on_truth_function_space, replacing current truth problem solution with reduced solution for problem "
                    + reduced_problem.truth_problem.name())
            # Assign to truth_solution
            if truth_problem in truth_problem_to_truth_solution:
                truth_solution = truth_problem_to_truth_solution[truth_problem]
                backend.assign(
                    truth_problem_to_truth_solution_copy[truth_problem],
                    truth_solution)
                for component in truth_problem_to_components[0][truth_problem]:
                    solution_to = _sub_from_tuple(truth_solution, component)
                    if t is None:
                        if not reduced_problem_is_solving:
                            solution_from = _sub_from_tuple(
                                truth_problem._solution, component)
                        else:
                            solution_from = _sub_from_tuple(
                                reduced_problem.
                                basis_functions[:reduced_problem._solution.N] *
                                reduced_problem._solution, component)
                    else:
                        if not reduced_problem_is_solving:
                            if not truth_problem_is_solving:
                                solution_from = _sub_from_tuple(
                                    truth_problem._solution_over_time.at(t),
                                    component)
                            else:
                                solution_from = _sub_from_tuple(
                                    truth_problem._solution, component)
                        else:
                            solution_from = _sub_from_tuple(
                                reduced_problem.
                                basis_functions[:reduced_problem._solution.N] *
                                reduced_problem._solution, component)
                    backend.assign(solution_to, solution_from)
            # Assign to truth_solution_dot
            if truth_problem in truth_problem_to_truth_solution_dot:
                truth_solution_dot = truth_problem_to_truth_solution_dot[
                    truth_problem]
                backend.assign(
                    truth_problem_to_truth_solution_dot_copy[truth_problem],
                    truth_solution_dot)
                for component in truth_problem_to_components[1][truth_problem]:
                    solution_dot_to = _sub_from_tuple(truth_solution_dot,
                                                      component)
                    assert t is not None
                    if not reduced_problem_is_solving:
                        if not truth_problem_is_solving:
                            solution_dot_from = _sub_from_tuple(
                                truth_problem._solution_dot_over_time.at(t),
                                component)
                        else:
                            solution_dot_from = _sub_from_tuple(
                                truth_problem._solution_dot, component)
                    else:
                        solution_dot_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem.
                                                            _solution_dot.N] *
                            reduced_problem._solution_dot, component)
                    backend.assign(solution_dot_to, solution_dot_from)

        # Solve reduced problems associated to nonlinear terms
        for (reduced_problem, is_solving) in required_reduced_problems:
            # Solve (if necessary)
            reduced_problem.set_mu(mu)
            if not is_solving:
                log(
                    PROGRESS,
                    "In form_on_truth_function_space, requiring reduced problem solve for problem "
                    + reduced_problem.truth_problem.name())
                reduced_problem.solve()
            else:
                log(
                    PROGRESS,
                    "In form_on_truth_function_space, loading current reduced problem solution for problem "
                    + reduced_problem.truth_problem.name())
            # Assign to truth_solution
            if reduced_problem in reduced_problem_to_truth_solution:
                truth_solution = reduced_problem_to_truth_solution[
                    reduced_problem]
                backend.assign(
                    reduced_problem_to_truth_solution_copy[reduced_problem],
                    truth_solution)
                for component in reduced_problem_to_components[0][
                        reduced_problem]:
                    solution_to = _sub_from_tuple(truth_solution, component)
                    if t is None or is_solving:
                        solution_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem.
                                                            _solution.N] *
                            reduced_problem._solution, component)
                    else:
                        solution_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem.
                                                            _solution.N] *
                            reduced_problem._solution_over_time.at(t),
                            component)
                    backend.assign(solution_to, solution_from)
            # Assign to truth_solution_dot
            if reduced_problem in reduced_problem_to_truth_solution_dot:
                truth_solution_dot = reduced_problem_to_truth_solution_dot[
                    reduced_problem]
                backend.assign(
                    reduced_problem_to_truth_solution_dot_copy[
                        reduced_problem], truth_solution_dot)
                for component in reduced_problem_to_components[1][
                        reduced_problem]:
                    solution_dot_to = _sub_from_tuple(truth_solution_dot,
                                                      component)
                    assert t is not None
                    if is_solving:
                        solution_dot_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem.
                                                            _solution_dot.N] *
                            reduced_problem._solution_dot, component)
                    else:
                        solution_dot_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem.
                                                            _solution_dot.N] *
                            reduced_problem._solution_dot_over_time.at(t),
                            component)
                    backend.assign(solution_dot_to, solution_dot_from)

        # Assemble
        assembled_form = wrapping.assemble(form, tensor)
        if not isinstance(assembled_form, Number):
            assembled_form.generator = form_wrapper  # for I/O
            form_rank = assembled_form.rank()
        else:
            form_rank = 0

        # Undo any side effect of truth problem solves
        for (truth_problem, _, _) in required_truth_problems:
            if truth_problem in truth_problem_to_truth_solution:
                truth_solution = truth_problem_to_truth_solution[truth_problem]
                truth_solution_copy = truth_problem_to_truth_solution_copy[
                    truth_problem]
                for component in truth_problem_to_components[0][truth_problem]:
                    solution_to = _sub_from_tuple(truth_solution, component)
                    solution_from = _sub_from_tuple(truth_solution_copy,
                                                    component)
                    backend.assign(solution_to, solution_from)
            if truth_problem in truth_problem_to_truth_solution_dot:
                truth_solution_dot = truth_problem_to_truth_solution_dot[
                    truth_problem]
                truth_solution_dot_copy = truth_problem_to_truth_solution_dot_copy[
                    truth_problem]
                for component in truth_problem_to_components[1][truth_problem]:
                    solution_dot_to = _sub_from_tuple(truth_solution_dot,
                                                      component)
                    solution_dot_from = _sub_from_tuple(
                        truth_solution_dot_copy, component)
                    backend.assign(solution_dot_to, solution_dot_from)

        # Undo any side effect of reduced problem solves
        for (reduced_problem, _) in required_reduced_problems:
            if reduced_problem in reduced_problem_to_truth_solution:
                truth_solution = reduced_problem_to_truth_solution[
                    reduced_problem]
                truth_solution_copy = reduced_problem_to_truth_solution_copy[
                    reduced_problem]
                for component in reduced_problem_to_components[0][
                        reduced_problem]:
                    solution_to = _sub_from_tuple(truth_solution, component)
                    solution_from = _sub_from_tuple(truth_solution_copy,
                                                    component)
                    backend.assign(solution_to, solution_from)
            if reduced_problem in reduced_problem_to_truth_solution_dot:
                truth_solution_dot = reduced_problem_to_truth_solution_dot[
                    reduced_problem]
                truth_solution_dot_copy = reduced_problem_to_truth_solution_dot_copy[
                    reduced_problem]
                for component in reduced_problem_to_components[1][
                        reduced_problem]:
                    solution_dot_to = _sub_from_tuple(truth_solution_dot,
                                                      component)
                    solution_dot_from = _sub_from_tuple(
                        truth_solution_dot_copy, component)
                    backend.assign(solution_dot_to, solution_dot_from)

        # Return
        return (assembled_form, form_rank)
    def _basic_expression_on_truth_mesh(expression_wrapper, function=None):
        expression = expression_wrapper._expression
        expression_name = expression_wrapper.name()
        expression_problem = get_problem_from_parametrized_expression(expression_wrapper)
        space = expression_wrapper._space
        mu = expression_problem.mu
        if hasattr(expression_problem, "set_time"):
            t = expression_problem.t
        else:
            t = None

        if expression_name not in reduced_problem_to_truth_solution_cache:
            visited = set()
            truth_problems = list()
            truth_problem_to_components = {  # outer dict index over time derivative
                0: dict(),
                1: dict()}
            truth_problem_to_exact_truth_problem = dict()
            truth_problem_to_truth_solution = dict()
            truth_problem_to_truth_solution_copy = dict()
            truth_problem_to_truth_solution_dot = dict()
            truth_problem_to_truth_solution_dot_copy = dict()
            reduced_problem_to_components = {  # outer dict index over time derivative
                0: dict(),
                1: dict()}
            reduced_problem_to_truth_solution = dict()
            reduced_problem_to_truth_solution_copy = dict()
            reduced_problem_to_truth_solution_dot = dict()
            reduced_problem_to_truth_solution_dot_copy = dict()

            # Look for terminals on truth mesh
            logger.log(DEBUG, "Traversing terminals of expression " + expression_name)
            for node in wrapping.expression_iterator(expression):
                if node in visited:
                    continue
                # ... problem solutions related to nonlinear terms
                elif wrapping.is_problem_solution_type(node):
                    node_is_problem_solution = wrapping.is_problem_solution(node)
                    node_is_problem_solution_dot = wrapping.is_problem_solution_dot(node)
                    if node_is_problem_solution or node_is_problem_solution_dot:
                        if node_is_problem_solution:
                            (preprocessed_node, component, truth_solution) = wrapping.solution_identify_component(node)
                            truth_problem = get_problem_from_solution(truth_solution)
                            logger.log(DEBUG, "\tFound problem solution of truth problem " + truth_problem.name()
                                       + " (exact problem decorator: " + str(hasattr(truth_problem, "__is_exact__"))
                                       + ", component: " + str(component) + ")")
                            # Store the solution
                            if truth_problem not in truth_problem_to_truth_solution:
                                truth_problem_to_truth_solution[truth_problem] = truth_solution
                                truth_problem_to_truth_solution_copy[truth_problem] = backend.copy(truth_solution)
                            else:
                                assert truth_problem_to_truth_solution[truth_problem] is truth_solution
                                assert truth_problem in truth_problem_to_truth_solution_copy
                            # Time derivative key for components dict
                            time_derivative = 0
                        elif node_is_problem_solution_dot:
                            (preprocessed_node, component,
                             truth_solution_dot) = wrapping.solution_dot_identify_component(node)
                            truth_problem = get_problem_from_solution_dot(truth_solution_dot)
                            logger.log(DEBUG, "\tFound problem solution dot of truth problem " + truth_problem.name()
                                       + " (exact problem decorator: " + str(hasattr(truth_problem, "__is_exact__"))
                                       + ", component: " + str(component) + ")")
                            # Store the solution_dot
                            if truth_problem not in truth_problem_to_truth_solution_dot:
                                truth_problem_to_truth_solution_dot[truth_problem] = truth_solution_dot
                                truth_problem_to_truth_solution_dot_copy[
                                    truth_problem] = backend.copy(truth_solution_dot)
                            else:
                                assert truth_problem_to_truth_solution_dot[truth_problem] is truth_solution_dot
                                assert truth_problem in truth_problem_to_truth_solution_dot_copy
                            # Time derivative key for components dict
                            time_derivative = 1
                        # Store truth problem
                        if truth_problem not in truth_problems:
                            truth_problems.append(truth_problem)
                        # Store the component
                        if truth_problem not in truth_problem_to_components[time_derivative]:
                            truth_problem_to_components[time_derivative][truth_problem] = list()
                        if component not in truth_problem_to_components[time_derivative][truth_problem]:
                            truth_problem_to_components[time_derivative][truth_problem].append(component)
                    else:
                        (preprocessed_node, component,
                         auxiliary_problem) = wrapping.get_auxiliary_problem_for_non_parametrized_function(node)
                        logger.log(DEBUG, "\tFound non parametrized function " + str(preprocessed_node)
                                   + " associated to auxiliary problem " + str(auxiliary_problem.name())
                                   + ", component: " + str(component))
                    # Make sure to skip any parent solution related to this one
                    visited.add(node)
                    visited.add(preprocessed_node)
                    for parent_node in wrapping.solution_iterator(preprocessed_node):
                        visited.add(parent_node)
                else:
                    visited.add(node)

            # Cache the resulting dicts
            truth_problems_cache[expression_name] = truth_problems
            truth_problem_to_components_cache[expression_name] = truth_problem_to_components
            truth_problem_to_exact_truth_problem_cache[expression_name] = truth_problem_to_exact_truth_problem
            truth_problem_to_truth_solution_cache[expression_name] = truth_problem_to_truth_solution
            truth_problem_to_truth_solution_copy_cache[expression_name] = truth_problem_to_truth_solution_copy
            truth_problem_to_truth_solution_dot_cache[expression_name] = truth_problem_to_truth_solution_dot
            truth_problem_to_truth_solution_dot_copy_cache[expression_name] = truth_problem_to_truth_solution_dot_copy
            reduced_problem_to_components_cache[expression_name] = reduced_problem_to_components
            reduced_problem_to_truth_solution_cache[expression_name] = reduced_problem_to_truth_solution
            reduced_problem_to_truth_solution_copy_cache[expression_name] = reduced_problem_to_truth_solution_copy
            reduced_problem_to_truth_solution_dot_cache[expression_name] = reduced_problem_to_truth_solution_dot
            reduced_problem_to_truth_solution_dot_copy_cache[
                expression_name] = reduced_problem_to_truth_solution_dot_copy

        # Extract from cache
        truth_problems = truth_problems_cache[expression_name]
        truth_problem_to_components = truth_problem_to_components_cache[expression_name]
        truth_problem_to_exact_truth_problem = truth_problem_to_exact_truth_problem_cache[expression_name]
        truth_problem_to_truth_solution = truth_problem_to_truth_solution_cache[expression_name]
        truth_problem_to_truth_solution_copy = truth_problem_to_truth_solution_copy_cache[expression_name]
        truth_problem_to_truth_solution_dot = truth_problem_to_truth_solution_dot_cache[expression_name]
        truth_problem_to_truth_solution_dot_copy = truth_problem_to_truth_solution_dot_copy_cache[expression_name]
        reduced_problem_to_components = reduced_problem_to_components_cache[expression_name]
        reduced_problem_to_truth_solution = reduced_problem_to_truth_solution_cache[expression_name]
        reduced_problem_to_truth_solution_copy = reduced_problem_to_truth_solution_copy_cache[expression_name]
        reduced_problem_to_truth_solution_dot = reduced_problem_to_truth_solution_dot_cache[expression_name]
        reduced_problem_to_truth_solution_dot_copy = reduced_problem_to_truth_solution_dot_cache[expression_name]

        # Get list of truth and reduced problems that need to be solved, possibly updating cache
        required_truth_problems = list()
        required_reduced_problems = list()
        for truth_problem in truth_problems:
            truth_problem_is_solving = hasattr(truth_problem, "_is_solving")
            if is_training_started(truth_problem):
                reduced_problem = get_reduced_problem_from_problem(truth_problem)
                reduced_problem_is_solving = hasattr(reduced_problem, "_is_solving")
            else:
                reduced_problem = None
                reduced_problem_is_solving = False
            if not truth_problem_is_solving:
                if is_training_finished(truth_problem):
                    logger.log(DEBUG, "Truth problem " + truth_problem.name()
                               + " (exact problem decorator: " + str(hasattr(truth_problem, "__is_exact__"))
                               + ") is not currently solving, and its offline stage has finished:"
                               + " truth problem will be replaced by reduced problem")
                    # Store the solution
                    if (reduced_problem not in reduced_problem_to_truth_solution
                            and truth_problem in truth_problem_to_truth_solution):
                        reduced_problem_to_truth_solution[
                            reduced_problem] = truth_problem_to_truth_solution[truth_problem]
                        assert reduced_problem not in reduced_problem_to_truth_solution_copy
                        assert truth_problem in truth_problem_to_truth_solution_copy
                        reduced_problem_to_truth_solution_copy[
                            reduced_problem] = truth_problem_to_truth_solution_copy[truth_problem]
                        # Store the component
                        assert reduced_problem not in reduced_problem_to_components[0]
                        assert truth_problem in truth_problem_to_components[0]
                        reduced_problem_to_components[
                            0][reduced_problem] = truth_problem_to_components[0][truth_problem]
                    # Store the solution_dot
                    if (reduced_problem not in reduced_problem_to_truth_solution_dot
                            and truth_problem in truth_problem_to_truth_solution_dot):
                        reduced_problem_to_truth_solution_dot[
                            reduced_problem] = truth_problem_to_truth_solution_dot[truth_problem]
                        assert reduced_problem not in reduced_problem_to_truth_solution_dot_copy
                        assert truth_problem in truth_problem_to_truth_solution_dot_copy
                        reduced_problem_to_truth_solution_dot_copy[
                            reduced_problem] = truth_problem_to_truth_solution_dot_copy[truth_problem]
                        # Store the component
                        assert reduced_problem not in reduced_problem_to_components[1]
                        assert truth_problem in truth_problem_to_components[1]
                        reduced_problem_to_components[
                            1][reduced_problem] = truth_problem_to_components[1][truth_problem]
                    # Append to list of required reduced problems
                    required_reduced_problems.append((reduced_problem, reduced_problem_is_solving))
                else:
                    if (hasattr(truth_problem, "_apply_exact_evaluation_at_stages")
                            and not hasattr(truth_problem, "_apply_EIM_at_stages")
                            and not hasattr(truth_problem, "_apply_DEIM_at_stages")):
                        logger.log(DEBUG, "Truth problem " + truth_problem.name()
                                   + " (exact problem decorator: " + str(hasattr(truth_problem, "__is_exact__"))
                                   + ") is not currently solving, its offline stage has not finished,"
                                   + " and only @ExactParametrizedFunctions has been used:"
                                   + " truth solve of this truth problem instance will be called")
                        # Init truth problem (if required), as it may not have been initialized
                        truth_problem.init()
                        # Append to list of required truth problems which are not currently solving
                        required_truth_problems.append((truth_problem, False, reduced_problem_is_solving))
                    else:
                        logger.log(DEBUG, "Truth problem " + truth_problem.name()
                                   + " (exact problem decorator: " + str(hasattr(truth_problem, "__is_exact__"))
                                   + ") is not currently solving, its offline stage has not finished,"
                                   + " and either @ExactParametrizedFunctions has not been used"
                                   + " or it has been used in combination with @DEIM or @EIM:"
                                   + " truth solve on an auxiliary instance (with exact problem decorator)"
                                   + " will be called, to prevent early initialization of DEIM/EIM data structures")
                        # Store the corresponding exact truth problem
                        if truth_problem not in truth_problem_to_exact_truth_problem:
                            exact_truth_problem = exact_problem(truth_problem)
                            truth_problem_to_exact_truth_problem[truth_problem] = exact_truth_problem
                            # Init exact truth problem (if required), as it may not have been initialized
                            exact_truth_problem.init()
                        else:
                            exact_truth_problem = truth_problem_to_exact_truth_problem[truth_problem]
                        # Store the solution
                        if (exact_truth_problem not in truth_problem_to_truth_solution
                                and truth_problem in truth_problem_to_truth_solution):
                            truth_problem_to_truth_solution[
                                exact_truth_problem] = truth_problem_to_truth_solution[truth_problem]
                            assert exact_truth_problem not in truth_problem_to_truth_solution_copy
                            assert truth_problem in truth_problem_to_truth_solution_copy
                            truth_problem_to_truth_solution_copy[
                                exact_truth_problem] = truth_problem_to_truth_solution_copy[truth_problem]
                            # Store the component
                            assert exact_truth_problem not in truth_problem_to_components[0]
                            assert truth_problem in truth_problem_to_components[0]
                            truth_problem_to_components[
                                0][exact_truth_problem] = truth_problem_to_components[0][truth_problem]
                        # Store the solution_dot
                        if (exact_truth_problem not in truth_problem_to_truth_solution_dot
                                and truth_problem in truth_problem_to_truth_solution_dot):
                            truth_problem_to_truth_solution_dot[
                                exact_truth_problem] = truth_problem_to_truth_solution_dot[truth_problem]
                            assert exact_truth_problem not in truth_problem_to_truth_solution_dot_copy
                            assert truth_problem in truth_problem_to_truth_solution_dot_copy
                            truth_problem_to_truth_solution_dot_copy[
                                exact_truth_problem] = truth_problem_to_truth_solution_dot_copy[truth_problem]
                            # Store the component
                            assert exact_truth_problem not in truth_problem_to_components[1]
                            assert truth_problem in truth_problem_to_components[1]
                            truth_problem_to_components[
                                1][exact_truth_problem] = truth_problem_to_components[1][truth_problem]
                        # Append to list of required truth problems which are not currently solving
                        required_truth_problems.append((exact_truth_problem, False, reduced_problem_is_solving))
            else:
                logger.log(DEBUG, "Truth problem " + truth_problem.name()
                           + " (exact problem decorator: " + str(hasattr(truth_problem, "__is_exact__"))
                           + ") is currently solving: current truth solution will be loaded")
                assert not reduced_problem_is_solving
                # Append to list of required truth problems which are currently solving
                required_truth_problems.append((truth_problem, True, False))

        # Solve truth problems (which have not been reduced yet) associated to nonlinear terms
        for (truth_problem, truth_problem_is_solving, reduced_problem_is_solving) in required_truth_problems:
            if not reduced_problem_is_solving:
                # Solve (if necessary)
                truth_problem.set_mu(mu)
                if not truth_problem_is_solving:
                    logger.log(DEBUG, "Requiring truth problem solve for problem " + truth_problem.name()
                               + " (exact problem decorator: " + str(hasattr(truth_problem, "__is_exact__")) + ")")
                    truth_problem.solve()
                else:
                    logger.log(DEBUG, "Loading current truth problem solution for problem " + truth_problem.name()
                               + " (exact problem decorator: " + str(hasattr(truth_problem, "__is_exact__")) + ")")
            else:
                reduced_problem = get_reduced_problem_from_problem(truth_problem)
                logger.log(DEBUG, "Replacing current truth problem solution with reduced solution for problem "
                           + reduced_problem.truth_problem.name())
            # Assign to truth_solution
            if truth_problem in truth_problem_to_truth_solution:
                truth_solution = truth_problem_to_truth_solution[truth_problem]
                backend.assign(truth_problem_to_truth_solution_copy[truth_problem], truth_solution)
                for component in truth_problem_to_components[0][truth_problem]:
                    solution_to = _sub_from_tuple(truth_solution, component)
                    if t is None:
                        if not reduced_problem_is_solving:
                            solution_from = _sub_from_tuple(truth_problem._solution, component)
                        else:
                            solution_from = _sub_from_tuple(
                                reduced_problem.basis_functions[:reduced_problem._solution.N]
                                * reduced_problem._solution, component)
                    else:
                        if not reduced_problem_is_solving:
                            if not truth_problem_is_solving:
                                solution_from = _sub_from_tuple(truth_problem._solution_over_time.at(t), component)
                            else:
                                solution_from = _sub_from_tuple(truth_problem._solution, component)
                        else:
                            solution_from = _sub_from_tuple(
                                reduced_problem.basis_functions[:reduced_problem._solution.N]
                                * reduced_problem._solution, component)
                    backend.assign(solution_to, solution_from)
            # Assign to truth_solution_dot
            if truth_problem in truth_problem_to_truth_solution_dot:
                truth_solution_dot = truth_problem_to_truth_solution_dot[truth_problem]
                backend.assign(truth_problem_to_truth_solution_dot_copy[truth_problem], truth_solution_dot)
                for component in truth_problem_to_components[1][truth_problem]:
                    solution_dot_to = _sub_from_tuple(truth_solution_dot, component)
                    assert t is not None
                    if not reduced_problem_is_solving:
                        if not truth_problem_is_solving:
                            solution_dot_from = _sub_from_tuple(truth_problem._solution_dot_over_time.at(t), component)
                        else:
                            solution_dot_from = _sub_from_tuple(truth_problem._solution_dot, component)
                    else:
                        solution_dot_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem._solution_dot.N]
                            * reduced_problem._solution_dot, component)
                    backend.assign(solution_dot_to, solution_dot_from)

        # Solve reduced problems associated to nonlinear terms
        for (reduced_problem, is_solving) in required_reduced_problems:
            # Solve (if necessary)
            reduced_problem.set_mu(mu)
            if not is_solving:
                logger.log(DEBUG, "Requiring reduced problem solve for problem "
                           + reduced_problem.truth_problem.name())
                reduced_problem.solve()
            else:
                logger.log(DEBUG, "Loading current reduced problem solution for problem "
                           + reduced_problem.truth_problem.name())
            # Assign to truth_solution
            if reduced_problem in reduced_problem_to_truth_solution:
                truth_solution = reduced_problem_to_truth_solution[reduced_problem]
                backend.assign(reduced_problem_to_truth_solution_copy[reduced_problem], truth_solution)
                for component in reduced_problem_to_components[0][reduced_problem]:
                    solution_to = _sub_from_tuple(truth_solution, component)
                    if t is None or is_solving:
                        solution_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem._solution.N]
                            * reduced_problem._solution, component)
                    else:
                        solution_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem._solution.N]
                            * reduced_problem._solution_over_time.at(t), component)
                    backend.assign(solution_to, solution_from)
            # Assign to truth_solution_dot
            if reduced_problem in reduced_problem_to_truth_solution_dot:
                truth_solution_dot = reduced_problem_to_truth_solution_dot[reduced_problem]
                backend.assign(reduced_problem_to_truth_solution_dot_copy[reduced_problem], truth_solution_dot)
                for component in reduced_problem_to_components[1][reduced_problem]:
                    solution_dot_to = _sub_from_tuple(truth_solution_dot, component)
                    assert t is not None
                    if is_solving:
                        solution_dot_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem._solution_dot.N]
                            * reduced_problem._solution_dot, component)
                    else:
                        solution_dot_from = _sub_from_tuple(
                            reduced_problem.basis_functions[:reduced_problem._solution_dot.N]
                            * reduced_problem._solution_dot_over_time.at(t), component)
                    backend.assign(solution_dot_to, solution_dot_from)

        # Evaluate
        if function is None:
            function = backend.Function(space)
        wrapping.evaluate_expression(expression, function)

        # Undo any side effect of truth problem solves
        for (truth_problem, _, _) in required_truth_problems:
            if truth_problem in truth_problem_to_truth_solution:
                truth_solution = truth_problem_to_truth_solution[truth_problem]
                truth_solution_copy = truth_problem_to_truth_solution_copy[truth_problem]
                for component in truth_problem_to_components[0][truth_problem]:
                    solution_to = _sub_from_tuple(truth_solution, component)
                    solution_from = _sub_from_tuple(truth_solution_copy, component)
                    backend.assign(solution_to, solution_from)
            if truth_problem in truth_problem_to_truth_solution_dot:
                truth_solution_dot = truth_problem_to_truth_solution_dot[truth_problem]
                truth_solution_dot_copy = truth_problem_to_truth_solution_dot_copy[truth_problem]
                for component in truth_problem_to_components[1][truth_problem]:
                    solution_dot_to = _sub_from_tuple(truth_solution_dot, component)
                    solution_dot_from = _sub_from_tuple(truth_solution_dot_copy, component)
                    backend.assign(solution_dot_to, solution_dot_from)

        # Undo any side effect of reduced problem solves
        for (reduced_problem, _) in required_reduced_problems:
            if reduced_problem in reduced_problem_to_truth_solution:
                truth_solution = reduced_problem_to_truth_solution[reduced_problem]
                truth_solution_copy = reduced_problem_to_truth_solution_copy[reduced_problem]
                for component in reduced_problem_to_components[0][reduced_problem]:
                    solution_to = _sub_from_tuple(truth_solution, component)
                    solution_from = _sub_from_tuple(truth_solution_copy, component)
                    backend.assign(solution_to, solution_from)
            if reduced_problem in reduced_problem_to_truth_solution_dot:
                truth_solution_dot = reduced_problem_to_truth_solution_dot[reduced_problem]
                truth_solution_dot_copy = reduced_problem_to_truth_solution_dot_copy[reduced_problem]
                for component in reduced_problem_to_components[1][reduced_problem]:
                    solution_dot_to = _sub_from_tuple(truth_solution_dot, component)
                    solution_dot_from = _sub_from_tuple(truth_solution_dot_copy, component)
                    backend.assign(solution_dot_to, solution_dot_from)

        # Return
        return function
 def _basic_expression_on_reduced_mesh(expression_wrapper, at):
     expression = expression_wrapper._expression
     expression_name = expression_wrapper.name()
     expression_problem = get_problem_from_parametrized_expression(expression_wrapper)
     reduced_space = at.get_reduced_function_space()
     reduced_mesh = at.get_reduced_mesh()
     mu = expression_problem.mu
     if hasattr(expression_problem, "set_time"):
         t = expression_problem.t
     else:
         t = None
     
     if (expression_name, reduced_mesh) not in expression_cache:
         visited = set()
         replacements = dict()
         truth_problems = list()
         truth_problem_to_components = { # outer dict index over time derivative
             0: dict(),
             1: dict()
         }
         truth_problem_to_exact_truth_problem = dict()
         truth_problem_to_reduced_mesh_solution = dict()
         truth_problem_to_reduced_mesh_solution_dot = dict()
         truth_problem_to_reduced_mesh_interpolator = { # outer dict index over time derivative
             0: dict(),
             1: dict()
         }
         reduced_problem_to_components = { # outer dict index over time derivative
             0: dict(),
             1: dict()
         }
         reduced_problem_to_reduced_mesh_solution = dict()
         reduced_problem_to_reduced_mesh_solution_dot = dict()
         reduced_problem_to_reduced_basis_functions = { # outer dict index over time derivative
             0: dict(),
             1: dict()
         }
         
         # Look for terminals on truth mesh
         for node in wrapping.expression_iterator(expression):
             if node in visited:
                 continue
             # ... problem solutions related to nonlinear terms
             elif wrapping.is_problem_solution_type(node):
                 node_is_problem_solution = wrapping.is_problem_solution(node)
                 node_is_problem_solution_dot = wrapping.is_problem_solution_dot(node)
                 if node_is_problem_solution or node_is_problem_solution_dot:
                     if node_is_problem_solution:
                         (preprocessed_node, component, truth_solution) = wrapping.solution_identify_component(node)
                         truth_problem = get_problem_from_solution(truth_solution)
                         # Time derivative key for components and interpolator dicts
                         time_derivative = 0
                     elif node_is_problem_solution_dot:
                         (preprocessed_node, component, truth_solution_dot) = wrapping.solution_dot_identify_component(node)
                         truth_problem = get_problem_from_solution_dot(truth_solution_dot)
                         # Time derivative key for components and interpolator dicts
                         time_derivative = 1
                     # Store truth problem
                     if truth_problem not in truth_problems:
                         truth_problems.append(truth_problem)
                     # Store the component
                     if truth_problem not in truth_problem_to_components[time_derivative]:
                         truth_problem_to_components[time_derivative][truth_problem] = list()
                     if component not in truth_problem_to_components[time_derivative][truth_problem]:
                         truth_problem_to_components[time_derivative][truth_problem].append(component)
                         # Get the function space corresponding to preprocessed_node on the reduced mesh
                         auxiliary_reduced_V = at.get_auxiliary_reduced_function_space(truth_problem, component)
                         # Define and store the replacement
                         assert preprocessed_node not in replacements # as it is related to a new truth solution component
                         replacements[preprocessed_node] = backend.Function(auxiliary_reduced_V)
                         if time_derivative is 0:
                             if truth_problem not in truth_problem_to_reduced_mesh_solution:
                                 truth_problem_to_reduced_mesh_solution[truth_problem] = list()
                             truth_problem_to_reduced_mesh_solution[truth_problem].append(replacements[preprocessed_node])
                         elif time_derivative is 1:
                             if truth_problem not in truth_problem_to_reduced_mesh_solution_dot:
                                 truth_problem_to_reduced_mesh_solution_dot[truth_problem] = list()
                             truth_problem_to_reduced_mesh_solution_dot[truth_problem].append(replacements[preprocessed_node])
                         # Get interpolator on reduced mesh
                         if truth_problem not in truth_problem_to_reduced_mesh_interpolator[time_derivative]:
                             truth_problem_to_reduced_mesh_interpolator[time_derivative][truth_problem] = list()
                         truth_problem_to_reduced_mesh_interpolator[time_derivative][truth_problem].append(at.get_auxiliary_function_interpolator(truth_problem, component))
                 else:
                     (preprocessed_node, component, auxiliary_problem) = wrapping.get_auxiliary_problem_for_non_parametrized_function(node)
                     if preprocessed_node not in replacements:
                         # Get interpolator on reduced mesh
                         auxiliary_truth_problem_to_reduced_mesh_interpolator = at.get_auxiliary_function_interpolator(auxiliary_problem, component)
                         # Define and store the replacement
                         replacements[preprocessed_node] = auxiliary_truth_problem_to_reduced_mesh_interpolator(preprocessed_node)
                 # Make sure to skip any parent solution related to this one
                 visited.add(node)
                 visited.add(preprocessed_node)
                 for parent_node in wrapping.solution_iterator(preprocessed_node):
                     visited.add(parent_node)
             # ... geometric quantities
             elif isinstance(node, GeometricQuantity):
                 replacements[node] = type(node)(reduced_mesh)
                 visited.add(node)
             else:
                 visited.add(node)
         # ... and replace them
         replaced_expression = wrapping.expression_replace(expression, replacements)
         
         # Cache the resulting dicts
         expression_cache[(expression_name, reduced_mesh)] = replaced_expression
         truth_problems_cache[(expression_name, reduced_mesh)] = truth_problems
         truth_problem_to_components_cache[(expression_name, reduced_mesh)] = truth_problem_to_components
         truth_problem_to_exact_truth_problem_cache[(expression_name, reduced_mesh)] = truth_problem_to_exact_truth_problem
         truth_problem_to_reduced_mesh_solution_cache[(expression_name, reduced_mesh)] = truth_problem_to_reduced_mesh_solution
         truth_problem_to_reduced_mesh_solution_dot_cache[(expression_name, reduced_mesh)] = truth_problem_to_reduced_mesh_solution_dot
         truth_problem_to_reduced_mesh_interpolator_cache[(expression_name, reduced_mesh)] = truth_problem_to_reduced_mesh_interpolator
         reduced_problem_to_components_cache[(expression_name, reduced_mesh)] = reduced_problem_to_components
         reduced_problem_to_reduced_mesh_solution_cache[(expression_name, reduced_mesh)] = reduced_problem_to_reduced_mesh_solution
         reduced_problem_to_reduced_mesh_solution_dot_cache[(expression_name, reduced_mesh)] = reduced_problem_to_reduced_mesh_solution_dot
         reduced_problem_to_reduced_basis_functions_cache[(expression_name, reduced_mesh)] = reduced_problem_to_reduced_basis_functions
         
     # Extract from cache
     replaced_expression = expression_cache[(expression_name, reduced_mesh)]
     truth_problems = truth_problems_cache[(expression_name, reduced_mesh)]
     truth_problem_to_components = truth_problem_to_components_cache[(expression_name, reduced_mesh)]
     truth_problem_to_exact_truth_problem = truth_problem_to_exact_truth_problem_cache[(expression_name, reduced_mesh)]
     truth_problem_to_reduced_mesh_solution = truth_problem_to_reduced_mesh_solution_cache[(expression_name, reduced_mesh)]
     truth_problem_to_reduced_mesh_solution_dot = truth_problem_to_reduced_mesh_solution_dot_cache[(expression_name, reduced_mesh)]
     truth_problem_to_reduced_mesh_interpolator = truth_problem_to_reduced_mesh_interpolator_cache[(expression_name, reduced_mesh)]
     reduced_problem_to_components = reduced_problem_to_components_cache[(expression_name, reduced_mesh)]
     reduced_problem_to_reduced_mesh_solution = reduced_problem_to_reduced_mesh_solution_cache[(expression_name, reduced_mesh)]
     reduced_problem_to_reduced_mesh_solution_dot = reduced_problem_to_reduced_mesh_solution_dot_cache[(expression_name, reduced_mesh)]
     reduced_problem_to_reduced_basis_functions = reduced_problem_to_reduced_basis_functions_cache[(expression_name, reduced_mesh)]
     
     # Get list of truth and reduced problems that need to be solved, possibly updating cache
     required_truth_problems = list()
     required_reduced_problems = list()
     for truth_problem in truth_problems:
         truth_problem_is_solving = hasattr(truth_problem, "_is_solving")
         if is_training_started(truth_problem):
             reduced_problem = get_reduced_problem_from_problem(truth_problem)
             reduced_problem_is_solving = hasattr(reduced_problem, "_is_solving")
         else:
             reduced_problem = None
             reduced_problem_is_solving = False
         if not truth_problem_is_solving:
             if is_training_finished(truth_problem):
                 # Store the replacement for solution
                 if (
                     reduced_problem not in reduced_problem_to_reduced_mesh_solution
                         and
                     truth_problem in truth_problem_to_reduced_mesh_solution
                 ):
                     reduced_problem_to_reduced_mesh_solution[reduced_problem] = truth_problem_to_reduced_mesh_solution[truth_problem]
                     # Store the component
                     assert reduced_problem not in reduced_problem_to_components[0]
                     assert truth_problem in truth_problem_to_components[0]
                     reduced_problem_to_components[0][reduced_problem] = truth_problem_to_components[0][truth_problem]
                     # Get reduced problem basis functions on reduced mesh
                     assert reduced_problem not in reduced_problem_to_reduced_basis_functions[0]
                     reduced_problem_to_reduced_basis_functions[0][reduced_problem] = [at.get_auxiliary_basis_functions_matrix(truth_problem, component) for component in reduced_problem_to_components[0][reduced_problem]]
                 # Store the replacement for solution_dot
                 if (
                     reduced_problem not in reduced_problem_to_reduced_mesh_solution_dot
                         and
                     truth_problem in truth_problem_to_reduced_mesh_solution_dot
                 ):
                     reduced_problem_to_reduced_mesh_solution_dot[reduced_problem] = truth_problem_to_reduced_mesh_solution_dot[truth_problem]
                     # Store the component
                     assert reduced_problem not in reduced_problem_to_components[1]
                     assert truth_problem in truth_problem_to_components[1]
                     reduced_problem_to_components[1][reduced_problem] = truth_problem_to_components[1][truth_problem]
                     # Get reduced problem basis functions on reduced mesh
                     assert reduced_problem not in reduced_problem_to_reduced_basis_functions[1]
                     reduced_problem_to_reduced_basis_functions[1][reduced_problem] = [at.get_auxiliary_basis_functions_matrix(truth_problem, component) for component in reduced_problem_to_components[1][reduced_problem]]
                 # Append to list of required reduced problems
                 required_reduced_problems.append((reduced_problem, reduced_problem_is_solving))
             else:
                 if (
                     hasattr(truth_problem, "_apply_exact_evaluation_at_stages")
                         and
                     not hasattr(truth_problem, "_apply_EIM_at_stages")
                         and
                     not hasattr(truth_problem, "_apply_DEIM_at_stages")
                 ):
                     # Init truth problem (if required), as it may not have been initialized
                     truth_problem.init()
                     # Append to list of required truth problems which are not currently solving
                     required_truth_problems.append((truth_problem, False, reduced_problem_is_solving))
                 else:
                     # Store the corresponding exact truth problem
                     if truth_problem not in truth_problem_to_exact_truth_problem:
                         exact_truth_problem = exact_problem(truth_problem)
                         truth_problem_to_exact_truth_problem[truth_problem] = exact_truth_problem
                         # Init exact truth problem (if required), as it may not have been initialized
                         exact_truth_problem.init()
                     else:
                         exact_truth_problem = truth_problem_to_exact_truth_problem[truth_problem]
                     # Store the replacement for solution
                     if (
                         exact_truth_problem not in truth_problem_to_reduced_mesh_solution
                             and
                         truth_problem in truth_problem_to_reduced_mesh_solution
                     ):
                         truth_problem_to_reduced_mesh_solution[exact_truth_problem] = truth_problem_to_reduced_mesh_solution[truth_problem]
                         # Store the component
                         assert exact_truth_problem not in truth_problem_to_components[0]
                         assert truth_problem in truth_problem_to_components[0]
                         truth_problem_to_components[0][exact_truth_problem] = truth_problem_to_components[0][truth_problem]
                         # Get interpolator on reduced mesh
                         assert exact_truth_problem not in truth_problem_to_reduced_mesh_interpolator[0]
                         assert truth_problem in truth_problem_to_reduced_mesh_interpolator[0]
                         truth_problem_to_reduced_mesh_interpolator[0][exact_truth_problem] = truth_problem_to_reduced_mesh_interpolator[0][truth_problem]
                     # Store the replacement for solution_dot
                     if (
                         exact_truth_problem not in truth_problem_to_reduced_mesh_solution_dot
                             and
                         truth_problem in truth_problem_to_reduced_mesh_solution_dot
                     ):
                         truth_problem_to_reduced_mesh_solution_dot[exact_truth_problem] = truth_problem_to_reduced_mesh_solution_dot[truth_problem]
                         # Store the component
                         assert exact_truth_problem not in truth_problem_to_components[1]
                         assert truth_problem in truth_problem_to_components[1]
                         truth_problem_to_components[1][exact_truth_problem] = truth_problem_to_components[1][truth_problem]
                         # Get interpolator on reduced mesh
                         assert exact_truth_problem not in truth_problem_to_reduced_mesh_interpolator[1]
                         assert truth_problem in truth_problem_to_reduced_mesh_interpolator[1]
                         truth_problem_to_reduced_mesh_interpolator[1][exact_truth_problem] = truth_problem_to_reduced_mesh_interpolator[1][truth_problem]
                     # Append to list of required truth problems which are not currently solving
                     required_truth_problems.append((exact_truth_problem, False, reduced_problem_is_solving))
         else:
             assert not reduced_problem_is_solving
             # Append to list of required truth problems which are currently solving
             required_truth_problems.append((truth_problem, True, False))
     
     # Solve truth problems (which have not been reduced yet) associated to nonlinear terms
     for (truth_problem, truth_problem_is_solving, reduced_problem_is_solving) in required_truth_problems:
         if not reduced_problem_is_solving:
             # Solve (if necessary)
             truth_problem.set_mu(mu)
             if not truth_problem_is_solving:
                 log(PROGRESS, "In expression_on_reduced_mesh, requiring truth problem solve for problem " + truth_problem.name())
                 truth_problem.solve()
             else:
                 log(PROGRESS, "In expression_on_reduced_mesh, loading current truth problem solution for problem " + truth_problem.name())
         else:
             reduced_problem = get_reduced_problem_from_problem(truth_problem)
             log(PROGRESS, "In expression_on_reduced_mesh, replacing current truth problem solution with reduced solution for problem " + reduced_problem.truth_problem.name())
         # Assign to reduced_mesh_solution
         if truth_problem in truth_problem_to_reduced_mesh_solution:
             for (reduced_mesh_solution, reduced_mesh_interpolator) in zip(truth_problem_to_reduced_mesh_solution[truth_problem], truth_problem_to_reduced_mesh_interpolator[0][truth_problem]):
                 solution_to = reduced_mesh_solution
                 if t is None:
                     if not reduced_problem_is_solving:
                         solution_from = reduced_mesh_interpolator(truth_problem._solution)
                     else:
                         solution_from = reduced_mesh_interpolator(reduced_problem.basis_functions[:reduced_problem._solution.N]*reduced_problem._solution)
                 else:
                     if not reduced_problem_is_solving:
                         if not truth_problem_is_solving:
                             solution_from = reduced_mesh_interpolator(truth_problem._solution_over_time.at(t))
                         else:
                             solution_from = reduced_mesh_interpolator(truth_problem._solution)
                     else:
                         solution_from = reduced_mesh_interpolator(reduced_problem.basis_functions[:reduced_problem._solution.N]*reduced_problem._solution)
                 backend.assign(solution_to, solution_from)
         # Assign to reduced_mesh_solution_dot
         if truth_problem in truth_problem_to_reduced_mesh_solution_dot:
             for (reduced_mesh_solution_dot, reduced_mesh_interpolator) in zip(truth_problem_to_reduced_mesh_solution_dot[truth_problem], truth_problem_to_reduced_mesh_interpolator[1][truth_problem]):
                 solution_dot_to = reduced_mesh_solution_dot
                 assert t is not None
                 if not reduced_problem_is_solving:
                     if not truth_problem_is_solving:
                         solution_dot_from = reduced_mesh_interpolator(truth_problem._solution_dot_over_time.at(t))
                     else:
                         solution_dot_from = reduced_mesh_interpolator(truth_problem._solution_dot)
                 else:
                     solution_dot_from = reduced_mesh_interpolator(reduced_problem.basis_functions[:reduced_problem._solution_dot.N]*reduced_problem._solution_dot)
                 backend.assign(solution_dot_to, solution_dot_from)
     
     # Solve reduced problems associated to nonlinear terms
     for (reduced_problem, is_solving) in required_reduced_problems:
         # Solve (if necessary)
         reduced_problem.set_mu(mu)
         if not is_solving:
             log(PROGRESS, "In expression_on_reduced_mesh, requiring reduced problem solve for problem " + reduced_problem.truth_problem.name())
             reduced_problem.solve()
         else:
             log(PROGRESS, "In expression_on_reduced_mesh, loading current reduced problem solution for problem " + reduced_problem.truth_problem.name())
         # Assign to reduced_mesh_solution
         if reduced_problem in reduced_problem_to_reduced_mesh_solution:
             for (reduced_mesh_solution, reduced_basis_functions) in zip(reduced_problem_to_reduced_mesh_solution[reduced_problem], reduced_problem_to_reduced_basis_functions[0][reduced_problem]):
                 solution_to = reduced_mesh_solution
                 solution_from_N = OnlineSizeDict()
                 for c, v in reduced_problem._solution.N.items():
                     if c in reduced_basis_functions._components_name:
                         solution_from_N[c] = v
                 solution_from = online_backend.OnlineFunction(solution_from_N)
                 if t is None or is_solving:
                     online_backend.online_assign(solution_from, reduced_problem._solution)
                 else:
                     online_backend.online_assign(solution_from, reduced_problem._solution_over_time.at(t))
                 solution_from = reduced_basis_functions[:solution_from_N]*solution_from
                 backend.assign(solution_to, solution_from)
         # Assign to reduced_mesh_solution_dot
         if reduced_problem in reduced_problem_to_reduced_mesh_solution_dot:
             for (reduced_mesh_solution_dot, reduced_basis_functions) in zip(reduced_problem_to_reduced_mesh_solution_dot[reduced_problem], reduced_problem_to_reduced_basis_functions[1][reduced_problem]):
                 solution_dot_to = reduced_mesh_solution_dot
                 solution_dot_from_N = OnlineSizeDict()
                 for c, v in reduced_problem._solution_dot.N.items():
                     if c in reduced_basis_functions._components_name:
                         solution_dot_from_N[c] = v
                 solution_dot_from = online_backend.OnlineFunction(solution_dot_from_N)
                 assert t is not None
                 if is_solving:
                     online_backend.online_assign(solution_dot_from, reduced_problem._solution_dot)
                 else:
                     online_backend.online_assign(solution_dot_from, reduced_problem._solution_dot_over_time.at(t))
                 solution_dot_from = reduced_basis_functions[:solution_dot_from_N]*solution_dot_from
                 backend.assign(solution_dot_to, solution_dot_from)
     
     # Evaluate and return
     reduced_function = backend.Function(reduced_space)
     wrapping.evaluate_expression(expression, reduced_function, replaced_expression)
     return reduced_function