Exemple #1
0
 def __init__(self, truth_problem, **kwargs):
     # Call the parent initialization
     DifferentialProblemReductionMethod_DerivedClass.__init__(self, truth_problem, **kwargs)
     
     # Declare a GS object
     self.GS = None # GramSchmidt (for problems with one component) or dict of GramSchmidt (for problem with several components)
     # I/O
     self.folder["snapshots"] = os.path.join(self.folder_prefix, "snapshots")
     self.folder["post_processing"] = os.path.join(self.folder_prefix, "post_processing")
     self.greedy_selected_parameters = GreedySelectedParametersList()
     self.greedy_error_estimators = GreedyErrorEstimatorsList()
     self.label = "RB"
Exemple #2
0
    def __init__(self, truth_problem, folder_prefix, **kwargs):
        # Call the parent initialization
        ParametrizedProblem.__init__(self, folder_prefix)
        # Store the parametrized problem object and the bc list
        self.truth_problem = truth_problem

        # Define additional storage for SCM
        self.B_min = BoundingBoxSideList(
        )  # minimum values of the bounding box mathcal{B}. Vector of size Q
        self.B_max = BoundingBoxSideList(
        )  # maximum values of the bounding box mathcal{B}. Vector of size Q
        self.training_set = None  # SCM algorithm needs the training set also in the online stage
        self.greedy_selected_parameters = GreedySelectedParametersList(
        )  # list storing the parameters selected during the training phase
        self.greedy_selected_parameters_complement = dict(
        )  # dict, over N, of list storing the complement of parameters selected during the training phase
        self.UB_vectors = UpperBoundsList(
        )  # list of Q-dimensional vectors storing the infimizing elements at the greedily selected parameters
        self.N = 0
        self.M_e = kwargs[
            "M_e"]  # integer denoting the number of constraints based on the exact eigenvalues, or None
        self.M_p = kwargs[
            "M_p"]  # integer denoting the number of constraints based on the previous lower bounds, or None

        # I/O
        self.folder["cache"] = os.path.join(self.folder_prefix,
                                            "reduced_cache")
        self.cache_config = config.get("SCM", "cache")
        self.folder["reduced_operators"] = os.path.join(
            self.folder_prefix, "reduced_operators")

        # Coercivity constant eigen problem
        self.exact_coercivity_constant_calculator = ParametrizedCoercivityConstantEigenProblem(
            truth_problem, "a", True, "smallest",
            kwargs["coercivity_eigensolver_parameters"], self.folder_prefix)

        # Store here input parameters provided by the user that are needed by the reduction method
        self._input_storage_for_SCM_reduction = dict()
        self._input_storage_for_SCM_reduction[
            "bounding_box_minimum_eigensolver_parameters"] = kwargs[
                "bounding_box_minimum_eigensolver_parameters"]
        self._input_storage_for_SCM_reduction[
            "bounding_box_maximum_eigensolver_parameters"] = kwargs[
                "bounding_box_maximum_eigensolver_parameters"]

        # Avoid useless linear programming solves
        self._alpha_LB = 0.
        self._alpha_LB_cache = dict()
        self._alpha_UB = 0.
        self._alpha_UB_cache = dict()
Exemple #3
0
        def __init__(self, truth_problem, **kwargs):
            # Call to parent
            EllipticCoerciveReducedProblem_DerivedClass.__init__(
                self, truth_problem, **kwargs)

            # Copy of greedy snapshots
            self.snapshots_mu = GreedySelectedParametersList(
            )  # the difference between this list and greedy_selected_parameters in the reduction method is that this one also stores the initial parameter
            self.snapshots = SnapshotsMatrix(truth_problem.V)

            # Extend allowed keywords argument in solve
            self._online_solve_default_kwargs["online_rectification"] = True
            self.OnlineSolveKwargs = OnlineSolveKwargsGenerator(
                **self._online_solve_default_kwargs)

            # Generate all combinations of allowed keyword arguments in solve
            online_solve_kwargs_with_rectification = list()
            online_solve_kwargs_without_rectification = list()
            for other_args in cartesian_product(
                (True, False),
                    repeat=len(self._online_solve_default_kwargs) - 1):
                args_with_rectification = self.OnlineSolveKwargs(*(other_args +
                                                                   (True, )))
                args_without_rectification = self.OnlineSolveKwargs(
                    *(other_args + (False, )))
                online_solve_kwargs_with_rectification.append(
                    args_with_rectification)
                online_solve_kwargs_without_rectification.append(
                    args_without_rectification)
            self.online_solve_kwargs_with_rectification = online_solve_kwargs_with_rectification
            self.online_solve_kwargs_without_rectification = online_solve_kwargs_without_rectification

            # Flag to disable error estimation after rectification has been setup
            self._disable_error_estimation = False
Exemple #4
0
 def __init__(self, EIM_approximation):
     # Call the parent initialization
     ReductionMethod.__init__(self, EIM_approximation.folder_prefix)
     
     # $$ OFFLINE DATA STRUCTURES $$ #
     # High fidelity problem
     self.EIM_approximation = EIM_approximation
     # Declare a new container to store the snapshots
     self.snapshots_container = self.EIM_approximation.parametrized_expression.create_snapshots_container()
     self._training_set_parameters_to_snapshots_container_index = dict()
     # I/O
     self.folder["snapshots"] = os.path.join(self.folder_prefix, "snapshots")
     self.folder["post_processing"] = os.path.join(self.folder_prefix, "post_processing")
     self.greedy_selected_parameters = GreedySelectedParametersList()
     self.greedy_errors = GreedyErrorEstimatorsList()
     #
     # By default set a tolerance slightly larger than zero, in order to
     # stop greedy iterations in trivial cases by default
     self.tol = 1e-15
