示例#1
0
 def get_auxiliary_reduced_function_space(self, auxiliary_problem, component, index=None):
     assert isinstance(component, tuple)
     assert len(component) > 0
     index = self._get_dict_index(index)
     auxiliary_V = _sub_from_tuple(auxiliary_problem.V, component)
     key = (auxiliary_problem, component, index)
     if key not in self._auxiliary_reduced_function_space:
         auxiliary_reduced_V = wrapping.convert_functionspace_to_submesh(auxiliary_V, self.reduced_mesh[index], self._get_auxiliary_reduced_function_space_type(auxiliary_V))
         self._auxiliary_reduced_function_space[key] = auxiliary_reduced_V
         if not self._load_auxiliary_reduced_function_space(key):
             # Get the map between DOFs on auxiliary_V and auxiliary_reduced_V
             (auxiliary_dofs_to_reduced_dofs, _) = wrapping.map_functionspaces_between_mesh_and_submesh(auxiliary_V, self.mesh, auxiliary_reduced_V, self.reduced_mesh[index])
             log(DEBUG, "Auxiliary DOFs to reduced DOFs is " + str(auxiliary_dofs_to_reduced_dofs))
             self._auxiliary_dofs_to_reduced_dofs[key] = auxiliary_dofs_to_reduced_dofs
             # Save to file
             self._save_auxiliary_reduced_function_space(key)
     else:
         assert key in self._auxiliary_dofs_to_reduced_dofs
     return self._auxiliary_reduced_function_space[key]
    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_form_on_truth_function_space(form_wrapper, tensor=None):
        form = form_wrapper._form
        form_name = form_wrapper.name()
        mu = get_problem_from_parametrized_operator(form_wrapper).mu

        if form_name not in form_on_truth_function_space__reduced_problem_to_truth_solution_cache:
            visited = set()
            truth_problems = list()
            truth_problem_to_components = dict()
            truth_problem_to_exact_truth_problem = dict()
            truth_problem_to_truth_solution = dict()
            reduced_problem_to_components = dict()
            reduced_problem_to_truth_solution = 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_or_problem_solution_component_type(
                        node):
                    if wrapping.is_problem_solution_or_problem_solution_component(
                            node):
                        (preprocessed_node, component, truth_solution
                         ) = wrapping.solution_identify_component(node)
                        truth_problem = get_problem_from_solution(
                            truth_solution)
                        truth_problems.append(truth_problem)
                        # Store the solution
                        truth_problem_to_truth_solution[
                            truth_problem] = truth_solution
                        # Store the component
                        if truth_problem not in truth_problem_to_components:
                            truth_problem_to_components[truth_problem] = list()
                        truth_problem_to_components[truth_problem].append(
                            component)
                    else:
                        preprocessed_node = 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)

            # Cache the resulting dicts
            form_on_truth_function_space__truth_problems_cache[
                form_name] = truth_problems
            form_on_truth_function_space__truth_problem_to_components_cache[
                form_name] = truth_problem_to_components
            form_on_truth_function_space__truth_problem_to_exact_truth_problem_cache[
                form_name] = truth_problem_to_exact_truth_problem
            form_on_truth_function_space__truth_problem_to_truth_solution_cache[
                form_name] = truth_problem_to_truth_solution
            form_on_truth_function_space__reduced_problem_to_components_cache[
                form_name] = reduced_problem_to_components
            form_on_truth_function_space__reduced_problem_to_truth_solution_cache[
                form_name] = reduced_problem_to_truth_solution

        # Extract from cache
        truth_problems = form_on_truth_function_space__truth_problems_cache[
            form_name]
        truth_problem_to_components = form_on_truth_function_space__truth_problem_to_components_cache[
            form_name]
        truth_problem_to_exact_truth_problem = form_on_truth_function_space__truth_problem_to_exact_truth_problem_cache[
            form_name]
        truth_problem_to_truth_solution = form_on_truth_function_space__truth_problem_to_truth_solution_cache[
            form_name]
        reduced_problem_to_components = form_on_truth_function_space__reduced_problem_to_components_cache[
            form_name]
        reduced_problem_to_truth_solution = form_on_truth_function_space__reduced_problem_to_truth_solution_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 component
                    if reduced_problem not in reduced_problem_to_components:
                        reduced_problem_to_components[
                            reduced_problem] = truth_problem_to_components[
                                truth_problem]
                    # Store the solution
                    if reduced_problem not in reduced_problem_to_truth_solution:
                        reduced_problem_to_truth_solution[
                            reduced_problem] = truth_problem_to_truth_solution[
                                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 component
                        if exact_truth_problem not in truth_problem_to_components:
                            truth_problem_to_components[
                                exact_truth_problem] = truth_problem_to_components[
                                    truth_problem]
                        # Store the solution
                        if exact_truth_problem not in truth_problem_to_truth_solution:
                            truth_problem_to_truth_solution[
                                exact_truth_problem] = truth_problem_to_truth_solution[
                                    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
        truth_problem_to_truth_solution_copy = dict()
        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())
            # ... and assign to truth_solution
            truth_solution = truth_problem_to_truth_solution[truth_problem]
            truth_problem_to_truth_solution_copy[truth_problem] = backend.copy(
                truth_solution)
            for component in truth_problem_to_components[truth_problem]:
                solution_to = _sub_from_tuple(truth_solution, component)
                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)
                backend.assign(solution_to, solution_from)

        # Solve reduced problems associated to nonlinear terms
        reduced_problem_to_truth_solution_copy = dict()
        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())
            # ... and assign to truth_solution
            truth_solution = reduced_problem_to_truth_solution[reduced_problem]
            reduced_problem_to_truth_solution_copy[
                reduced_problem] = backend.copy(truth_solution)
            for component in reduced_problem_to_components[reduced_problem]:
                solution_to = _sub_from_tuple(truth_solution, component)
                solution_from = _sub_from_tuple(
                    reduced_problem.basis_functions[:reduced_problem._solution.
                                                    N] *
                    reduced_problem._solution, component)
                backend.assign(solution_to, solution_from)

        # Assemble
        assembled_form = wrapping.assemble(form, tensor)
        assembled_form.generator = form_wrapper  # for I/O
        form_rank = assembled_form.rank()

        # Undo any side effect of truth problem solves
        for (truth_problem, _, _) in required_truth_problems:
            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[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)

        # Undo any side effect of reduced problem solves
        for (reduced_problem, _) in required_reduced_problems:
            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[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)

        # 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