Esempio n. 1
0
 def _basic_is_time_dependent(expression_or_form, iterator):
     visited = set()
     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_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)
                 if hasattr(truth_problem, "set_time"):
                     return True
             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)
     return False
Esempio n. 2
0
 def _basic_expression_name(expression):
     str_repr = ""
     visited = set()
     coefficients_replacement = dict()
     for n in wrapping.expression_iterator(expression):
         if n in visited:
             continue
         if has_pybind11():
             cppcode_attribute = "_cppcode"
         else:
             cppcode_attribute = "cppcode"
         if hasattr(n, cppcode_attribute):
             coefficients_replacement[repr(n)] = str(
                 getattr(n, cppcode_attribute))
             str_repr += repr(getattr(n, cppcode_attribute))
             visited.add(n)
         elif wrapping.is_problem_solution_or_problem_solution_component_type(
                 n):
             if wrapping.is_problem_solution_or_problem_solution_component(
                     n):
                 (preprocessed_n, component,
                  truth_solution) = wrapping.solution_identify_component(n)
                 problem = get_problem_from_solution(truth_solution)
             else:
                 (
                     problem, component
                 ) = wrapping.get_auxiliary_problem_for_non_parametrized_function(
                     n)
                 preprocessed_n = n
             coefficients_replacement[repr(preprocessed_n)] = str(
                 problem.name()) + str(component)
             str_repr += repr(problem.name()) + 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):
             if has_pybind11():
                 vals = n.values()
             else:
                 x = zeros(1)
                 vals = zeros(n.value_size())
                 n.eval(vals, x)
             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 + dolfin_version).encode("utf-8")).hexdigest(
         )  # similar to dolfin/compilemodules/compilemodule.py
     return hash_code