Exemple #5
0
class EIMApproximationReductionMethod(ReductionMethod):
    
    # Default initialization of members
    def __init__(self, EIM_approximation):
        # Call the parent initialization
        ReductionMethod.__init__(self, EIM_approximation.folder_prefix)
        
        # $$ OFFLINE DATA STRUCTURES $$ #
        # High fidelity problem
        self.EIM_approximation = EIM_approximation
        # Declare a new container to store the snapshots
        self.snapshots_container = self.EIM_approximation.parametrized_expression.create_snapshots_container()
        self._training_set_parameters_to_snapshots_container_index = dict()
        # I/O
        self.folder["snapshots"] = os.path.join(self.folder_prefix, "snapshots")
        self.folder["post_processing"] = os.path.join(self.folder_prefix, "post_processing")
        self.greedy_selected_parameters = GreedySelectedParametersList()
        self.greedy_errors = GreedyErrorEstimatorsList()
        #
        # By default set a tolerance slightly larger than zero, in order to
        # stop greedy iterations in trivial cases by default
        self.tol = 1e-15
    
    def initialize_training_set(self, ntrain, enable_import=True, sampling=None, **kwargs):
        import_successful = ReductionMethod.initialize_training_set(self, self.EIM_approximation.mu_range, ntrain, enable_import, sampling, **kwargs)
        # Since exact evaluation is required, we cannot use a distributed training set
        self.training_set.distributed_max = False
        # Also initialize the map from parameter values to snapshots container index
        self._training_set_parameters_to_snapshots_container_index = dict((mu, mu_index) for (mu_index, mu) in enumerate(self.training_set))
        return import_successful
        
    def initialize_testing_set(self, ntest, enable_import=False, sampling=None, **kwargs):
        return ReductionMethod.initialize_testing_set(self, self.EIM_approximation.mu_range, ntest, enable_import, sampling, **kwargs)
    
    # Perform the offline phase of EIM
    def offline(self):
        need_to_do_offline_stage = self._init_offline()
        if need_to_do_offline_stage:
            self._offline()
        self._finalize_offline()
        return self.EIM_approximation
        
    # Initialize data structures required for the offline phase
    def _init_offline(self):
        # Prepare folders and init EIM approximation
        all_folders = Folders()
        all_folders.update(self.folder)
        all_folders.update(self.EIM_approximation.folder)
        all_folders.pop("testing_set") # this is required only in the error/speedup analysis
        all_folders.pop("error_analysis") # this is required only in the error analysis
        all_folders.pop("speedup_analysis") # this is required only in the speedup analysis
        at_least_one_folder_created = all_folders.create()
        if not at_least_one_folder_created:
            return False # offline construction should be skipped, since data are already available
        else:
            self.EIM_approximation.init("offline")
            return True # offline construction should be carried out
        
    def _offline(self):
        interpolation_method_name = self.EIM_approximation.parametrized_expression.interpolation_method_name()
        description = self.EIM_approximation.parametrized_expression.description()
        
        # Evaluate the parametrized expression for all parameters in the training set
        print(TextBox(interpolation_method_name + " preprocessing phase begins for" + "\n" + "\n".join(description), fill="="))
        print("")
        
        for (mu_index, mu) in enumerate(self.training_set):
            print(TextLine(interpolation_method_name + " " + str(mu_index), fill=":"))
            
            self.EIM_approximation.set_mu(mu)
            
            print("evaluate parametrized expression at mu =", mu)
            self.EIM_approximation.evaluate_parametrized_expression()
            self.EIM_approximation.export_solution(self.folder["snapshots"], "truth_" + str(mu_index))
            
            print("add to snapshots")
            self.add_to_snapshots(self.EIM_approximation.snapshot)

            print("")
            
        # If basis generation is POD, compute the first POD modes of the snapshots
        if self.EIM_approximation.basis_generation == "POD":
            print("compute basis")
            N_POD = self.compute_basis_POD()
            print("")
        
        print(TextBox(interpolation_method_name + " preprocessing phase ends for" + "\n" + "\n".join(description), fill="="))
        print("")
        
        print(TextBox(interpolation_method_name + " offline phase begins for" + "\n" + "\n".join(description), fill="="))
        print("")
        
        if self.EIM_approximation.basis_generation == "Greedy":
            # Arbitrarily start from the first parameter in the training set
            self.EIM_approximation.set_mu(self.training_set[0])
            
            # Carry out greedy selection
            relative_error_max = 2.*self.tol
            while self.EIM_approximation.N < self.Nmax and relative_error_max >= self.tol:
                print(TextLine(interpolation_method_name + " N = " + str(self.EIM_approximation.N), fill=":"))
                
                self._print_greedy_interpolation_solve_message()
                self.EIM_approximation.solve()
                
                print("compute and locate maximum interpolation error")
                self.EIM_approximation.snapshot = self.load_snapshot()
                (error, maximum_error, maximum_location) = self.EIM_approximation.compute_maximum_interpolation_error()
                
                print("update locations with", maximum_location)
                self.update_interpolation_locations(maximum_location)
                
                print("update basis")
                self.update_basis_greedy(error, maximum_error)
                
                print("update interpolation matrix")
                self.update_interpolation_matrix()
                
                (error_max, relative_error_max) = self.greedy()
                print("maximum interpolation error =", error_max)
                print("maximum interpolation relative error =", relative_error_max)
                
                print("")
        else:
            while self.EIM_approximation.N < N_POD:
                print(TextLine(interpolation_method_name + " N = " + str(self.EIM_approximation.N), fill=":"))
            
                print("solve interpolation for basis number", self.EIM_approximation.N)
                self.EIM_approximation._solve(self.EIM_approximation.basis_functions[self.EIM_approximation.N])
                
                print("compute and locate maximum interpolation error")
                self.EIM_approximation.snapshot = self.EIM_approximation.basis_functions[self.EIM_approximation.N]
                (error, maximum_error, maximum_location) = self.EIM_approximation.compute_maximum_interpolation_error()
                
                print("update locations with", maximum_location)
                self.update_interpolation_locations(maximum_location)
                
                self.EIM_approximation.N += 1
                
                print("update interpolation matrix")
                self.update_interpolation_matrix()
                
                print("")
                
        print(TextBox(interpolation_method_name + " offline phase ends for" + "\n" + "\n".join(description), fill="="))
        print("")
        
    # Finalize data structures required after the offline phase
    def _finalize_offline(self):
        self.EIM_approximation.init("online")
        
    def _print_greedy_interpolation_solve_message(self):
        print("solve interpolation for mu =", self.EIM_approximation.mu)
        
    # Update the snapshots container
    def add_to_snapshots(self, snapshot):
        self.snapshots_container.enrich(snapshot)
        
    # Update basis (greedy version)
    def update_basis_greedy(self, error, maximum_error):
        if abs(maximum_error) > 0.:
            self.EIM_approximation.basis_functions.enrich(error/maximum_error)
        else:
            # Trivial case, greedy will stop at the first iteration
            assert self.EIM_approximation.N == 0
            self.EIM_approximation.basis_functions.enrich(error) # error is actually zero
        self.EIM_approximation.basis_functions.save(self.EIM_approximation.folder["basis"], "basis")
        self.EIM_approximation.N += 1

    # Update basis (POD version)
    def compute_basis_POD(self):
        POD = self.EIM_approximation.parametrized_expression.create_POD_container()
        POD.store_snapshot(self.snapshots_container)
        (_, _, basis_functions, N) = POD.apply(self.Nmax, self.tol)
        self.EIM_approximation.basis_functions.enrich(basis_functions)
        self.EIM_approximation.basis_functions.save(self.EIM_approximation.folder["basis"], "basis")
        # do not increment self.EIM_approximation.N
        POD.print_eigenvalues(N)
        POD.save_eigenvalues_file(self.folder["post_processing"], "eigs")
        POD.save_retained_energy_file(self.folder["post_processing"], "retained_energy")
        return N
        
    def update_interpolation_locations(self, maximum_location):
        self.EIM_approximation.interpolation_locations.append(maximum_location)
        self.EIM_approximation.interpolation_locations.save(self.EIM_approximation.folder["reduced_operators"], "interpolation_locations")
    
    # Assemble the interpolation matrix
    def update_interpolation_matrix(self):
        self.EIM_approximation.interpolation_matrix[0] = evaluate(self.EIM_approximation.basis_functions[:self.EIM_approximation.N], self.EIM_approximation.interpolation_locations)
        self.EIM_approximation.interpolation_matrix.save(self.EIM_approximation.folder["reduced_operators"], "interpolation_matrix")
            
    # Load the precomputed snapshot
    def load_snapshot(self):
        assert self.EIM_approximation.basis_generation == "Greedy"
        mu = self.EIM_approximation.mu
        mu_index = self._training_set_parameters_to_snapshots_container_index[mu]
        assert mu == self.training_set[mu_index]
        return self.snapshots_container[mu_index]
        
    # Choose the next parameter in the offline stage in a greedy fashion
    def greedy(self):
        assert self.EIM_approximation.basis_generation == "Greedy"
        
        # Print some additional information on the consistency of the reduced basis
        self.EIM_approximation.solve()
        self.EIM_approximation.snapshot = self.load_snapshot()
        error = self.EIM_approximation.snapshot - self.EIM_approximation.basis_functions*self.EIM_approximation._interpolation_coefficients
        error_on_interpolation_locations = evaluate(error, self.EIM_approximation.interpolation_locations)
        (maximum_error, _) = max(abs(error))
        (maximum_error_on_interpolation_locations, _) = max(abs(error_on_interpolation_locations)) # for consistency check, should be zero
        print("interpolation error for current mu =", abs(maximum_error))
        print("interpolation error on interpolation locations for current mu =", abs(maximum_error_on_interpolation_locations))
        
        # Carry out the actual greedy search
        def solve_and_computer_error(mu):
            self.EIM_approximation.set_mu(mu)
            
            self.EIM_approximation.solve()
            self.EIM_approximation.snapshot = self.load_snapshot()
            (_, maximum_error, _) = self.EIM_approximation.compute_maximum_interpolation_error()
            return abs(maximum_error)
            
        print("find next mu")
        (error_max, error_argmax) = self.training_set.max(solve_and_computer_error)
        self.EIM_approximation.set_mu(self.training_set[error_argmax])
        self.greedy_selected_parameters.append(self.training_set[error_argmax])
        self.greedy_selected_parameters.save(self.folder["post_processing"], "mu_greedy")
        self.greedy_errors.append(error_max)
        self.greedy_errors.save(self.folder["post_processing"], "error_max")
        if abs(self.greedy_errors[0]) > 0.:
            return (abs(error_max), abs(error_max/self.greedy_errors[0]))
        else:
            # Trivial case, greedy will stop at the first iteration
            assert len(self.greedy_errors) == 1
            assert self.EIM_approximation.N == 1
            return (0., 0.)
    
    # Compute the error of the empirical interpolation approximation with respect to the
    # exact function over the testing set
    def error_analysis(self, N_generator=None, filename=None, **kwargs):
        assert len(kwargs) == 0 # not used in this method
            
        self._init_error_analysis(**kwargs)
        self._error_analysis(N_generator, filename, **kwargs)
        self._finalize_error_analysis(**kwargs)
        
    def _error_analysis(self, N_generator=None, filename=None, **kwargs):
        if N_generator is None:
            def N_generator(n):
                return n
                
        N = self.EIM_approximation.N
        interpolation_method_name = self.EIM_approximation.parametrized_expression.interpolation_method_name()
        description = self.EIM_approximation.parametrized_expression.description()
        
        print(TextBox(interpolation_method_name + " error analysis begins for" + "\n" + "\n".join(description), fill="="))
        print("")
        
        error_analysis_table = ErrorAnalysisTable(self.testing_set)
        error_analysis_table.set_Nmax(N)
        error_analysis_table.add_column("error", group_name="eim", operations=("mean", "max"))
        error_analysis_table.add_column("relative_error", group_name="eim", operations=("mean", "max"))
        
        for (mu_index, mu) in enumerate(self.testing_set):
            print(TextLine(interpolation_method_name + " " + str(mu_index), fill=":"))
            
            self.EIM_approximation.set_mu(mu)
            
            # Evaluate the exact function on the truth grid
            self.EIM_approximation.evaluate_parametrized_expression()
            
            for n in range(1, N + 1): # n = 1, ... N
                n_arg = N_generator(n)
                
                if n_arg is not None:
                    self.EIM_approximation.solve(n_arg)
                    (_, error, _) = self.EIM_approximation.compute_maximum_interpolation_error(n)
                    (_, relative_error, _) = self.EIM_approximation.compute_maximum_interpolation_relative_error(n)
                    error_analysis_table["error", n, mu_index] = abs(error)
                    error_analysis_table["relative_error", n, mu_index] = abs(relative_error)
                else:
                    error_analysis_table["error", n, mu_index] = NotImplemented
                    error_analysis_table["relative_error", n, mu_index] = NotImplemented
        
        # Print
        print("")
        print(error_analysis_table)
        
        print("")
        print(TextBox(interpolation_method_name + " error analysis ends for" + "\n" + "\n".join(description), fill="="))
        print("")
        
        # Export error analysis table
        error_analysis_table.save(self.folder["error_analysis"], "error_analysis" if filename is None else filename)
        
    # Compute the speedup of the empirical interpolation approximation with respect to the
    # exact function over the testing set
    def speedup_analysis(self, N_generator=None, filename=None, **kwargs):
        assert len(kwargs) == 0 # not used in this method
            
        self._init_speedup_analysis(**kwargs)
        self._speedup_analysis(N_generator, filename, **kwargs)
        self._finalize_speedup_analysis(**kwargs)
        
    # Initialize data structures required for the speedup analysis phase
    def _init_speedup_analysis(self, **kwargs):
        # Make sure to clean up snapshot cache to ensure that parametrized
        # expression evaluation is actually carried out
        self.EIM_approximation.snapshot_cache.clear()
        # ... and also disable the capability of importing/exporting truth solutions
        self.disable_import_solution = PatchInstanceMethod(self.EIM_approximation, "import_solution", lambda self_, folder, filename, solution=None: False)
        self.disable_export_solution = PatchInstanceMethod(self.EIM_approximation, "export_solution", lambda self_, folder, filename, solution=None: None)
        self.disable_import_solution.patch()
        self.disable_export_solution.patch()
        
    def _speedup_analysis(self, N_generator=None, filename=None, **kwargs):
        if N_generator is None:
            def N_generator(n):
                return n
                
        N = self.EIM_approximation.N
        interpolation_method_name = self.EIM_approximation.parametrized_expression.interpolation_method_name()
        description = self.EIM_approximation.parametrized_expression.description()
        
        print(TextBox(interpolation_method_name + " speedup analysis begins for" + "\n" + "\n".join(description), fill="="))
        print("")
        
        speedup_analysis_table = SpeedupAnalysisTable(self.testing_set)
        speedup_analysis_table.set_Nmax(N)
        speedup_analysis_table.add_column("speedup", group_name="speedup", operations=("min", "mean", "max"))
        
        evaluate_timer = Timer("parallel")
        EIM_timer = Timer("serial")
        
        for (mu_index, mu) in enumerate(self.testing_set):
            print(TextLine(interpolation_method_name + " " + str(mu_index), fill=":"))
            
            self.EIM_approximation.set_mu(mu)
            
            # Evaluate the exact function on the truth grid
            evaluate_timer.start()
            self.EIM_approximation.evaluate_parametrized_expression()
            elapsed_evaluate = evaluate_timer.stop()
            
            for n in range(1, N + 1): # n = 1, ... N
                n_arg = N_generator(n)
                
                if n_arg is not None:
                    EIM_timer.start()
                    self.EIM_approximation.solve(n_arg)
                    elapsed_EIM = EIM_timer.stop()
                    speedup_analysis_table["speedup", n, mu_index] = elapsed_evaluate/elapsed_EIM
                else:
                    speedup_analysis_table["speedup", n, mu_index] = NotImplemented
        
        # Print
        print("")
        print(speedup_analysis_table)
        
        print("")
        print(TextBox(interpolation_method_name + " speedup analysis ends for" + "\n" + "\n".join(description), fill="="))
        print("")
        
        # Export speedup analysis table
        speedup_analysis_table.save(self.folder["speedup_analysis"], "speedup_analysis" if filename is None else filename)
        
    # Finalize data structures required after the speedup analysis phase
    def _finalize_speedup_analysis(self, **kwargs):
        # Restore the capability to import/export truth solutions
        self.disable_import_solution.unpatch()
        self.disable_export_solution.unpatch()
        del self.disable_import_solution
        del self.disable_export_solution
Exemple #6
0
    class RBReduction_Class(DifferentialProblemReductionMethod_DerivedClass):
        """
        The folders used to store the snapshots and for the post processing data, the parameters for the greedy algorithm and the error estimator evaluations are initialized.
        
        :param truth_problem: class of the truth problem to be solved.
        :return: reduced RB class.
       
        """
        def __init__(self, truth_problem, **kwargs):
            # Call the parent initialization
            DifferentialProblemReductionMethod_DerivedClass.__init__(
                self, truth_problem, **kwargs)

            # Declare a GS object
            self.GS = None  # GramSchmidt (for problems with one component) or dict of GramSchmidt (for problem with several components)
            # I/O
            self.folder["snapshots"] = os.path.join(self.folder_prefix,
                                                    "snapshots")
            self.folder["post_processing"] = os.path.join(
                self.folder_prefix, "post_processing")
            self.greedy_selected_parameters = GreedySelectedParametersList()
            self.greedy_error_estimators = GreedyErrorEstimatorsList()
            self.label = "RB"

        def _init_offline(self):
            # Call parent to initialize inner product and reduced problem
            output = DifferentialProblemReductionMethod_DerivedClass._init_offline(
                self)

            # Declare a new GS for each basis component
            if len(self.truth_problem.components) > 1:
                self.GS = dict()
                for component in self.truth_problem.components:
                    assert len(
                        self.truth_problem.inner_product[component]) == 1
                    inner_product = self.truth_problem.inner_product[
                        component][0]
                    self.GS[component] = GramSchmidt(self.truth_problem.V,
                                                     inner_product)
            else:
                assert len(self.truth_problem.inner_product) == 1
                inner_product = self.truth_problem.inner_product[0]
                self.GS = GramSchmidt(self.truth_problem.V, inner_product)

            # Return
            return output

        def offline(self):
            """
            It performs the offline phase of the reduced order model.
            
            :return: reduced_problem where all offline data are stored.
            """
            need_to_do_offline_stage = self._init_offline()
            if need_to_do_offline_stage:
                self._offline()
            self._finalize_offline()
            return self.reduced_problem

        @snapshot_links_to_cache
        def _offline(self):
            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " offline phase begins",
                        fill="="))
            print("")

            # Initialize first parameter to be used
            self.reduced_problem.build_reduced_operators()
            self.reduced_problem.build_error_estimation_operators()
            (absolute_error_estimator_max,
             relative_error_estimator_max) = self.greedy()
            print(
                "initial maximum absolute error estimator over training set =",
                absolute_error_estimator_max)
            print(
                "initial maximum relative error estimator over training set =",
                relative_error_estimator_max)

            print("")

            iteration = 0
            while self.reduced_problem.N < self.Nmax and relative_error_estimator_max >= self.tol:
                print(TextLine("N = " + str(self.reduced_problem.N), fill="#"))

                print("truth solve for mu =", self.truth_problem.mu)
                snapshot = self.truth_problem.solve()
                self.truth_problem.export_solution(self.folder["snapshots"],
                                                   "truth_" + str(iteration),
                                                   snapshot)
                snapshot = self.postprocess_snapshot(snapshot, iteration)

                print("update basis matrix")
                self.update_basis_matrix(snapshot)
                iteration += 1

                print("build reduced operators")
                self.reduced_problem.build_reduced_operators()

                print("reduced order solve")
                self.reduced_problem.solve()

                print("build operators for error estimation")
                self.reduced_problem.build_error_estimation_operators()

                (absolute_error_estimator_max,
                 relative_error_estimator_max) = self.greedy()
                print("maximum absolute error estimator over training set =",
                      absolute_error_estimator_max)
                print("maximum relative error estimator over training set =",
                      relative_error_estimator_max)

                print("")

            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " offline phase ends",
                        fill="="))
            print("")

        def update_basis_matrix(self, snapshot):
            """
            It updates basis matrix.
            
            :param snapshot: last offline solution calculated.
            """
            if len(self.truth_problem.components) > 1:
                for component in self.truth_problem.components:
                    new_basis_function = self.GS[component].apply(
                        snapshot,
                        self.reduced_problem.basis_functions[component]
                        [self.reduced_problem.N_bc[component]:],
                        component=component)
                    self.reduced_problem.basis_functions.enrich(
                        new_basis_function, component=component)
                    self.reduced_problem.N[component] += 1
                self.reduced_problem.basis_functions.save(
                    self.reduced_problem.folder["basis"], "basis")
            else:
                new_basis_function = self.GS.apply(
                    snapshot, self.reduced_problem.
                    basis_functions[self.reduced_problem.N_bc:])
                self.reduced_problem.basis_functions.enrich(new_basis_function)
                self.reduced_problem.N += 1
                self.reduced_problem.basis_functions.save(
                    self.reduced_problem.folder["basis"], "basis")

        def greedy(self):
            """
            It chooses the next parameter in the offline stage in a greedy fashion: wrapper with post processing of the result (in particular, set greedily selected parameter and save to file)
            
            :return: max error estimator and the comparison with the first one calculated.
            """
            (error_estimator_max, error_estimator_argmax) = self._greedy()
            self.truth_problem.set_mu(
                self.training_set[error_estimator_argmax])
            self.greedy_selected_parameters.append(
                self.training_set[error_estimator_argmax])
            self.greedy_selected_parameters.save(
                self.folder["post_processing"], "mu_greedy")
            self.greedy_error_estimators.append(error_estimator_max)
            self.greedy_error_estimators.save(self.folder["post_processing"],
                                              "error_estimator_max")
            return (error_estimator_max,
                    error_estimator_max / self.greedy_error_estimators[0])

        def _greedy(self):
            """
            It chooses the next parameter in the offline stage in a greedy fashion. Internal method.
            
            :return: max error estimator and the respective parameter.
            """

            if self.reduced_problem.N > 0:  # skip during initialization
                # Print some additional information on the consistency of the reduced basis
                print("absolute error for current mu =",
                      self.reduced_problem.compute_error())
                print("absolute error estimator for current mu =",
                      self.reduced_problem.estimate_error())

            # Carry out the actual greedy search
            def solve_and_estimate_error(mu):
                self.reduced_problem.set_mu(mu)
                self.reduced_problem.solve()
                error_estimator = self.reduced_problem.estimate_error()
                logger.log(
                    DEBUG, "Error estimator for mu = " + str(mu) + " is " +
                    str(error_estimator))
                return error_estimator

            if self.reduced_problem.N == 0:
                print("find initial mu")
            else:
                print("find next mu")

            return self.training_set.max(solve_and_estimate_error)

        def error_analysis(self, N_generator=None, filename=None, **kwargs):
            """
            It computes the error of the reduced order approximation with respect to the full order one over the testing set.
            
            :param N_generator: generator of dimension of reduced problem.
            """
            self._init_error_analysis(**kwargs)
            self._error_analysis(N_generator, filename, **kwargs)
            self._finalize_error_analysis(**kwargs)

        def _error_analysis(self, N_generator=None, filename=None, **kwargs):
            if N_generator is None:

                def N_generator():
                    N = self.reduced_problem.N
                    if isinstance(N, dict):
                        N = min(N.values())
                    for n in range(1, N + 1):  # n = 1, ... N
                        yield n

            if "components" in kwargs:
                components = kwargs["components"]
            else:
                components = self.truth_problem.components

            def N_generator_items():
                for n in N_generator():
                    assert isinstance(n, (dict, int))
                    if isinstance(n, int):
                        yield (n, n)
                    elif isinstance(n, dict):
                        assert len(n) == 1
                        (n_int, n_online_size_dict) = n.popitem()
                        assert isinstance(n_int, int)
                        assert isinstance(n_online_size_dict, OnlineSizeDict)
                        yield (n_int, n_online_size_dict)
                    else:
                        raise TypeError(
                            "Invalid item generated by N_generator")

            def N_generator_max():
                *_, Nmax = N_generator_items()
                assert isinstance(Nmax, tuple)
                assert len(Nmax) == 2
                assert isinstance(Nmax[0], int)
                return Nmax[0]

            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " error analysis begins",
                        fill="="))
            print("")

            error_analysis_table = ErrorAnalysisTable(self.testing_set)
            error_analysis_table.set_Nmax(N_generator_max())
            if len(components) > 1:
                all_components_string = "".join(components)
                for component in components:
                    error_analysis_table.add_column("error_" + component,
                                                    group_name="solution_" +
                                                    component + "_error",
                                                    operations=("mean", "max"))
                    error_analysis_table.add_column(
                        "relative_error_" + component,
                        group_name="solution_" + component + "_relative_error",
                        operations=("mean", "max"))
                error_analysis_table.add_column(
                    "error_" + all_components_string,
                    group_name="solution_" + all_components_string + "_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "error_estimator_" + all_components_string,
                    group_name="solution_" + all_components_string + "_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "effectivity_" + all_components_string,
                    group_name="solution_" + all_components_string + "_error",
                    operations=("min", "mean", "max"))
                error_analysis_table.add_column(
                    "relative_error_" + all_components_string,
                    group_name="solution_" + all_components_string +
                    "_relative_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "relative_error_estimator_" + all_components_string,
                    group_name="solution_" + all_components_string +
                    "_relative_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "relative_effectivity_" + all_components_string,
                    group_name="solution_" + all_components_string +
                    "_relative_error",
                    operations=("min", "mean", "max"))
            else:
                component = components[0]
                error_analysis_table.add_column("error_" + component,
                                                group_name="solution_" +
                                                component + "_error",
                                                operations=("mean", "max"))
                error_analysis_table.add_column("error_estimator_" + component,
                                                group_name="solution_" +
                                                component + "_error",
                                                operations=("mean", "max"))
                error_analysis_table.add_column(
                    "effectivity_" + component,
                    group_name="solution_" + component + "_error",
                    operations=("min", "mean", "max"))
                error_analysis_table.add_column("relative_error_" + component,
                                                group_name="solution_" +
                                                component + "_relative_error",
                                                operations=("mean", "max"))
                error_analysis_table.add_column(
                    "relative_error_estimator_" + component,
                    group_name="solution_" + component + "_relative_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "relative_effectivity_" + component,
                    group_name="solution_" + component + "_relative_error",
                    operations=("min", "mean", "max"))
            error_analysis_table.add_column("error_output",
                                            group_name="output_error",
                                            operations=("mean", "max"))
            error_analysis_table.add_column("error_estimator_output",
                                            group_name="output_error",
                                            operations=("mean", "max"))
            error_analysis_table.add_column("effectivity_output",
                                            group_name="output_error",
                                            operations=("min", "mean", "max"))
            error_analysis_table.add_column("relative_error_output",
                                            group_name="output_relative_error",
                                            operations=("mean", "max"))
            error_analysis_table.add_column("relative_error_estimator_output",
                                            group_name="output_relative_error",
                                            operations=("mean", "max"))
            error_analysis_table.add_column("relative_effectivity_output",
                                            group_name="output_relative_error",
                                            operations=("min", "mean", "max"))

            for (mu_index, mu) in enumerate(self.testing_set):
                print(TextLine(str(mu_index), fill="#"))

                self.reduced_problem.set_mu(mu)

                for (n_int, n_arg) in N_generator_items():
                    self.reduced_problem.solve(n_arg, **kwargs)
                    error = self.reduced_problem.compute_error(**kwargs)
                    if len(components) > 1:
                        error[all_components_string] = sqrt(
                            sum([
                                error[component]**2 for component in components
                            ]))
                    error_estimator = self.reduced_problem.estimate_error()
                    relative_error = self.reduced_problem.compute_relative_error(
                        **kwargs)
                    if len(components) > 1:
                        relative_error[all_components_string] = sqrt(
                            sum([
                                relative_error[component]**2
                                for component in components
                            ]))
                    relative_error_estimator = self.reduced_problem.estimate_relative_error(
                    )

                    self.reduced_problem.compute_output()
                    error_output = self.reduced_problem.compute_error_output(
                        **kwargs)
                    error_output_estimator = self.reduced_problem.estimate_error_output(
                    )
                    relative_error_output = self.reduced_problem.compute_relative_error_output(
                        **kwargs)
                    relative_error_output_estimator = self.reduced_problem.estimate_relative_error_output(
                    )

                    if len(components) > 1:
                        for component in components:
                            error_analysis_table["error_" + component, n_int,
                                                 mu_index] = error[component]
                            error_analysis_table[
                                "relative_error_" + component, n_int,
                                mu_index] = relative_error[component]
                        error_analysis_table[
                            "error_" + all_components_string, n_int,
                            mu_index] = error[all_components_string]
                        error_analysis_table["error_estimator_" +
                                             all_components_string, n_int,
                                             mu_index] = error_estimator
                        error_analysis_table[
                            "effectivity_" + all_components_string, n_int,
                            mu_index] = error_analysis_table[
                                "error_estimator_" + all_components_string,
                                n_int, mu_index] / error_analysis_table[
                                    "error_" + all_components_string, n_int,
                                    mu_index]
                        error_analysis_table[
                            "relative_error_" + all_components_string, n_int,
                            mu_index] = relative_error[all_components_string]
                        error_analysis_table[
                            "relative_error_estimator_" +
                            all_components_string, n_int,
                            mu_index] = relative_error_estimator
                        error_analysis_table[
                            "relative_effectivity_" + all_components_string,
                            n_int, mu_index] = error_analysis_table[
                                "relative_error_estimator_" +
                                all_components_string, n_int,
                                mu_index] / error_analysis_table[
                                    "relative_error_" + all_components_string,
                                    n_int, mu_index]
                    else:
                        component = components[0]
                        error_analysis_table["error_" + component, n_int,
                                             mu_index] = error
                        error_analysis_table["error_estimator_" + component,
                                             n_int, mu_index] = error_estimator
                        error_analysis_table[
                            "effectivity_" + component, n_int,
                            mu_index] = error_analysis_table[
                                "error_estimator_" + component, n_int,
                                mu_index] / error_analysis_table[
                                    "error_" + component, n_int, mu_index]
                        error_analysis_table["relative_error_" + component,
                                             n_int, mu_index] = relative_error
                        error_analysis_table[
                            "relative_error_estimator_" + component, n_int,
                            mu_index] = relative_error_estimator
                        error_analysis_table[
                            "relative_effectivity_" + component, n_int,
                            mu_index] = error_analysis_table[
                                "relative_error_estimator_" + component, n_int,
                                mu_index] / error_analysis_table[
                                    "relative_error_" + component, n_int,
                                    mu_index]

                    error_analysis_table["error_output", n_int,
                                         mu_index] = error_output
                    error_analysis_table["error_estimator_output", n_int,
                                         mu_index] = error_output_estimator
                    error_analysis_table[
                        "effectivity_output", n_int,
                        mu_index] = error_analysis_table[
                            "error_estimator_output", n_int,
                            mu_index] / error_analysis_table["error_output",
                                                             n_int, mu_index]
                    error_analysis_table["relative_error_output", n_int,
                                         mu_index] = relative_error_output
                    error_analysis_table[
                        "relative_error_estimator_output", n_int,
                        mu_index] = relative_error_output_estimator
                    error_analysis_table[
                        "relative_effectivity_output", n_int,
                        mu_index] = error_analysis_table[
                            "relative_error_estimator_output", n_int,
                            mu_index] / error_analysis_table[
                                "relative_error_output", n_int, mu_index]

            # Print
            print("")
            print(error_analysis_table)

            print("")
            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " error analysis ends",
                        fill="="))
            print("")

            # Export error analysis table
            error_analysis_table.save(
                self.folder["error_analysis"],
                "error_analysis" if filename is None else filename)

        def speedup_analysis(self, N_generator=None, filename=None, **kwargs):
            """
            It computes the speedup of the reduced order approximation with respect to the full order one over the testing set.
            
            :param N_generator: generator of dimension of the reduced problem.
            """
            self._init_speedup_analysis(**kwargs)
            self._speedup_analysis(N_generator, filename, **kwargs)
            self._finalize_speedup_analysis(**kwargs)

        def _speedup_analysis(self, N_generator=None, filename=None, **kwargs):
            if N_generator is None:

                def N_generator():
                    N = self.reduced_problem.N
                    if isinstance(N, dict):
                        N = min(N.values())
                    for n in range(1, N + 1):  # n = 1, ... N
                        yield n

            def N_generator_items():
                for n in N_generator():
                    assert isinstance(n, (dict, int))
                    if isinstance(n, int):
                        yield (n, n)
                    elif isinstance(n, dict):
                        assert len(n) == 1
                        (n_int, n_online_size_dict) = n.popitem()
                        assert isinstance(n_int, int)
                        assert isinstance(n_online_size_dict, OnlineSizeDict)
                        yield (n_int, n_online_size_dict)
                    else:
                        raise TypeError(
                            "Invalid item generated by N_generator")

            def N_generator_max():
                *_, Nmax = N_generator_items()
                assert isinstance(Nmax, tuple)
                assert len(Nmax) == 2
                assert isinstance(Nmax[0], int)
                return Nmax[0]

            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " speedup analysis begins",
                        fill="="))
            print("")

            speedup_analysis_table = SpeedupAnalysisTable(self.testing_set)
            speedup_analysis_table.set_Nmax(N_generator_max())
            speedup_analysis_table.add_column("speedup_solve",
                                              group_name="speedup_solve",
                                              operations=("min", "mean",
                                                          "max"))
            speedup_analysis_table.add_column(
                "speedup_solve_and_estimate_error",
                group_name="speedup_solve_and_estimate_error",
                operations=("min", "mean", "max"))
            speedup_analysis_table.add_column(
                "speedup_solve_and_estimate_relative_error",
                group_name="speedup_solve_and_estimate_relative_error",
                operations=("min", "mean", "max"))
            speedup_analysis_table.add_column("speedup_output",
                                              group_name="speedup_output",
                                              operations=("min", "mean",
                                                          "max"))
            speedup_analysis_table.add_column(
                "speedup_output_and_estimate_error_output",
                group_name="speedup_output_and_estimate_error_output",
                operations=("min", "mean", "max"))
            speedup_analysis_table.add_column(
                "speedup_output_and_estimate_relative_error_output",
                group_name="speedup_output_and_estimate_relative_error_output",
                operations=("min", "mean", "max"))

            truth_timer = Timer("parallel")
            reduced_timer = Timer("serial")

            for (mu_index, mu) in enumerate(self.testing_set):
                print(TextLine(str(mu_index), fill="#"))

                self.reduced_problem.set_mu(mu)

                truth_timer.start()
                self.truth_problem.solve(**kwargs)
                elapsed_truth_solve = truth_timer.stop()

                truth_timer.start()
                self.truth_problem.compute_output()
                elapsed_truth_output = truth_timer.stop()

                for (n_int, n_arg) in N_generator_items():
                    reduced_timer.start()
                    solution = self.reduced_problem.solve(n_arg, **kwargs)
                    elapsed_reduced_solve = reduced_timer.stop()

                    truth_timer.start()
                    self.reduced_problem.compute_error(**kwargs)
                    elapsed_error = truth_timer.stop()

                    reduced_timer.start()
                    error_estimator = self.reduced_problem.estimate_error()
                    elapsed_error_estimator = reduced_timer.stop()

                    truth_timer.start()
                    self.reduced_problem.compute_relative_error(**kwargs)
                    elapsed_relative_error = truth_timer.stop()

                    reduced_timer.start()
                    relative_error_estimator = self.reduced_problem.estimate_relative_error(
                    )
                    elapsed_relative_error_estimator = reduced_timer.stop()

                    reduced_timer.start()
                    output = self.reduced_problem.compute_output()
                    elapsed_reduced_output = reduced_timer.stop()

                    truth_timer.start()
                    self.reduced_problem.compute_error_output(**kwargs)
                    elapsed_error_output = truth_timer.stop()

                    reduced_timer.start()
                    error_estimator_output = self.reduced_problem.estimate_error_output(
                    )
                    elapsed_error_estimator_output = reduced_timer.stop()

                    truth_timer.start()
                    self.reduced_problem.compute_relative_error_output(
                        **kwargs)
                    elapsed_relative_error_output = truth_timer.stop()

                    reduced_timer.start()
                    relative_error_estimator_output = self.reduced_problem.estimate_relative_error_output(
                    )
                    elapsed_relative_error_estimator_output = reduced_timer.stop(
                    )

                    if solution is not NotImplemented:
                        speedup_analysis_table[
                            "speedup_solve", n_int,
                            mu_index] = elapsed_truth_solve / elapsed_reduced_solve
                    else:
                        speedup_analysis_table["speedup_solve", n_int,
                                               mu_index] = NotImplemented
                    if error_estimator is not NotImplemented:
                        speedup_analysis_table[
                            "speedup_solve_and_estimate_error", n_int,
                            mu_index] = (elapsed_truth_solve + elapsed_error
                                         ) / (elapsed_reduced_solve +
                                              elapsed_error_estimator)
                    else:
                        speedup_analysis_table[
                            "speedup_solve_and_estimate_error", n_int,
                            mu_index] = NotImplemented
                    if relative_error_estimator is not NotImplemented:
                        speedup_analysis_table[
                            "speedup_solve_and_estimate_relative_error", n_int,
                            mu_index] = (elapsed_truth_solve +
                                         elapsed_relative_error) / (
                                             elapsed_reduced_solve +
                                             elapsed_relative_error_estimator)
                    else:
                        speedup_analysis_table[
                            "speedup_solve_and_estimate_relative_error", n_int,
                            mu_index] = NotImplemented
                    if output is not NotImplemented:
                        speedup_analysis_table[
                            "speedup_output", n_int,
                            mu_index] = (elapsed_truth_solve +
                                         elapsed_truth_output) / (
                                             elapsed_reduced_solve +
                                             elapsed_reduced_output)
                    else:
                        speedup_analysis_table["speedup_output", n_int,
                                               mu_index] = NotImplemented
                    if error_estimator_output is not NotImplemented:
                        assert output is not NotImplemented
                        speedup_analysis_table[
                            "speedup_output_and_estimate_error_output", n_int,
                            mu_index] = (elapsed_truth_solve +
                                         elapsed_truth_output +
                                         elapsed_error_output) / (
                                             elapsed_reduced_solve +
                                             elapsed_reduced_output +
                                             elapsed_error_estimator_output)
                    else:
                        speedup_analysis_table[
                            "speedup_output_and_estimate_error_output", n_int,
                            mu_index] = NotImplemented
                    if relative_error_estimator_output is not NotImplemented:
                        assert output is not NotImplemented
                        speedup_analysis_table[
                            "speedup_output_and_estimate_relative_error_output",
                            n_int, mu_index] = (
                                elapsed_truth_solve + elapsed_truth_output +
                                elapsed_relative_error_output) / (
                                    elapsed_reduced_solve +
                                    elapsed_reduced_output +
                                    elapsed_relative_error_estimator_output)
                    else:
                        speedup_analysis_table[
                            "speedup_output_and_estimate_relative_error_output",
                            n_int, mu_index] = NotImplemented

            # Print
            print("")
            print(speedup_analysis_table)

            print("")
            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " speedup analysis ends",
                        fill="="))
            print("")

            # Export speedup analysis table
            speedup_analysis_table.save(
                self.folder["speedup_analysis"],
                "speedup_analysis" if filename is None else filename)