Esempio n. 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 has_pybind11():
             cppcode_attribute = "_cppcode"
         else:
             cppcode_attribute = "cppcode"
         if hasattr(n, cppcode_attribute):
             coefficients_repr[n] = str(getattr(n, cppcode_attribute))
             visited.add(n)
         elif wrapping.is_problem_solution_or_problem_solution_component_type(n):
             if wrapping.is_problem_solution_or_problem_solution_component(n):
                 (preprocessed_n, component, truth_solution) = wrapping.solution_identify_component(n)
                 problem = get_problem_from_solution(truth_solution)
             else:
                 (problem, component) = wrapping.get_auxiliary_problem_for_non_parametrized_function(n)
                 preprocessed_n = n
             coefficients_repr[preprocessed_n] = "solution of " + str(problem.name())
             if len(component) is 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):
             if has_pybind11():
                 vals = n.values()
             else:
                 x = zeros(1)
                 vals = zeros(n.value_size())
                 n.eval(vals, x)
             if len(vals) == 1:
                 coefficients_repr[n] = str(vals[0])
             else:
                 coefficients_repr[n] = str(vals.reshape(n.ufl_shape))
             visited.add(n)
     return coefficients_repr
    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_reduced_mesh(expression_wrapper, at):
        expression = expression_wrapper._expression
        expression_name = expression_wrapper.name()
        reduced_space = at.get_reduced_function_space()
        mu = get_problem_from_parametrized_expression(expression_wrapper).mu
        reduced_mesh = at.get_reduced_mesh()

        if (expression_name, reduced_mesh
            ) not in expression_on_reduced_mesh__expression_cache:
            visited = set()
            replacements = dict()
            truth_problems = list()
            truth_problem_to_components = dict()
            truth_problem_to_exact_truth_problem = dict()
            truth_problem_to_reduced_mesh_solution = dict()
            truth_problem_to_reduced_mesh_interpolator = dict()
            reduced_problem_to_components = dict()
            reduced_problem_to_reduced_mesh_solution = dict()
            reduced_problem_to_reduced_basis_functions = 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_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 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)
                        # 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
                        if truth_problem not in truth_problem_to_reduced_mesh_solution:
                            truth_problem_to_reduced_mesh_solution[
                                truth_problem] = list()
                        replacements[preprocessed_node] = backend.Function(
                            auxiliary_reduced_V)
                        truth_problem_to_reduced_mesh_solution[
                            truth_problem].append(
                                replacements[preprocessed_node])
                        # Get interpolator on reduced mesh
                        if truth_problem not in truth_problem_to_reduced_mesh_interpolator:
                            truth_problem_to_reduced_mesh_interpolator[
                                truth_problem] = list()
                        truth_problem_to_reduced_mesh_interpolator[
                            truth_problem].append(
                                at.get_auxiliary_function_interpolator(
                                    truth_problem, component))
                    else:
                        (
                            auxiliary_problem, component
                        ) = wrapping.get_auxiliary_problem_for_non_parametrized_function(
                            node)
                        preprocessed_node = node
                        # Get the function space corresponding to preprocessed_node on the reduced mesh
                        auxiliary_reduced_V = at.get_auxiliary_reduced_function_space(
                            auxiliary_problem, component)
                        # 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)
            # ... and replace them
            replaced_expression = wrapping.expression_replace(
                expression, replacements)

            # Cache the resulting dicts
            expression_on_reduced_mesh__expression_cache[(
                expression_name, reduced_mesh)] = replaced_expression
            expression_on_reduced_mesh__truth_problems_cache[(
                expression_name, reduced_mesh)] = truth_problems
            expression_on_reduced_mesh__truth_problem_to_components_cache[(
                expression_name, reduced_mesh)] = truth_problem_to_components
            expression_on_reduced_mesh__truth_problem_to_exact_truth_problem_cache[
                (expression_name,
                 reduced_mesh)] = truth_problem_to_exact_truth_problem
            expression_on_reduced_mesh__truth_problem_to_reduced_mesh_solution_cache[
                (expression_name,
                 reduced_mesh)] = truth_problem_to_reduced_mesh_solution
            expression_on_reduced_mesh__truth_problem_to_reduced_mesh_interpolator_cache[
                (expression_name,
                 reduced_mesh)] = truth_problem_to_reduced_mesh_interpolator
            expression_on_reduced_mesh__reduced_problem_to_components_cache[(
                expression_name, reduced_mesh)] = reduced_problem_to_components
            expression_on_reduced_mesh__reduced_problem_to_reduced_mesh_solution_cache[
                (expression_name,
                 reduced_mesh)] = reduced_problem_to_reduced_mesh_solution
            expression_on_reduced_mesh__reduced_problem_to_reduced_basis_functions_cache[
                (expression_name,
                 reduced_mesh)] = reduced_problem_to_reduced_basis_functions

        # Extract from cache
        replaced_expression = expression_on_reduced_mesh__expression_cache[(
            expression_name, reduced_mesh)]
        truth_problems = expression_on_reduced_mesh__truth_problems_cache[(
            expression_name, reduced_mesh)]
        truth_problem_to_components = expression_on_reduced_mesh__truth_problem_to_components_cache[
            (expression_name, reduced_mesh)]
        truth_problem_to_exact_truth_problem = expression_on_reduced_mesh__truth_problem_to_exact_truth_problem_cache[
            (expression_name, reduced_mesh)]
        truth_problem_to_reduced_mesh_solution = expression_on_reduced_mesh__truth_problem_to_reduced_mesh_solution_cache[
            (expression_name, reduced_mesh)]
        truth_problem_to_reduced_mesh_interpolator = expression_on_reduced_mesh__truth_problem_to_reduced_mesh_interpolator_cache[
            (expression_name, reduced_mesh)]
        reduced_problem_to_components = expression_on_reduced_mesh__reduced_problem_to_components_cache[
            (expression_name, reduced_mesh)]
        reduced_problem_to_reduced_mesh_solution = expression_on_reduced_mesh__reduced_problem_to_reduced_mesh_solution_cache[
            (expression_name, reduced_mesh)]
        reduced_problem_to_reduced_basis_functions = expression_on_reduced_mesh__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 component
                    if reduced_problem not in reduced_problem_to_components:
                        reduced_problem_to_components[
                            reduced_problem] = truth_problem_to_components[
                                truth_problem]
                    # Store the replacement
                    if reduced_problem not in reduced_problem_to_reduced_mesh_solution:
                        reduced_problem_to_reduced_mesh_solution[
                            reduced_problem] = truth_problem_to_reduced_mesh_solution[
                                truth_problem]
                    # Get reduced problem basis functions on reduced mesh
                    if reduced_problem not in reduced_problem_to_reduced_basis_functions:
                        reduced_problem_to_reduced_basis_functions[
                            reduced_problem] = list()
                        for component in reduced_problem_to_components[
                                reduced_problem]:
                            reduced_problem_to_reduced_basis_functions[
                                reduced_problem].append(
                                    at.get_auxiliary_basis_functions_matrix(
                                        truth_problem, reduced_problem,
                                        component))
                    # 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 replacement
                        if exact_truth_problem not in truth_problem_to_reduced_mesh_solution:
                            truth_problem_to_reduced_mesh_solution[
                                exact_truth_problem] = truth_problem_to_reduced_mesh_solution[
                                    truth_problem]
                        # Get interpolator on reduced mesh
                        if exact_truth_problem not in truth_problem_to_reduced_mesh_interpolator:
                            truth_problem_to_reduced_mesh_interpolator[
                                exact_truth_problem] = list()
                            for component in truth_problem_to_components[
                                    exact_truth_problem]:
                                truth_problem_to_reduced_mesh_interpolator[
                                    exact_truth_problem].append(
                                        at.get_auxiliary_function_interpolator(
                                            exact_truth_problem, component))
                        # 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())
            # ... and assign 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[truth_problem]):
                solution_to = reduced_mesh_solution
                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)
                backend.assign(solution_to, solution_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())
            # ... and assign 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[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)
                online_backend.online_assign(solution_from,
                                             reduced_problem._solution)
                solution_from = reduced_basis_functions[:solution_from_N] * solution_from
                backend.assign(solution_to, solution_from)

        # Evaluate and return
        reduced_function = backend.Function(reduced_space)
        wrapping.evaluate_expression(expression, reduced_function,
                                     replaced_expression)
        return reduced_function
        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)
            
            log(PROGRESS, "***        SEPARATE FORM COEFFICIENTS        ***")
            
            log(PROGRESS, "1. Extract coefficients")
            integral_to_coefficients = dict()
            for integral in self._form.integrals():
                log(PROGRESS, "\t Currently on integrand " + str(integral.integrand()))
                self._coefficients.append(list()) # of ParametrizedExpression
                for e in iter_expressions(integral):
                    log(PROGRESS, "\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):
                                log(PROGRESS, "\t\t Node " + str(n) + " is skipped because it is an Argument")
                                continue
                            elif isinstance(n, Constant):
                                log(PROGRESS, "\t\t Node " + str(n) + " is skipped because it is a Constant")
                                continue
                            elif isinstance(n, MultiIndex):
                                log(PROGRESS, "\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):
                                    log(PROGRESS, "\t\t Node " + str(n) + " is skipped because it contains an Argument")
                                    break
                            else: # not broken
                                log(PROGRESS, "\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):
                                                    log(PROGRESS, "\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:
                                                    if has_pybind11():
                                                        parameters = t._parameters
                                                    else:
                                                        parameters = t.user_parameters
                                                    if "mu_0" not in parameters:
                                                        log(PROGRESS, "\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):
                                                log(PROGRESS, "\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:
                                                log(PROGRESS, "\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_or_problem_solution_component_type(t):
                                                if not wrapping.is_problem_solution_or_problem_solution_component(t):
                                                    log(PROGRESS, "\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
                                                    (_, _, solution) = wrapping.solution_identify_component(t)
                                                    log(PROGRESS, "\t\t\t Descendant node " + str(d) + " causes the non-parametrized check to break because it contains the solution of " + get_problem_from_solution(solution).name() + "and strict mode is on")
                                                    break
                                        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
                                                    log(PROGRESS, "\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_or_problem_solution_component_type(t):
                                                    if wrapping.is_problem_solution_or_problem_solution_component(t):
                                                        at_least_one_expression_or_solution = True
                                                        (_, _, solution) = wrapping.solution_identify_component(t)
                                                        log(PROGRESS, "\t\t\t Descendant node " + str(d) + " is a candidate after non-parametrized check because it contains the solution of " + get_problem_from_solution(solution).name())
                                                        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:
                                                log(PROGRESS, "\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
                                    log(PROGRESS, "\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
                                    log(PROGRESS, "\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
                                        log(PROGRESS, "\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
                                        log(PROGRESS, "\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()]):
                                                log(PROGRESS, "\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:
                                                log(PROGRESS, "\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()])
                                            log(PROGRESS, "\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) is 1:
                                                preprocessed_candidate = candidates.pop()
                                                log(PROGRESS, "\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) is 1
                                                    preprocessed_candidate = candidates_from_components.pop()
                                                    log(PROGRESS, "\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:
                                                    log(PROGRESS, "\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:
                                            log(PROGRESS, "\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)
                                    log(PROGRESS, "\t\t\t Accepting descendant node " + str(preprocessed_candidate) + " as a coefficient of type " + str(type(preprocessed_candidate)))
                        else:
                            log(PROGRESS, "\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
                    log(PROGRESS, "\t There were no coefficients to extract")
                    self._coefficients.pop() # remove the (empty) element that was added to possibly store coefficients
                else:
                    log(PROGRESS, "\t Extracted coefficients are:\n\t\t" + "\n\t\t".join([str(c) for c in self._coefficients[-1]]))
                    integral_to_coefficients[integral] = self._coefficients[-1]
            
            log(PROGRESS, "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:
                    log(PROGRESS, "\t Adding form for integrand " + str(integral.integrand()) + " to unchanged forms")
                    self._form_unchanged.append(integral.integrand()*measure)
                else:
                    log(PROGRESS, "\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]
                        log(PROGRESS, "\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)
                
            log(PROGRESS, "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"
                            if has_pybind11():
                                parameters = e._parameters
                            else:
                                parameters = e.user_parameters
                            assert "mu_0" not in parameters, "Form " + str(integral) + " still contains a parametrized expression"
            
            log(PROGRESS, "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))
                    
            log(PROGRESS, "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)
            
            log(PROGRESS, "*** DONE - SEPARATE FORM COEFFICIENTS - DONE ***")
            log(PROGRESS, "")
Esempio n. 7
0
    def _basic_form_on_reduced_function_space(form_wrapper, at):
        form = form_wrapper._form
        form_name = form_wrapper.name()
        mu = get_problem_from_parametrized_operator(form_wrapper).mu
        reduced_V = at.get_reduced_function_spaces()
        reduced_subdomain_data = at.get_reduced_subdomain_data()

        if (form_name,
                reduced_V) not in form_on_reduced_function_space__form_cache:
            visited = set()
            replacements = dict()
            truth_problems = list()
            truth_problem_to_components = dict()
            truth_problem_to_exact_truth_problem = dict()
            truth_problem_to_reduced_mesh_solution = dict()
            truth_problem_to_reduced_mesh_interpolator = dict()
            reduced_problem_to_components = dict()
            reduced_problem_to_reduced_mesh_solution = dict()
            reduced_problem_to_reduced_basis_functions = dict()

            # Look for terminals on truth mesh
            for node in wrapping.form_iterator(form, "nodes"):
                if node in visited:
                    continue
                # ... test and trial functions
                elif isinstance(node, Argument):
                    replacements[node] = wrapping.form_argument_replace(
                        node, reduced_V)
                    visited.add(node)
                # ... 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 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)
                        # 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
                        if truth_problem not in truth_problem_to_reduced_mesh_solution:
                            truth_problem_to_reduced_mesh_solution[
                                truth_problem] = list()
                        replacements[preprocessed_node] = backend.Function(
                            auxiliary_reduced_V)
                        truth_problem_to_reduced_mesh_solution[
                            truth_problem].append(
                                replacements[preprocessed_node])
                        # Get interpolator on reduced mesh
                        if truth_problem not in truth_problem_to_reduced_mesh_interpolator:
                            truth_problem_to_reduced_mesh_interpolator[
                                truth_problem] = list()
                        truth_problem_to_reduced_mesh_interpolator[
                            truth_problem].append(
                                at.get_auxiliary_function_interpolator(
                                    truth_problem, component))
                    else:
                        (
                            auxiliary_problem, component
                        ) = wrapping.get_auxiliary_problem_for_non_parametrized_function(
                            node)
                        preprocessed_node = node
                        # Get the function space corresponding to preprocessed_node on the reduced mesh
                        auxiliary_reduced_V = at.get_auxiliary_reduced_function_space(
                            auxiliary_problem, component)
                        # 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):
                    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)
            # ... 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_on_reduced_function_space__form_cache[(
                form_name, reduced_V)] = replaced_form_with_replaced_measures
            form_on_reduced_function_space__truth_problems_cache[(
                form_name, reduced_V)] = truth_problems
            form_on_reduced_function_space__truth_problem_to_components_cache[(
                form_name, reduced_V)] = truth_problem_to_components
            form_on_reduced_function_space__truth_problem_to_exact_truth_problem_cache[
                (form_name, reduced_V)] = truth_problem_to_exact_truth_problem
            form_on_reduced_function_space__truth_problem_to_reduced_mesh_solution_cache[
                (form_name,
                 reduced_V)] = truth_problem_to_reduced_mesh_solution
            form_on_reduced_function_space__truth_problem_to_reduced_mesh_interpolator_cache[
                (form_name,
                 reduced_V)] = truth_problem_to_reduced_mesh_interpolator
            form_on_reduced_function_space__reduced_problem_to_components_cache[
                (form_name, reduced_V)] = reduced_problem_to_components
            form_on_reduced_function_space__reduced_problem_to_reduced_mesh_solution_cache[
                (form_name,
                 reduced_V)] = reduced_problem_to_reduced_mesh_solution
            form_on_reduced_function_space__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_on_reduced_function_space__form_cache[
            (form_name, reduced_V)]
        truth_problems = form_on_reduced_function_space__truth_problems_cache[(
            form_name, reduced_V)]
        truth_problem_to_components = form_on_reduced_function_space__truth_problem_to_components_cache[
            (form_name, reduced_V)]
        truth_problem_to_exact_truth_problem = form_on_reduced_function_space__truth_problem_to_exact_truth_problem_cache[
            (form_name, reduced_V)]
        truth_problem_to_reduced_mesh_solution = form_on_reduced_function_space__truth_problem_to_reduced_mesh_solution_cache[
            (form_name, reduced_V)]
        truth_problem_to_reduced_mesh_interpolator = form_on_reduced_function_space__truth_problem_to_reduced_mesh_interpolator_cache[
            (form_name, reduced_V)]
        reduced_problem_to_components = form_on_reduced_function_space__reduced_problem_to_components_cache[
            (form_name, reduced_V)]
        reduced_problem_to_reduced_mesh_solution = form_on_reduced_function_space__reduced_problem_to_reduced_mesh_solution_cache[
            (form_name, reduced_V)]
        reduced_problem_to_reduced_basis_functions = form_on_reduced_function_space__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):
                    # 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 replacement
                    if reduced_problem not in reduced_problem_to_reduced_mesh_solution:
                        reduced_problem_to_reduced_mesh_solution[
                            reduced_problem] = truth_problem_to_reduced_mesh_solution[
                                truth_problem]
                    # Get reduced problem basis functions on reduced mesh
                    if reduced_problem not in reduced_problem_to_reduced_basis_functions:
                        reduced_problem_to_reduced_basis_functions[
                            reduced_problem] = list()
                        for component in reduced_problem_to_components[
                                reduced_problem]:
                            reduced_problem_to_reduced_basis_functions[
                                reduced_problem].append(
                                    at.get_auxiliary_basis_functions_matrix(
                                        truth_problem, reduced_problem,
                                        component))
                    # 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 replacement
                        if exact_truth_problem not in truth_problem_to_reduced_mesh_solution:
                            truth_problem_to_reduced_mesh_solution[
                                exact_truth_problem] = truth_problem_to_reduced_mesh_solution[
                                    truth_problem]
                        # Get interpolator on reduced mesh
                        if exact_truth_problem not in truth_problem_to_reduced_mesh_interpolator:
                            truth_problem_to_reduced_mesh_interpolator[
                                exact_truth_problem] = list()
                            for component in truth_problem_to_components[
                                    exact_truth_problem]:
                                truth_problem_to_reduced_mesh_interpolator[
                                    exact_truth_problem].append(
                                        at.get_auxiliary_function_interpolator(
                                            exact_truth_problem, component))
                        # 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_reduced_function_space, requiring truth problem solve for problem "
                        + truth_problem.name())
                    truth_problem.solve()
                else:
                    log(
                        PROGRESS,
                        "In form_on_reduced_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_reduced_function_space, replacing current truth problem solution with reduced solution for problem "
                    + reduced_problem.truth_problem.name())
            # ... and assign 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[truth_problem]):
                solution_to = reduced_mesh_solution
                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)
                backend.assign(solution_to, solution_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_reduced_function_space, requiring reduced problem solve for problem "
                    + reduced_problem.truth_problem.name())
                reduced_problem.solve()
            else:
                log(
                    PROGRESS,
                    "In form_on_reduced_function_space, loading current reduced problem solution for problem "
                    + reduced_problem.truth_problem.name())
            # ... and assign 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[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)
                online_backend.online_assign(solution_from,
                                             reduced_problem._solution)
                solution_from = reduced_basis_functions[:solution_from_N] * solution_from
                backend.assign(solution_to, solution_from)

        # Assemble and return
        assembled_replaced_form = wrapping.assemble(
            replaced_form_with_replaced_measures)
        form_rank = assembled_replaced_form.rank()
        return (assembled_replaced_form, form_rank)