Exemple #7
0
    def __init__(self, truth_problem, folder_prefix):
        # Call the parent initialization
        ParametrizedProblem.__init__(self, folder_prefix)
        # Store the parametrized problem object and the bc list
        self.truth_problem = truth_problem

        # Define additional storage for SCM
        self.bounding_box_min = BoundingBoxSideList(
        )  # minimum values of the bounding box. Vector of size Q
        self.bounding_box_max = BoundingBoxSideList(
        )  # maximum values of the bounding box. Vector of size Q
        self.training_set = None  # SCM algorithm needs the training set also in the online stage
        # greedy_selected_parameters: list storing the parameters selected during the training phase
        self.greedy_selected_parameters = GreedySelectedParametersList()
        # greedy_selected_parameters_complement: dict, over N, of list storing the complement of parameters
        # selected during the training phase
        self.greedy_selected_parameters_complement = dict()
        # upper_bound_vectors: list of Q-dimensional vectors storing the infimizing elements at the greedily
        # selected parameters
        self.upper_bound_vectors = UpperBoundsList()
        self.N = 0

        # Storage for online computations
        self._stability_factor_lower_bound = 0.
        self._stability_factor_upper_bound = 0.

        # I/O
        self.folder["cache"] = os.path.join(self.folder_prefix,
                                            "reduced_cache")
        self.folder["reduced_operators"] = os.path.join(
            self.folder_prefix, "reduced_operators")

        def _stability_factor_cache_key_generator(*args, **kwargs):
            assert len(args) == 2
            assert args[0] == self.mu
            assert len(kwargs) == 0
            return self._cache_key(args[1])

        def _stability_factor_cache_filename_generator(*args, **kwargs):
            assert len(args) == 2
            assert args[0] == self.mu
            assert len(kwargs) == 0
            return self._cache_file(args[1])

        def _stability_factor_lower_bound_cache_import(filename):
            self.import_stability_factor_lower_bound(self.folder["cache"],
                                                     filename)
            return self._stability_factor_lower_bound

        def _stability_factor_lower_bound_cache_export(filename):
            self.export_stability_factor_lower_bound(self.folder["cache"],
                                                     filename)

        self._stability_factor_lower_bound_cache = Cache(
            "SCM",
            key_generator=_stability_factor_cache_key_generator,
            import_=_stability_factor_lower_bound_cache_import,
            export=_stability_factor_lower_bound_cache_export,
            filename_generator=_stability_factor_cache_filename_generator)

        def _stability_factor_upper_bound_cache_import(filename):
            self.import_stability_factor_upper_bound(self.folder["cache"],
                                                     filename)
            return self._stability_factor_upper_bound

        def _stability_factor_upper_bound_cache_export(filename):
            self.export_stability_factor_upper_bound(self.folder["cache"],
                                                     filename)

        self._stability_factor_upper_bound_cache = Cache(
            "SCM",
            key_generator=_stability_factor_cache_key_generator,
            import_=_stability_factor_upper_bound_cache_import,
            export=_stability_factor_upper_bound_cache_export,
            filename_generator=_stability_factor_cache_filename_generator)

        # Stability factor eigen problem
        self.stability_factor_calculator = ParametrizedStabilityFactorEigenProblem(
            self.truth_problem, "smallest",
            self.truth_problem._eigen_solver_parameters["stability_factor"],
            self.folder_prefix)
Exemple #8
0
class SCMApproximation(ParametrizedProblem):

    # Default initialization of members
    @sync_setters("truth_problem", "set_mu", "mu")
    @sync_setters("truth_problem", "set_mu_range", "mu_range")
    def __init__(self, truth_problem, folder_prefix):
        # Call the parent initialization
        ParametrizedProblem.__init__(self, folder_prefix)
        # Store the parametrized problem object and the bc list
        self.truth_problem = truth_problem

        # Define additional storage for SCM
        self.bounding_box_min = BoundingBoxSideList(
        )  # minimum values of the bounding box. Vector of size Q
        self.bounding_box_max = BoundingBoxSideList(
        )  # maximum values of the bounding box. Vector of size Q
        self.training_set = None  # SCM algorithm needs the training set also in the online stage
        # greedy_selected_parameters: list storing the parameters selected during the training phase
        self.greedy_selected_parameters = GreedySelectedParametersList()
        # greedy_selected_parameters_complement: dict, over N, of list storing the complement of parameters
        # selected during the training phase
        self.greedy_selected_parameters_complement = dict()
        # upper_bound_vectors: list of Q-dimensional vectors storing the infimizing elements at the greedily
        # selected parameters
        self.upper_bound_vectors = UpperBoundsList()
        self.N = 0

        # Storage for online computations
        self._stability_factor_lower_bound = 0.
        self._stability_factor_upper_bound = 0.

        # I/O
        self.folder["cache"] = os.path.join(self.folder_prefix,
                                            "reduced_cache")
        self.folder["reduced_operators"] = os.path.join(
            self.folder_prefix, "reduced_operators")

        def _stability_factor_cache_key_generator(*args, **kwargs):
            assert len(args) == 2
            assert args[0] == self.mu
            assert len(kwargs) == 0
            return self._cache_key(args[1])

        def _stability_factor_cache_filename_generator(*args, **kwargs):
            assert len(args) == 2
            assert args[0] == self.mu
            assert len(kwargs) == 0
            return self._cache_file(args[1])

        def _stability_factor_lower_bound_cache_import(filename):
            self.import_stability_factor_lower_bound(self.folder["cache"],
                                                     filename)
            return self._stability_factor_lower_bound

        def _stability_factor_lower_bound_cache_export(filename):
            self.export_stability_factor_lower_bound(self.folder["cache"],
                                                     filename)

        self._stability_factor_lower_bound_cache = Cache(
            "SCM",
            key_generator=_stability_factor_cache_key_generator,
            import_=_stability_factor_lower_bound_cache_import,
            export=_stability_factor_lower_bound_cache_export,
            filename_generator=_stability_factor_cache_filename_generator)

        def _stability_factor_upper_bound_cache_import(filename):
            self.import_stability_factor_upper_bound(self.folder["cache"],
                                                     filename)
            return self._stability_factor_upper_bound

        def _stability_factor_upper_bound_cache_export(filename):
            self.export_stability_factor_upper_bound(self.folder["cache"],
                                                     filename)

        self._stability_factor_upper_bound_cache = Cache(
            "SCM",
            key_generator=_stability_factor_cache_key_generator,
            import_=_stability_factor_upper_bound_cache_import,
            export=_stability_factor_upper_bound_cache_export,
            filename_generator=_stability_factor_cache_filename_generator)

        # Stability factor eigen problem
        self.stability_factor_calculator = ParametrizedStabilityFactorEigenProblem(
            self.truth_problem, "smallest",
            self.truth_problem._eigen_solver_parameters["stability_factor"],
            self.folder_prefix)

    # Initialize data structures required for the online phase
    def init(self, current_stage="online"):
        assert current_stage in ("online", "offline")
        # Init truth problem, to setup stability_factor_{left,right}_hand_matrix operators
        self.truth_problem.init()
        # Init exact stability factor computations
        self.stability_factor_calculator.init()
        # Read/Initialize reduced order data structures
        if current_stage == "online":
            self.bounding_box_min.load(self.folder["reduced_operators"],
                                       "bounding_box_min")
            self.bounding_box_max.load(self.folder["reduced_operators"],
                                       "bounding_box_max")
            self.training_set.load(self.folder["reduced_operators"],
                                   "training_set")
            self.greedy_selected_parameters.load(
                self.folder["reduced_operators"], "greedy_selected_parameters")
            self.upper_bound_vectors.load(self.folder["reduced_operators"],
                                          "upper_bound_vectors")
            # Set the value of N
            self.N = len(self.greedy_selected_parameters)
        elif current_stage == "offline":
            # Properly resize structures related to operator
            Q = self.truth_problem.Q["stability_factor_left_hand_matrix"]
            self.bounding_box_min = BoundingBoxSideList(Q)
            self.bounding_box_max = BoundingBoxSideList(Q)
            # Save the training set, which was passed by the reduction method,
            # in order to use it online
            assert self.training_set is not None
            self.training_set.save(self.folder["reduced_operators"],
                                   "training_set")
            # Properly initialize structures related to greedy selected parameters
            assert len(self.greedy_selected_parameters) == 0
        else:
            raise ValueError("Invalid stage in init().")

    def evaluate_stability_factor(self):
        return self.stability_factor_calculator.solve()

    # Get a lower bound for the stability factor
    def get_stability_factor_lower_bound(self, N=None):
        if N is None:
            N = self.N
        try:
            self._stability_factor_lower_bound = self._stability_factor_lower_bound_cache[
                self.mu, N]
        except KeyError:
            self._get_stability_factor_lower_bound(N)
            self._stability_factor_lower_bound_cache[
                self.mu, N] = self._stability_factor_lower_bound
        return self._stability_factor_lower_bound

    def _get_stability_factor_lower_bound(self, N):
        assert N <= len(self.greedy_selected_parameters)
        Q = self.truth_problem.Q["stability_factor_left_hand_matrix"]
        M_e = N
        M_p = min(
            N,
            len(self.training_set) - len(self.greedy_selected_parameters))

        # 1. Constrain the Q variables to be in the bounding box
        bounds = list()  # of Q pairs
        for q in range(Q):
            assert self.bounding_box_min[q] <= self.bounding_box_max[q]
            bounds.append((self.bounding_box_min[q], self.bounding_box_max[q]))

        # 2. Add three different sets of constraints.
        #    Our constrains are of the form
        #       a^T * x >= b
        constraints_matrix = Matrix(M_e + M_p + 1, Q)
        constraints_vector = Vector(M_e + M_p + 1)

        # 2a. Add constraints: a constraint is added for the closest samples to mu among the selected parameters
        mu_bak = self.mu
        closest_selected_parameters = self._closest_selected_parameters(
            M_e, N, self.mu)
        for (j, omega) in enumerate(closest_selected_parameters):
            # Overwrite parameter values
            self.set_mu(omega)

            # Compute theta
            current_theta = self.truth_problem.compute_theta(
                "stability_factor_left_hand_matrix")

            # Assemble the LHS of the constraint
            for q in range(Q):
                constraints_matrix[j, q] = current_theta[q]

            # Assemble the RHS of the constraint: note that computations for this call may be already cached
            (constraints_vector[j], _) = self.evaluate_stability_factor()
        self.set_mu(mu_bak)

        # 2b. Add constraints: also constrain the closest point in the complement of selected parameters,
        #                      with RHS depending on previously computed lower bounds
        mu_bak = self.mu
        closest_selected_parameters_complement = self._closest_unselected_parameters(
            M_p, N, self.mu)
        for (j, nu) in enumerate(closest_selected_parameters_complement):
            # Overwrite parameter values
            self.set_mu(nu)

            # Compute theta
            current_theta = self.truth_problem.compute_theta(
                "stability_factor_left_hand_matrix")

            # Assemble the LHS of the constraint
            for q in range(Q):
                constraints_matrix[M_e + j, q] = current_theta[q]

            # Assemble the RHS of the constraint: note that computations for this call may be already cached
            if N > 1:
                constraints_vector[
                    M_e + j] = self.get_stability_factor_lower_bound(N - 1)
            else:
                constraints_vector[M_e + j] = 0.
        self.set_mu(mu_bak)

        # 2c. Add constraints: also constrain the stability factor for mu to be positive
        # Compute theta
        current_theta = self.truth_problem.compute_theta(
            "stability_factor_left_hand_matrix")

        # Assemble the LHS of the constraint
        for q in range(Q):
            constraints_matrix[M_e + M_p, q] = current_theta[q]

        # Assemble the RHS of the constraint
        constraints_vector[M_e + M_p] = 0.

        # 3. Add cost function coefficients
        cost = Vector(Q)
        for q in range(Q):
            cost[q] = current_theta[q]

        # 4. Solve the linear programming problem
        linear_program = LinearProgramSolver(cost, constraints_matrix,
                                             constraints_vector, bounds)
        try:
            stability_factor_lower_bound = linear_program.solve()
        except LinearProgramSolverError:
            print("SCM warning at mu = " + str(self.mu) +
                  ": error occured while solving linear program.")
            print(
                "Please consider switching to a different solver. A truth eigensolve will be performed."
            )

            (stability_factor_lower_bound,
             _) = self.evaluate_stability_factor()

        self._stability_factor_lower_bound = stability_factor_lower_bound

    # Get an upper bound for the stability factor
    def get_stability_factor_upper_bound(self, N=None):
        if N is None:
            N = self.N
        try:
            self._stability_factor_upper_bound = self._stability_factor_upper_bound_cache[
                self.mu, N]
        except KeyError:
            self._get_stability_factor_upper_bound(N)
            self._stability_factor_upper_bound_cache[
                self.mu, N] = self._stability_factor_upper_bound
        return self._stability_factor_upper_bound

    def _get_stability_factor_upper_bound(self, N):
        Q = self.truth_problem.Q["stability_factor_left_hand_matrix"]
        upper_bound_vectors = self.upper_bound_vectors

        stability_factor_upper_bound = None
        current_theta = self.truth_problem.compute_theta(
            "stability_factor_left_hand_matrix")

        for j in range(N):
            upper_bound_vector = upper_bound_vectors[j]

            # Compute the cost function for fixed omega
            obj = 0.
            for q in range(Q):
                obj += upper_bound_vector[q] * current_theta[q]

            if stability_factor_upper_bound is None or obj < stability_factor_upper_bound:
                stability_factor_upper_bound = obj

        assert stability_factor_upper_bound is not None
        self._stability_factor_upper_bound = stability_factor_upper_bound

    def _cache_key(self, N):
        return (self.mu, N)

    def _cache_file(self, N):
        return hashlib.sha1(str(
            self._cache_key(N)).encode("utf-8")).hexdigest()

    def _closest_selected_parameters(self, M, N, mu):
        return self.greedy_selected_parameters[:N].closest(M, mu)

    def _closest_unselected_parameters(self, M, N, mu):
        if N not in self.greedy_selected_parameters_complement:
            self.greedy_selected_parameters_complement[
                N] = self.training_set.diff(
                    self.greedy_selected_parameters[:N])
        return self.greedy_selected_parameters_complement[N].closest(M, mu)

    def export_stability_factor_lower_bound(self, folder=None, filename=None):
        if folder is None:
            folder = self.folder_prefix
        if filename is None:
            filename = "stability_factor"
        export([self._stability_factor_lower_bound], folder,
               filename + "_lower_bound")

    def export_stability_factor_upper_bound(self, folder=None, filename=None):
        if folder is None:
            folder = self.folder_prefix
        if filename is None:
            filename = "stability_factor"
        export([self._stability_factor_upper_bound], folder,
               filename + "_upper_bound")

    def import_stability_factor_lower_bound(self, folder=None, filename=None):
        if folder is None:
            folder = self.folder_prefix
        if filename is None:
            filename = "stability_factor"
        stability_factor_lower_bound_storage = [0.]
        import_(stability_factor_lower_bound_storage, folder,
                filename + "_lower_bound")
        assert len(stability_factor_lower_bound_storage) == 1
        self._stability_factor_lower_bound = stability_factor_lower_bound_storage[
            0]

    def import_stability_factor_upper_bound(self, folder=None, filename=None):
        if folder is None:
            folder = self.folder_prefix
        if filename is None:
            filename = "stability_factor"
        stability_factor_upper_bound_storage = [0.]
        import_(stability_factor_upper_bound_storage, folder,
                filename + "_upper_bound")
        assert len(stability_factor_upper_bound_storage) == 1
        self._stability_factor_upper_bound = stability_factor_upper_bound_storage[
            0]
Exemple #9
0
class SCMApproximation(ParametrizedProblem):

    # Default initialization of members
    @sync_setters("truth_problem", "set_mu", "mu")
    @sync_setters("truth_problem", "set_mu_range", "mu_range")
    def __init__(self, truth_problem, folder_prefix, **kwargs):
        # Call the parent initialization
        ParametrizedProblem.__init__(self, folder_prefix)
        # Store the parametrized problem object and the bc list
        self.truth_problem = truth_problem

        # Define additional storage for SCM
        self.B_min = BoundingBoxSideList(
        )  # minimum values of the bounding box mathcal{B}. Vector of size Q
        self.B_max = BoundingBoxSideList(
        )  # maximum values of the bounding box mathcal{B}. Vector of size Q
        self.training_set = None  # SCM algorithm needs the training set also in the online stage
        self.greedy_selected_parameters = GreedySelectedParametersList(
        )  # list storing the parameters selected during the training phase
        self.greedy_selected_parameters_complement = dict(
        )  # dict, over N, of list storing the complement of parameters selected during the training phase
        self.UB_vectors = UpperBoundsList(
        )  # list of Q-dimensional vectors storing the infimizing elements at the greedily selected parameters
        self.N = 0
        self.M_e = kwargs[
            "M_e"]  # integer denoting the number of constraints based on the exact eigenvalues, or None
        self.M_p = kwargs[
            "M_p"]  # integer denoting the number of constraints based on the previous lower bounds, or None

        # I/O
        self.folder["cache"] = os.path.join(self.folder_prefix,
                                            "reduced_cache")
        self.cache_config = config.get("SCM", "cache")
        self.folder["reduced_operators"] = os.path.join(
            self.folder_prefix, "reduced_operators")

        # Coercivity constant eigen problem
        self.exact_coercivity_constant_calculator = ParametrizedCoercivityConstantEigenProblem(
            truth_problem, "a", True, "smallest",
            kwargs["coercivity_eigensolver_parameters"], self.folder_prefix)

        # Store here input parameters provided by the user that are needed by the reduction method
        self._input_storage_for_SCM_reduction = dict()
        self._input_storage_for_SCM_reduction[
            "bounding_box_minimum_eigensolver_parameters"] = kwargs[
                "bounding_box_minimum_eigensolver_parameters"]
        self._input_storage_for_SCM_reduction[
            "bounding_box_maximum_eigensolver_parameters"] = kwargs[
                "bounding_box_maximum_eigensolver_parameters"]

        # Avoid useless linear programming solves
        self._alpha_LB = 0.
        self._alpha_LB_cache = dict()
        self._alpha_UB = 0.
        self._alpha_UB_cache = dict()

    # Initialize data structures required for the online phase
    def init(self, current_stage="online"):
        assert current_stage in ("online", "offline")
        # Read/Initialize reduced order data structures
        if current_stage == "online":
            self.B_min.load(self.folder["reduced_operators"], "B_min")
            self.B_max.load(self.folder["reduced_operators"], "B_max")
            self.training_set.load(self.folder["reduced_operators"],
                                   "training_set")
            self.greedy_selected_parameters.load(
                self.folder["reduced_operators"], "greedy_selected_parameters")
            self.UB_vectors.load(self.folder["reduced_operators"],
                                 "UB_vectors")
            # Set the value of N
            self.N = len(self.greedy_selected_parameters)
        elif current_stage == "offline":
            self.truth_problem.init()
            # Properly resize structures related to operator
            Q = self.truth_problem.Q["a"]
            self.B_min = BoundingBoxSideList(Q)
            self.B_max = BoundingBoxSideList(Q)
            # Save the training set, which was passed by the reduction method,
            # in order to use it online
            assert self.training_set is not None
            self.training_set.save(self.folder["reduced_operators"],
                                   "training_set")
            # Properly initialize structures related to greedy selected parameters
            assert len(self.greedy_selected_parameters) is 0
            # Init exact coercivity constant computations
            self.exact_coercivity_constant_calculator.init()
        else:
            raise ValueError("Invalid stage in init().")

    def evaluate_stability_factor(self):
        return self.exact_coercivity_constant_calculator.solve()

    # Get a lower bound for alpha
    def get_stability_factor_lower_bound(self, N=None):
        if N is None:
            N = self.N
        assert N <= len(self.greedy_selected_parameters)
        (cache_key, cache_file) = self._cache_key_and_file(N)
        if "RAM" in self.cache_config and cache_key in self._alpha_LB_cache:
            log(PROGRESS, "Loading stability factor lower bound from cache")
            self._alpha_LB = self._alpha_LB_cache[cache_key]
        elif "Disk" in self.cache_config and self.import_stability_factor_lower_bound(
                self.folder["cache"], cache_file):
            log(PROGRESS, "Loading stability factor lower bound from file")
            if "RAM" in self.cache_config:
                self._alpha_LB_cache[cache_key] = self._alpha_LB
        else:
            log(PROGRESS,
                "Solving stability factor lower bound reduced problem")
            Q = self.truth_problem.Q["a"]
            M_e = min(self.M_e if self.M_e is not None else N, N,
                      len(self.greedy_selected_parameters))
            M_p = min(
                self.M_p if self.M_p is not None else N, N,
                len(self.training_set) - len(self.greedy_selected_parameters))

            # 1. Constrain the Q variables to be in the bounding box
            bounds = list()  # of Q pairs
            for q in range(Q):
                assert self.B_min[q] <= self.B_max[q]
                bounds.append((self.B_min[q], self.B_max[q]))

            # 2. Add three different sets of constraints.
            #    Our constrains are of the form
            #       a^T * x >= b
            constraints_matrix = Matrix(M_e + M_p + 1, Q)
            constraints_vector = Vector(M_e + M_p + 1)

            # 2a. Add constraints: a constraint is added for the closest samples to mu among the selected parameters
            mu_bak = self.mu
            closest_selected_parameters = self._closest_selected_parameters(
                M_e, N, self.mu)
            for (j, omega) in enumerate(closest_selected_parameters):
                # Overwrite parameter values
                self.set_mu(omega)

                # Compute theta
                current_theta_a = self.truth_problem.compute_theta("a")

                # Assemble the LHS of the constraint
                for q in range(Q):
                    constraints_matrix[j, q] = current_theta_a[q]

                # Assemble the RHS of the constraint
                (constraints_vector[j], _) = self.evaluate_stability_factor(
                )  # note that computations for this call may be already cached
            self.set_mu(mu_bak)

            # 2b. Add constraints: also constrain the closest point in the complement of selected parameters,
            #                      with RHS depending on previously computed lower bounds
            mu_bak = self.mu
            closest_selected_parameters_complement = self._closest_unselected_parameters(
                M_p, N, self.mu)
            for (j, nu) in enumerate(closest_selected_parameters_complement):
                # Overwrite parameter values
                self.set_mu(nu)

                # Compute theta
                current_theta_a = self.truth_problem.compute_theta("a")

                # Assemble the LHS of the constraint
                for q in range(Q):
                    constraints_matrix[M_e + j, q] = current_theta_a[q]

                # Assemble the RHS of the constraint
                if N > 1:
                    constraints_vector[
                        M_e + j] = self.get_stability_factor_lower_bound(
                            N - 1
                        )  # note that computations for this call may be already cached
                else:
                    constraints_vector[M_e + j] = 0.
            self.set_mu(mu_bak)

            # 2c. Add constraints: also constrain the coercivity constant for mu to be positive
            # Compute theta
            current_theta_a = self.truth_problem.compute_theta("a")

            # Assemble the LHS of the constraint
            for q in range(Q):
                constraints_matrix[M_e + M_p, q] = current_theta_a[q]

            # Assemble the RHS of the constraint
            constraints_vector[M_e + M_p] = 0.

            # 3. Add cost function coefficients
            cost = Vector(Q)
            for q in range(Q):
                cost[q] = current_theta_a[q]

            # 4. Solve the linear programming problem
            linear_program = LinearProgramSolver(cost, constraints_matrix,
                                                 constraints_vector, bounds)
            try:
                alpha_LB = linear_program.solve()
            except LinearProgramSolverError:
                print("SCM warning at mu = " + str(self.mu) +
                      ": error occured while solving linear program.")
                print(
                    "Please consider switching to a different solver. A truth eigensolve will be performed."
                )

                (alpha_LB, _) = self.evaluate_stability_factor()

            self._alpha_LB = alpha_LB
            if "RAM" in self.cache_config:
                self._alpha_LB_cache[cache_key] = alpha_LB
            self.export_stability_factor_lower_bound(
                self.folder["cache"], cache_file
            )  # Note that we export to file regardless of config options, because they may change across different runs
        return self._alpha_LB

    # Get an upper bound for alpha
    def get_stability_factor_upper_bound(self, N=None):
        if N is None:
            N = self.N
        (cache_key, cache_file) = self._cache_key_and_file(N)
        if "RAM" in self.cache_config and cache_key in self._alpha_UB_cache:
            log(PROGRESS, "Loading stability factor upper bound from cache")
            self._alpha_UB = self._alpha_UB_cache[cache_key]
        elif "Disk" in self.cache_config and self.import_stability_factor_upper_bound(
                self.folder["cache"], cache_file):
            log(PROGRESS, "Loading stability factor upper bound from file")
            if "RAM" in self.cache_config:
                self._alpha_UB_cache[cache_key] = self._alpha_UB
        else:
            log(PROGRESS,
                "Solving stability factor upper bound reduced problem")
            Q = self.truth_problem.Q["a"]
            UB_vectors = self.UB_vectors

            alpha_UB = None
            current_theta_a = self.truth_problem.compute_theta("a")

            for j in range(N):
                UB_vector = UB_vectors[j]

                # Compute the cost function for fixed omega
                obj = 0.
                for q in range(Q):
                    obj += UB_vector[q] * current_theta_a[q]

                if alpha_UB is None or obj < alpha_UB:
                    alpha_UB = obj

            assert alpha_UB is not None
            self._alpha_UB = alpha_UB
            if "RAM" in self.cache_config:
                self._alpha_UB_cache[cache_key] = alpha_UB
            self.export_stability_factor_upper_bound(
                self.folder["cache"], cache_file
            )  # Note that we export to file regardless of config options, because they may change across different runs
        return self._alpha_UB

    def _cache_key_and_file(self, N):
        cache_key = (self.mu, N)
        cache_file = hashlib.sha1(str(cache_key).encode("utf-8")).hexdigest()
        return (cache_key, cache_file)

    def _closest_selected_parameters(self, M, N, mu):
        return self.greedy_selected_parameters[:N].closest(M, mu)

    def _closest_unselected_parameters(self, M, N, mu):
        if N not in self.greedy_selected_parameters_complement:
            self.greedy_selected_parameters_complement[
                N] = self.training_set.diff(
                    self.greedy_selected_parameters[:N])
        return self.greedy_selected_parameters_complement[N].closest(M, mu)

    def export_stability_factor_lower_bound(self, folder, filename):
        export([self._alpha_LB], folder, filename + "_LB")

    def export_stability_factor_upper_bound(self, folder, filename):
        export([self._alpha_UB], folder, filename + "_UB")

    def import_stability_factor_lower_bound(self, folder, filename):
        eigenvalue_storage = [0.]
        import_successful = import_(eigenvalue_storage, folder,
                                    filename + "_LB")
        if import_successful:
            assert len(eigenvalue_storage) == 1
            self._alpha_LB = eigenvalue_storage[0]
        return import_successful

    def import_stability_factor_upper_bound(self, folder, filename):
        eigenvalue_storage = [0.]
        import_successful = import_(eigenvalue_storage, folder,
                                    filename + "_UB")
        if import_successful:
            assert len(eigenvalue_storage) == 1
            self._alpha_UB = eigenvalue_storage[0]
        return import_successful
Exemple #10
0
    class RBReduction_Class(DifferentialProblemReductionMethod_DerivedClass):
        """
        The folders used to store the snapshots and for the post processing data, the parameters for the greedy algorithm and the error estimator evaluations are initialized.
        
        :param truth_problem: class of the truth problem to be solved.
        :return: reduced RB class.
       
        """
        def __init__(self, truth_problem, **kwargs):
            # Call the parent initialization
            DifferentialProblemReductionMethod_DerivedClass.__init__(
                self, truth_problem, **kwargs)

            # Declare a GS object
            self.GS = None  # GramSchmidt (for problems with one component) or dict of GramSchmidt (for problem with several components)
            # I/O
            self.folder["snapshots"] = os.path.join(self.folder_prefix,
                                                    "snapshots")
            self.folder["post_processing"] = os.path.join(
                self.folder_prefix, "post_processing")
            self.greedy_selected_parameters = GreedySelectedParametersList()
            self.greedy_error_estimators = GreedyErrorEstimatorsList()
            self.label = "RB"

        def _init_offline(self):
            # Call parent to initialize inner product and reduced problem
            output = DifferentialProblemReductionMethod_DerivedClass._init_offline(
                self)

            # Declare a new GS for each basis component
            if len(self.truth_problem.components) > 1:
                self.GS = dict()
                for component in self.truth_problem.components:
                    assert len(
                        self.truth_problem.inner_product[component]) == 1
                    inner_product = self.truth_problem.inner_product[
                        component][0]
                    self.GS[component] = GramSchmidt(inner_product)
            else:
                assert len(self.truth_problem.inner_product) == 1
                inner_product = self.truth_problem.inner_product[0]
                self.GS = GramSchmidt(inner_product)

            # The current value of mu may have been already used when computing lifting functions.
            # If so, we do not want to use that value again at the first greedy iteration, because
            # for steady linear problems with only one paremtrized BC the resulting first snapshot
            # would have been already stored in the basis, being exactly equal to the lifting.
            # To this end, we arbitrarily change the current value of mu to the first parameter
            # in the training set.
            if output:  # do not bother changing current mu if offline stage has been already completed
                need_to_change_mu = False
                if len(self.truth_problem.components) > 1:
                    for component in self.truth_problem.components:
                        if self.reduced_problem.dirichlet_bc[
                                component] and not self.reduced_problem.dirichlet_bc_are_homogeneous[
                                    component]:
                            need_to_change_mu = True
                            break
                else:
                    if self.reduced_problem.dirichlet_bc and not self.reduced_problem.dirichlet_bc_are_homogeneous:
                        need_to_change_mu = True
                if (need_to_change_mu and len(
                        self.truth_problem.mu
                ) > 0  # there is not much we can change in the trivial case without any parameter!
                    ):
                    new_mu = self.training_set[0]
                    assert self.truth_problem.mu != new_mu
                    self.truth_problem.set_mu(new_mu)

            # Return
            return output

        def offline(self):
            """
            It performs the offline phase of the reduced order model.
            
            :return: reduced_problem where all offline data are stored.
            """
            need_to_do_offline_stage = self._init_offline()
            if need_to_do_offline_stage:
                self._offline()
            self._finalize_offline()
            return self.reduced_problem

        def _offline(self):
            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " offline phase begins",
                        fill="="))
            print("")

            iteration = 0
            relative_error_estimator_max = 2. * self.tol
            while self.reduced_problem.N < self.Nmax and relative_error_estimator_max >= self.tol:
                print(TextLine("N = " + str(self.reduced_problem.N), fill="#"))

                print("truth solve for mu =", self.truth_problem.mu)
                snapshot = self.truth_problem.solve()
                self.truth_problem.export_solution(self.folder["snapshots"],
                                                   "truth_" + str(iteration),
                                                   snapshot)
                snapshot = self.postprocess_snapshot(snapshot, iteration)

                print("update basis matrix")
                self.update_basis_matrix(snapshot)
                iteration += 1

                print("build reduced operators")
                self.reduced_problem.build_reduced_operators()

                print("reduced order solve")
                self.reduced_problem.solve()

                print("build operators for error estimation")
                self.reduced_problem.build_error_estimation_operators()

                (absolute_error_estimator_max,
                 relative_error_estimator_max) = self.greedy()
                print("maximum absolute error estimator over training set =",
                      absolute_error_estimator_max)
                print("maximum relative error estimator over training set =",
                      relative_error_estimator_max)

                print("")

            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " offline phase ends",
                        fill="="))
            print("")

        def update_basis_matrix(self, snapshot):
            """
            It updates basis matrix.
            
            :param snapshot: last offline solution calculated.
            """
            if len(self.truth_problem.components) > 1:
                for component in self.truth_problem.components:
                    self.reduced_problem.basis_functions.enrich(
                        snapshot, component=component)
                    self.GS[component].apply(
                        self.reduced_problem.basis_functions[component],
                        self.reduced_problem.N_bc[component])
                    self.reduced_problem.N[component] += 1
                self.reduced_problem.basis_functions.save(
                    self.reduced_problem.folder["basis"], "basis")
            else:
                self.reduced_problem.basis_functions.enrich(snapshot)
                self.GS.apply(self.reduced_problem.basis_functions,
                              self.reduced_problem.N_bc)
                self.reduced_problem.N += 1
                self.reduced_problem.basis_functions.save(
                    self.reduced_problem.folder["basis"], "basis")

        def greedy(self):
            """
            It chooses the next parameter in the offline stage in a greedy fashion: wrapper with post processing of the result (in particular, set greedily selected parameter and save to file)
            
            :return: max error estimator and the comparison with the first one calculated.
            """
            (error_estimator_max, error_estimator_argmax) = self._greedy()
            self.truth_problem.set_mu(
                self.training_set[error_estimator_argmax])
            self.greedy_selected_parameters.append(
                self.training_set[error_estimator_argmax])
            self.greedy_selected_parameters.save(
                self.folder["post_processing"], "mu_greedy")
            self.greedy_error_estimators.append(error_estimator_max)
            self.greedy_error_estimators.save(self.folder["post_processing"],
                                              "error_estimator_max")
            return (error_estimator_max,
                    error_estimator_max / self.greedy_error_estimators[0])

        def _greedy(self):
            """
            It chooses the next parameter in the offline stage in a greedy fashion. Internal method.
            
            :return: max error estimator and the respective parameter.
            """

            # Print some additional information on the consistency of the reduced basis
            print("absolute error for current mu =",
                  self.reduced_problem.compute_error())
            print("absolute error estimator for current mu =",
                  self.reduced_problem.estimate_error())

            # Carry out the actual greedy search
            def solve_and_estimate_error(mu):
                self.reduced_problem.set_mu(mu)
                self.reduced_problem.solve()
                error_estimator = self.reduced_problem.estimate_error()
                log(
                    DEBUG, "Error estimator for mu = " + str(mu) + " is " +
                    str(error_estimator))
                return error_estimator

            print("find next mu")
            return self.training_set.max(solve_and_estimate_error)

        def error_analysis(self, N_generator=None, filename=None, **kwargs):
            """
            It computes the error of the reduced order approximation with respect to the full order one over the testing set.
            
            :param N: dimension of reduced problem.
            """
            self._init_error_analysis(**kwargs)
            self._error_analysis(N_generator, filename, **kwargs)
            self._finalize_error_analysis(**kwargs)

        def _error_analysis(self, N_generator=None, filename=None, **kwargs):
            if N_generator is None:

                def N_generator(n):
                    return n

            if "components" in kwargs:
                components = kwargs["components"]
            else:
                components = self.truth_problem.components

            N = self.reduced_problem.N
            if isinstance(N, dict):
                N = min(N.values())

            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " error analysis begins",
                        fill="="))
            print("")

            error_analysis_table = ErrorAnalysisTable(self.testing_set)
            error_analysis_table.set_Nmax(N)
            if len(components) > 1:
                all_components_string = "".join(components)
                for component in components:
                    error_analysis_table.add_column("error_" + component,
                                                    group_name="solution_" +
                                                    component + "_error",
                                                    operations=("mean", "max"))
                    error_analysis_table.add_column(
                        "relative_error_" + component,
                        group_name="solution_" + component + "_relative_error",
                        operations=("mean", "max"))
                error_analysis_table.add_column(
                    "error_" + all_components_string,
                    group_name="solution_" + all_components_string + "_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "error_estimator_" + all_components_string,
                    group_name="solution_" + all_components_string + "_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "effectivity_" + all_components_string,
                    group_name="solution_" + all_components_string + "_error",
                    operations=("min", "mean", "max"))
                error_analysis_table.add_column(
                    "relative_error_" + all_components_string,
                    group_name="solution_" + all_components_string +
                    "_relative_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "relative_error_estimator_" + all_components_string,
                    group_name="solution_" + all_components_string +
                    "_relative_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "relative_effectivity_" + all_components_string,
                    group_name="solution_" + all_components_string +
                    "_relative_error",
                    operations=("min", "mean", "max"))
            else:
                component = components[0]
                error_analysis_table.add_column("error_" + component,
                                                group_name="solution_" +
                                                component + "_error",
                                                operations=("mean", "max"))
                error_analysis_table.add_column("error_estimator_" + component,
                                                group_name="solution_" +
                                                component + "_error",
                                                operations=("mean", "max"))
                error_analysis_table.add_column(
                    "effectivity_" + component,
                    group_name="solution_" + component + "_error",
                    operations=("min", "mean", "max"))
                error_analysis_table.add_column("relative_error_" + component,
                                                group_name="solution_" +
                                                component + "_relative_error",
                                                operations=("mean", "max"))
                error_analysis_table.add_column(
                    "relative_error_estimator_" + component,
                    group_name="solution_" + component + "_relative_error",
                    operations=("mean", "max"))
                error_analysis_table.add_column(
                    "relative_effectivity_" + component,
                    group_name="solution_" + component + "_relative_error",
                    operations=("min", "mean", "max"))
            error_analysis_table.add_column("error_output",
                                            group_name="output_error",
                                            operations=("mean", "max"))
            error_analysis_table.add_column("error_estimator_output",
                                            group_name="output_error",
                                            operations=("mean", "max"))
            error_analysis_table.add_column("effectivity_output",
                                            group_name="output_error",
                                            operations=("min", "mean", "max"))
            error_analysis_table.add_column("relative_error_output",
                                            group_name="output_relative_error",
                                            operations=("mean", "max"))
            error_analysis_table.add_column("relative_error_estimator_output",
                                            group_name="output_relative_error",
                                            operations=("mean", "max"))
            error_analysis_table.add_column("relative_effectivity_output",
                                            group_name="output_relative_error",
                                            operations=("min", "mean", "max"))

            for (mu_index, mu) in enumerate(self.testing_set):
                print(TextLine(str(mu_index), fill="#"))

                self.reduced_problem.set_mu(mu)

                for n in range(1, N + 1):  # n = 1, ... N
                    n_arg = N_generator(n)

                    if n_arg is not None:
                        self.reduced_problem.solve(n_arg, **kwargs)
                        error = self.reduced_problem.compute_error(**kwargs)
                        if len(components) > 1:
                            error[all_components_string] = sqrt(
                                sum([
                                    error[component]**2
                                    for component in components
                                ]))
                        error_estimator = self.reduced_problem.estimate_error()
                        relative_error = self.reduced_problem.compute_relative_error(
                            **kwargs)
                        if len(components) > 1:
                            relative_error[all_components_string] = sqrt(
                                sum([
                                    relative_error[component]**2
                                    for component in components
                                ]))
                        relative_error_estimator = self.reduced_problem.estimate_relative_error(
                        )

                        self.reduced_problem.compute_output()
                        error_output = self.reduced_problem.compute_error_output(
                            **kwargs)
                        error_output_estimator = self.reduced_problem.estimate_error_output(
                        )
                        relative_error_output = self.reduced_problem.compute_relative_error_output(
                            **kwargs)
                        relative_error_output_estimator = self.reduced_problem.estimate_relative_error_output(
                        )
                    else:
                        if len(components) > 1:
                            error = {
                                component: NotImplemented
                                for component in components
                            }
                            error[all_components_string] = NotImplemented
                        else:
                            error = NotImplemented
                        error_estimator = NotImplemented
                        if len(components) > 1:
                            relative_error = {
                                component: NotImplemented
                                for component in components
                            }
                            relative_error[
                                all_components_string] = NotImplemented
                        else:
                            relative_error = NotImplemented
                        relative_error_estimator = NotImplemented

                        error_output = NotImplemented
                        error_output_estimator = NotImplemented
                        relative_error_output = NotImplemented
                        relative_error_output_estimator = NotImplemented

                    if len(components) > 1:
                        for component in components:
                            error_analysis_table["error_" + component, n,
                                                 mu_index] = error[component]
                            error_analysis_table[
                                "relative_error_" + component, n,
                                mu_index] = relative_error[component]
                        error_analysis_table[
                            "error_" + all_components_string, n,
                            mu_index] = error[all_components_string]
                        error_analysis_table["error_estimator_" +
                                             all_components_string, n,
                                             mu_index] = error_estimator
                        error_analysis_table[
                            "effectivity_" + all_components_string, n,
                            mu_index] = error_analysis_table[
                                "error_estimator_" + all_components_string, n,
                                mu_index] / error_analysis_table[
                                    "error_" + all_components_string, n,
                                    mu_index]
                        error_analysis_table[
                            "relative_error_" + all_components_string, n,
                            mu_index] = relative_error[all_components_string]
                        error_analysis_table[
                            "relative_error_estimator_" +
                            all_components_string, n,
                            mu_index] = relative_error_estimator
                        error_analysis_table[
                            "relative_effectivity_" + all_components_string, n,
                            mu_index] = error_analysis_table[
                                "relative_error_estimator_" +
                                all_components_string, n,
                                mu_index] / error_analysis_table[
                                    "relative_error_" + all_components_string,
                                    n, mu_index]
                    else:
                        component = components[0]
                        error_analysis_table["error_" + component, n,
                                             mu_index] = error
                        error_analysis_table["error_estimator_" + component, n,
                                             mu_index] = error_estimator
                        error_analysis_table[
                            "effectivity_" + component, n,
                            mu_index] = error_analysis_table[
                                "error_estimator_" + component, n,
                                mu_index] / error_analysis_table["error_" +
                                                                 component, n,
                                                                 mu_index]
                        error_analysis_table["relative_error_" + component, n,
                                             mu_index] = relative_error
                        error_analysis_table[
                            "relative_error_estimator_" + component, n,
                            mu_index] = relative_error_estimator
                        error_analysis_table[
                            "relative_effectivity_" + component, n,
                            mu_index] = error_analysis_table[
                                "relative_error_estimator_" + component, n,
                                mu_index] / error_analysis_table[
                                    "relative_error_" + component, n, mu_index]

                    error_analysis_table["error_output", n,
                                         mu_index] = error_output
                    error_analysis_table["error_estimator_output", n,
                                         mu_index] = error_output_estimator
                    error_analysis_table["effectivity_output", n,
                                         mu_index] = error_analysis_table[
                                             "error_estimator_output", n,
                                             mu_index] / error_analysis_table[
                                                 "error_output", n, mu_index]
                    error_analysis_table["relative_error_output", n,
                                         mu_index] = relative_error_output
                    error_analysis_table[
                        "relative_error_estimator_output", n,
                        mu_index] = relative_error_output_estimator
                    error_analysis_table[
                        "relative_effectivity_output", n,
                        mu_index] = error_analysis_table[
                            "relative_error_estimator_output", n,
                            mu_index] / error_analysis_table[
                                "relative_error_output", n, mu_index]

            # Print
            print("")
            print(error_analysis_table)

            print("")
            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " error analysis ends",
                        fill="="))
            print("")

            # Export error analysis table
            error_analysis_table.save(
                self.folder["error_analysis"],
                "error_analysis" if filename is None else filename)

        def speedup_analysis(self, N_generator=None, filename=None, **kwargs):
            """
            It computes the speedup of the reduced order approximation with respect to the full order one over the testing set.
            
            :param N: dimension of the reduced problem.
            """
            self._init_speedup_analysis(**kwargs)
            self._speedup_analysis(N_generator, filename, **kwargs)
            self._finalize_speedup_analysis(**kwargs)

        def _speedup_analysis(self, N_generator=None, filename=None, **kwargs):
            if N_generator is None:

                def N_generator(n):
                    return n

            N = self.reduced_problem.N
            if isinstance(N, dict):
                N = min(N.values())

            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " speedup analysis begins",
                        fill="="))
            print("")

            speedup_analysis_table = SpeedupAnalysisTable(self.testing_set)
            speedup_analysis_table.set_Nmax(N)
            speedup_analysis_table.add_column("speedup_solve",
                                              group_name="speedup_solve",
                                              operations=("min", "mean",
                                                          "max"))
            speedup_analysis_table.add_column(
                "speedup_solve_and_estimate_error",
                group_name="speedup_solve_and_estimate_error",
                operations=("min", "mean", "max"))
            speedup_analysis_table.add_column(
                "speedup_solve_and_estimate_relative_error",
                group_name="speedup_solve_and_estimate_relative_error",
                operations=("min", "mean", "max"))
            speedup_analysis_table.add_column("speedup_output",
                                              group_name="speedup_output",
                                              operations=("min", "mean",
                                                          "max"))
            speedup_analysis_table.add_column(
                "speedup_output_and_estimate_error_output",
                group_name="speedup_output_and_estimate_error_output",
                operations=("min", "mean", "max"))
            speedup_analysis_table.add_column(
                "speedup_output_and_estimate_relative_error_output",
                group_name="speedup_output_and_estimate_relative_error_output",
                operations=("min", "mean", "max"))

            truth_timer = Timer("parallel")
            reduced_timer = Timer("serial")

            for (mu_index, mu) in enumerate(self.testing_set):
                print(TextLine(str(mu_index), fill="#"))

                self.reduced_problem.set_mu(mu)

                truth_timer.start()
                self.truth_problem.solve(**kwargs)
                elapsed_truth_solve = truth_timer.stop()

                truth_timer.start()
                self.truth_problem.compute_output()
                elapsed_truth_output = truth_timer.stop()

                for n in range(1, N + 1):  # n = 1, ... N
                    n_arg = N_generator(n)

                    if n_arg is not None:
                        reduced_timer.start()
                        solution = self.reduced_problem.solve(n_arg, **kwargs)
                        elapsed_reduced_solve = reduced_timer.stop()

                        truth_timer.start()
                        self.reduced_problem.compute_error(**kwargs)
                        elapsed_error = truth_timer.stop()

                        reduced_timer.start()
                        error_estimator = self.reduced_problem.estimate_error()
                        elapsed_error_estimator = reduced_timer.stop()

                        truth_timer.start()
                        self.reduced_problem.compute_relative_error(**kwargs)
                        elapsed_relative_error = truth_timer.stop()

                        reduced_timer.start()
                        relative_error_estimator = self.reduced_problem.estimate_relative_error(
                        )
                        elapsed_relative_error_estimator = reduced_timer.stop()

                        reduced_timer.start()
                        output = self.reduced_problem.compute_output()
                        elapsed_reduced_output = reduced_timer.stop()

                        truth_timer.start()
                        self.reduced_problem.compute_error_output(**kwargs)
                        elapsed_error_output = truth_timer.stop()

                        reduced_timer.start()
                        error_estimator_output = self.reduced_problem.estimate_error_output(
                        )
                        elapsed_error_estimator_output = reduced_timer.stop()

                        truth_timer.start()
                        self.reduced_problem.compute_relative_error_output(
                            **kwargs)
                        elapsed_relative_error_output = truth_timer.stop()

                        reduced_timer.start()
                        relative_error_estimator_output = self.reduced_problem.estimate_relative_error_output(
                        )
                        elapsed_relative_error_estimator_output = reduced_timer.stop(
                        )
                    else:
                        solution = NotImplemented
                        error_estimator = NotImplemented
                        relative_error_estimator = NotImplemented

                        output = NotImplemented
                        error_estimator_output = NotImplemented
                        relative_error_estimator_output = NotImplemented

                    if solution is not NotImplemented:
                        speedup_analysis_table[
                            "speedup_solve", n,
                            mu_index] = elapsed_truth_solve / elapsed_reduced_solve
                    else:
                        speedup_analysis_table["speedup_solve", n,
                                               mu_index] = NotImplemented
                    if error_estimator is not NotImplemented:
                        speedup_analysis_table[
                            "speedup_solve_and_estimate_error", n,
                            mu_index] = (elapsed_truth_solve + elapsed_error
                                         ) / (elapsed_reduced_solve +
                                              elapsed_error_estimator)
                    else:
                        speedup_analysis_table[
                            "speedup_solve_and_estimate_error", n,
                            mu_index] = NotImplemented
                    if relative_error_estimator is not NotImplemented:
                        speedup_analysis_table[
                            "speedup_solve_and_estimate_relative_error", n,
                            mu_index] = (elapsed_truth_solve +
                                         elapsed_relative_error) / (
                                             elapsed_reduced_solve +
                                             elapsed_relative_error_estimator)
                    else:
                        speedup_analysis_table[
                            "speedup_solve_and_estimate_relative_error", n,
                            mu_index] = NotImplemented
                    if output is not NotImplemented:
                        speedup_analysis_table[
                            "speedup_output", n,
                            mu_index] = (elapsed_truth_solve +
                                         elapsed_truth_output) / (
                                             elapsed_reduced_solve +
                                             elapsed_reduced_output)
                    else:
                        speedup_analysis_table["speedup_output", n,
                                               mu_index] = NotImplemented
                    if error_estimator_output is not NotImplemented:
                        assert output is not NotImplemented
                        speedup_analysis_table[
                            "speedup_output_and_estimate_error_output", n,
                            mu_index] = (elapsed_truth_solve +
                                         elapsed_truth_output +
                                         elapsed_error_output) / (
                                             elapsed_reduced_solve +
                                             elapsed_reduced_output +
                                             elapsed_error_estimator_output)
                    else:
                        speedup_analysis_table[
                            "speedup_output_and_estimate_error_output", n,
                            mu_index] = NotImplemented
                    if relative_error_estimator_output is not NotImplemented:
                        assert output is not NotImplemented
                        speedup_analysis_table[
                            "speedup_output_and_estimate_relative_error_output",
                            n, mu_index] = (
                                elapsed_truth_solve + elapsed_truth_output +
                                elapsed_relative_error_output) / (
                                    elapsed_reduced_solve +
                                    elapsed_reduced_output +
                                    elapsed_relative_error_estimator_output)
                    else:
                        speedup_analysis_table[
                            "speedup_output_and_estimate_relative_error_output",
                            n, mu_index] = NotImplemented

            # Print
            print("")
            print(speedup_analysis_table)

            print("")
            print(
                TextBox(self.truth_problem.name() + " " + self.label +
                        " speedup analysis ends",
                        fill="="))
            print("")

            # Export speedup analysis table
            speedup_analysis_table.save(
                self.folder["speedup_analysis"],
                "speedup_analysis" if filename is None else filename)